first code

This commit is contained in:
Pavel 2021-05-13 21:08:55 +03:00
parent 5fb3636afc
commit 2429eab7f8
16 changed files with 725 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
composer.lock
vendor/*
.idea/*

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# pock
Easy to use HTTP mocking solution, compatible with PSR-18 and HTTPlug. Should resemble [gock](https://github.com/h2non/gock) in the end.
Project in its early development stage. Stay tuned!

28
composer.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "neur0toxine/pock",
"description": "PSR-18 compatible HTTP mock library",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Neur0toxine",
"email": "pashok9825@gmail.com"
}
],
"autoload": {
"psr-4": {
"Pock\\": "src/"
}
},
"require": {
"php": ">=7.1.0",
"psr/http-client": "^1.0",
"psr/http-message": "^1.0",
"php-http/httplug": "^1.0 || ^2.0"
},
"provide": {
"psr/http-client-implementation": "1.0",
"php-http/client-implementation": "1.0",
"php-http/async-client-implementation": "1.0"
}
}

91
src/Client.php Normal file
View File

@ -0,0 +1,91 @@
<?php
/**
* PHP 7.3
*
* @category Client
* @package Pock
*/
namespace Pock;
use Http\Client\HttpAsyncClient;
use Http\Client\HttpClient;
use Http\Client\Promise\HttpFulfilledPromise;
use Http\Promise\Promise;
use Pock\Exception\BrokenMockException;
use Pock\Exception\UnsupportedRequestException;
use Pock\Promise\HttpRejectedPromise;
use Psr\Http\Client\ClientInterface;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Class Client
*
* @category Client
* @package Pock
*/
class Client implements ClientInterface, HttpClient, HttpAsyncClient
{
/** @var \Pock\MockInterface[] */
private $mocks;
/**
* Client constructor.
*
* @param \Pock\MockInterface[] $mocks
*/
public function __construct(array $mocks)
{
$this->mocks = $mocks;
}
/**
* @throws \Throwable
*/
public function doSendRequest(RequestInterface $request): ResponseInterface
{
return $this->sendAsyncRequest($request)->wait();
}
/**
* @inheritDoc
* @throws \Throwable
*/
public function sendRequest(RequestInterface $request): ResponseInterface
{
return $this->doSendRequest($request);
}
/**
* @inheritDoc
* @throws \Throwable
*/
public function sendAsyncRequest(RequestInterface $request): Promise
{
foreach ($this->mocks as $mock) {
if ($mock->isFired()) {
continue;
}
if ($mock->getMatcher()->matches($request)) {
if (null !== $mock->getResponse()) {
$mock->markAsFired();
return new HttpFulfilledPromise($mock->getResponse());
}
if (null !== $mock->getThrowable()) {
$mock->markAsFired();
return new HttpRejectedPromise($mock->getThrowable());
}
throw new BrokenMockException($mock);
}
}
throw new UnsupportedRequestException();
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* PHP 7.3
*
* @category BrokenMockException
* @package Pock\Exception
*/
namespace Pock\Exception;
use Exception;
use Pock\MockInterface;
/**
* Class BrokenMockException
*
* @category BrokenMockException
* @package Pock\Exception
*/
class BrokenMockException extends Exception
{
/** @var \Pock\MockInterface */
private $mock;
/**
* AbstractMockException constructor.
*
* @param \Pock\MockInterface $mock
*/
public function __construct(MockInterface $mock)
{
parent::__construct('Neither response nor the throwable is set in the mock.');
$this->mock = $mock;
}
/**
* @return \Pock\MockInterface
*/
public function getMock(): MockInterface
{
return $this->mock;
}
}

View File

@ -0,0 +1,50 @@
<?php
/**
* PHP 7.3
*
* @category UniversalMockException
* @package Pock\Exception
*/
namespace Pock\Exception;
use Exception;
use Psr\Http\Client\ClientExceptionInterface;
use Psr\Http\Client\NetworkExceptionInterface;
use Psr\Http\Client\RequestExceptionInterface;
use Psr\Http\Message\RequestInterface;
/**
* Class UniversalMockException
*
* @category UniversalMockException
* @package Pock\Exception
*/
class UniversalMockException extends Exception implements
ClientExceptionInterface,
NetworkExceptionInterface,
RequestExceptionInterface
{
private $request;
/**
* UniversalMockException constructor.
*
* @param $request
*/
public function __construct($request)
{
parent::__construct('Default mock exception');
$this->request = $request;
}
/**
* @inheritDoc
*/
public function getRequest(): RequestInterface
{
return $this->request;
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* PHP 7.3
*
* @category UnsupportedRequestException
* @package Pock\Exception
*/
namespace Pock\Exception;
use Exception;
/**
* Class UnsupportedRequestException
*
* @category UnsupportedRequestException
* @package Pock\Exception
*/
class UnsupportedRequestException extends Exception
{
public function __construct()
{
parent::__construct('Cannot match request with any available matchers');
}
}

View File

@ -0,0 +1,29 @@
<?php
/**
* PHP 7.3
*
* @category AnyRequestMatcher
* @package Pock\Matchers
*/
namespace Pock\Matchers;
use Psr\Http\Message\RequestInterface;
/**
* Class AnyRequestMatcher
*
* @category AnyRequestMatcher
* @package Pock\Matchers
*/
class AnyRequestMatcher implements RequestMatcherInterface
{
/**
* @inheritDoc
*/
public function matches(RequestInterface $request): bool
{
return true;
}
}

View File

@ -0,0 +1,42 @@
<?php
/**
* PHP 7.3
*
* @category HostMatcher
* @package Pock\Matchers
*/
namespace Pock\Matchers;
use Psr\Http\Message\RequestInterface;
/**
* Class HostMatcher
*
* @category HostMatcher
* @package Pock\Matchers
*/
class HostMatcher implements RequestMatcherInterface
{
/** @var string */
private $host;
/**
* HostMatcher constructor.
*
* @param string $host
*/
public function __construct(string $host)
{
$this->host = $host;
}
/**
* @inheritDoc
*/
public function matches(RequestInterface $request): bool
{
return $request->getUri()->getHost() === $this->host;
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* PHP 7.3
*
* @category MultipleMatcher
* @package Pock\Matchers
*/
namespace Pock\Matchers;
use Countable;
use Psr\Http\Message\RequestInterface;
/**
* Class MultipleMatcher
*
* @category MultipleMatcher
* @package Pock\Matchers
*/
class MultipleMatcher implements RequestMatcherInterface, Countable
{
/** @var \Pock\Matchers\RequestMatcherInterface[] */
public $matchers;
/**
* MultipleMatcher constructor.
*
* @param \Pock\Matchers\RequestMatcherInterface[] $matchers
*/
public function __construct(array $matchers = [])
{
$this->matchers = $matchers;
}
/**
* @param \Pock\Matchers\RequestMatcherInterface $matcher
*
* @return $this
*/
public function addMatcher(RequestMatcherInterface $matcher): self
{
$this->matchers[] = $matcher;
return $this;
}
/**
* @return int
*/
public function count(): int
{
return count($this->matchers);
}
/**
* @param \Psr\Http\Message\RequestInterface $request
*
* @return bool
*/
public function matches(RequestInterface $request): bool
{
foreach ($this->matchers as $matcher) {
if (!$matcher->matches($request)) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* PHP 7.3
*
* @category RequestMatcherInterface
* @package Pock\Matchers
*/
namespace Pock\Matchers;
use Psr\Http\Message\RequestInterface;
/**
* Interface RequestMatcherInterface
*
* @category RequestMatcherInterface
* @package Pock\Matchers
*/
interface RequestMatcherInterface
{
/**
* Returns true if request is matched by this matcher.
*
* @param \Psr\Http\Message\RequestInterface $request
*
* @return bool
*/
public function matches(RequestInterface $request): bool;
}

87
src/Mock.php Normal file
View File

@ -0,0 +1,87 @@
<?php
/**
* PHP 7.3
*
* @category Mock
* @package Pock
*/
namespace Pock;
use Pock\Matchers\RequestMatcherInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
/**
* Class Mock
*
* @category Mock
* @package Pock
*/
class Mock implements MockInterface
{
/** @var \Pock\Matchers\RequestMatcherInterface */
private $matcher;
/** @var \Psr\Http\Message\ResponseInterface|null */
private $response;
/** @var \Throwable|null */
private $throwable;
/** @var bool */
private $fired = false;
/**
* Mock constructor.
*
* @param \Pock\Matchers\RequestMatcherInterface $matcher
* @param \Psr\Http\Message\ResponseInterface|null $response
* @param \Throwable|null $throwable
*/
public function __construct(RequestMatcherInterface $matcher, ?ResponseInterface $response, ?Throwable $throwable)
{
$this->matcher = $matcher;
$this->response = $response;
$this->throwable = $throwable;
}
public function markAsFired(): MockInterface
{
$this->fired = true;
return $this;
}
/**
* @return bool
*/
public function isFired(): bool
{
return $this->fired;
}
/**
* @return \Pock\Matchers\RequestMatcherInterface
*/
public function getMatcher(): RequestMatcherInterface
{
return $this->matcher;
}
/**
* @return \Psr\Http\Message\ResponseInterface|null
*/
public function getResponse(): ?ResponseInterface
{
return $this->response;
}
/**
* @return \Throwable|null
*/
public function getThrowable(): ?Throwable
{
return $this->throwable;
}
}

58
src/MockInterface.php Normal file
View File

@ -0,0 +1,58 @@
<?php
/**
* PHP 7.3
*
* @category MockInterface
* @package Pock
*/
namespace Pock;
use Pock\Matchers\RequestMatcherInterface;
use Psr\Http\Message\ResponseInterface;
use Throwable;
/**
* Interface MockInterface
*
* @category MockInterface
* @package Pock
*/
interface MockInterface
{
/**
* Marks mock as already used.
*
* @return \Pock\MockInterface
*/
public function markAsFired(): MockInterface;
/**
* Returns true if mock was not used yet.
*
* @return bool
*/
public function isFired(): bool;
/**
* Returns matcher for the request.
*
* @return \Pock\Matchers\RequestMatcherInterface
*/
public function getMatcher(): RequestMatcherInterface;
/**
* Returns response which should be used as mock data.
*
* @return \Psr\Http\Message\ResponseInterface|null
*/
public function getResponse(): ?ResponseInterface;
/**
* Returns the throwable which will be thrown as mock data.
*
* @return \Throwable|null
*/
public function getThrowable(): ?Throwable;
}

89
src/PockBuilder.php Normal file
View File

@ -0,0 +1,89 @@
<?php
/**
* PHP 7.3
*
* @category PockBuilder
* @package Pock
*/
namespace Pock;
use Pock\Matchers\AnyRequestMatcher;
use Pock\Matchers\HostMatcher;
use Pock\Matchers\MultipleMatcher;
/**
* Class PockBuilder
*
* @category PockBuilder
* @package Pock
*/
class PockBuilder
{
/** @var \Pock\Matchers\MultipleMatcher */
private $matcher;
/** @var \Psr\Http\Message\ResponseInterface|null */
private $response;
/** @var \Throwable|null */
private $throwable;
/** @var \Pock\MockInterface[] */
private $mocks;
/**
* PockBuilder constructor.
*/
public function __construct()
{
$this->reset();
}
/**
* Matches request by hostname.
*
* @param string $host
*
* @return $this
*/
public function host(string $host): PockBuilder
{
$this->closePrevious();
$this->matcher->addMatcher(new HostMatcher($host));
return $this;
}
/**
* Resets the builder.
*
* @return \Pock\PockBuilder
*/
public function reset(): PockBuilder
{
$this->matcher = new MultipleMatcher();
$this->response = null;
$this->throwable = null;
$this->mocks = [];
}
public function getClient(): Client
{
return new Client($this->mocks);
}
private function closePrevious(): void
{
if (null !== $this->response || null !== $this->throwable) {
if (0 === count($this->matcher)) {
$this->matcher->addMatcher(new AnyRequestMatcher());
}
$this->mocks[] = new Mock($this->matcher, $this->response, $this->throwable);
$this->response = null;
$this->throwable = null;
}
}
}

View File

@ -0,0 +1,72 @@
<?php
/**
* PHP 7.3
*
* @category HttpRejectedPromise
* @package Pock\Promise
*/
namespace Pock\Promise;
use Http\Client\Exception;
use Http\Client\Promise\HttpFulfilledPromise;
use Http\Promise\Promise;
use Throwable;
/**
* Class HttpRejectedPromise
*
* @category HttpRejectedPromise
* @package Pock\Promise
*/
class HttpRejectedPromise implements Promise
{
/** @var \Throwable */
private $throwable;
/**
* HttpRejectedPromise constructor.
*
* @param \Throwable $throwable
*/
public function __construct(Throwable $throwable)
{
$this->throwable = $throwable;
}
/**
* @inheritDoc
*/
public function then(callable $onFulfilled = null, callable $onRejected = null)
{
if (null === $onRejected) {
return $this;
}
try {
return new HttpFulfilledPromise($onRejected($this->throwable));
} catch (Exception $e) {
return new self($e);
}
}
/**
* @inheritDoc
*/
public function getState(): string
{
return Promise::REJECTED;
}
/**
* @inheritDoc
* @throws \Throwable
*/
public function wait($unwrap = true): void
{
if ($unwrap) {
throw $this->throwable;
}
}
}

0
tests/.gitkeep Normal file
View File