From 2429eab7f87575df469e90f3efb42f1968d15b47 Mon Sep 17 00:00:00 2001 From: Neur0toxine Date: Thu, 13 May 2021 21:08:55 +0300 Subject: [PATCH] first code --- .gitignore | 3 + README.md | 5 + composer.json | 28 ++++++ src/Client.php | 91 +++++++++++++++++++ src/Exception/BrokenMockException.php | 45 +++++++++ src/Exception/UniversalMockException.php | 50 ++++++++++ src/Exception/UnsupportedRequestException.php | 26 ++++++ src/Matchers/AnyRequestMatcher.php | 29 ++++++ src/Matchers/HostMatcher.php | 42 +++++++++ src/Matchers/MultipleMatcher.php | 70 ++++++++++++++ src/Matchers/RequestMatcherInterface.php | 30 ++++++ src/Mock.php | 87 ++++++++++++++++++ src/MockInterface.php | 58 ++++++++++++ src/PockBuilder.php | 89 ++++++++++++++++++ src/Promise/HttpRejectedPromise.php | 72 +++++++++++++++ tests/.gitkeep | 0 16 files changed, 725 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 composer.json create mode 100644 src/Client.php create mode 100644 src/Exception/BrokenMockException.php create mode 100644 src/Exception/UniversalMockException.php create mode 100644 src/Exception/UnsupportedRequestException.php create mode 100644 src/Matchers/AnyRequestMatcher.php create mode 100644 src/Matchers/HostMatcher.php create mode 100644 src/Matchers/MultipleMatcher.php create mode 100644 src/Matchers/RequestMatcherInterface.php create mode 100644 src/Mock.php create mode 100644 src/MockInterface.php create mode 100644 src/PockBuilder.php create mode 100644 src/Promise/HttpRejectedPromise.php create mode 100644 tests/.gitkeep diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a78056 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +composer.lock +vendor/* +.idea/* diff --git a/README.md b/README.md new file mode 100644 index 0000000..b72b48b --- /dev/null +++ b/README.md @@ -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! diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c62ca0d --- /dev/null +++ b/composer.json @@ -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" + } +} diff --git a/src/Client.php b/src/Client.php new file mode 100644 index 0000000..e05fa1b --- /dev/null +++ b/src/Client.php @@ -0,0 +1,91 @@ +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(); + } +} diff --git a/src/Exception/BrokenMockException.php b/src/Exception/BrokenMockException.php new file mode 100644 index 0000000..1949652 --- /dev/null +++ b/src/Exception/BrokenMockException.php @@ -0,0 +1,45 @@ +mock = $mock; + } + + /** + * @return \Pock\MockInterface + */ + public function getMock(): MockInterface + { + return $this->mock; + } +} diff --git a/src/Exception/UniversalMockException.php b/src/Exception/UniversalMockException.php new file mode 100644 index 0000000..a035305 --- /dev/null +++ b/src/Exception/UniversalMockException.php @@ -0,0 +1,50 @@ +request = $request; + } + + /** + * @inheritDoc + */ + public function getRequest(): RequestInterface + { + return $this->request; + } +} diff --git a/src/Exception/UnsupportedRequestException.php b/src/Exception/UnsupportedRequestException.php new file mode 100644 index 0000000..4ba031d --- /dev/null +++ b/src/Exception/UnsupportedRequestException.php @@ -0,0 +1,26 @@ +host = $host; + } + + /** + * @inheritDoc + */ + public function matches(RequestInterface $request): bool + { + return $request->getUri()->getHost() === $this->host; + } +} diff --git a/src/Matchers/MultipleMatcher.php b/src/Matchers/MultipleMatcher.php new file mode 100644 index 0000000..04a1bca --- /dev/null +++ b/src/Matchers/MultipleMatcher.php @@ -0,0 +1,70 @@ +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; + } +} diff --git a/src/Matchers/RequestMatcherInterface.php b/src/Matchers/RequestMatcherInterface.php new file mode 100644 index 0000000..4e7b1af --- /dev/null +++ b/src/Matchers/RequestMatcherInterface.php @@ -0,0 +1,30 @@ +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; + } +} diff --git a/src/MockInterface.php b/src/MockInterface.php new file mode 100644 index 0000000..77d75d9 --- /dev/null +++ b/src/MockInterface.php @@ -0,0 +1,58 @@ +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; + } + } +} diff --git a/src/Promise/HttpRejectedPromise.php b/src/Promise/HttpRejectedPromise.php new file mode 100644 index 0000000..c0b6b16 --- /dev/null +++ b/src/Promise/HttpRejectedPromise.php @@ -0,0 +1,72 @@ +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; + } + } +} diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..e69de29