From 2a6cda0cf8e3714d95b13f81379a2bd39d20a0fc Mon Sep 17 00:00:00 2001 From: Fabrizio Gargiulo Date: Fri, 9 Jun 2023 18:58:28 +0200 Subject: [PATCH] ISSUE-31: Add Middlewares to HandlerStack (#37) * ISSUE-31: Add Middlewares to HandlerStack * ClientBuilder moved inside Guzzle namespace * FIX --------- Co-authored-by: Fabrizio Gargiulo --- README.md | 7 +- src/MockedClient/Guzzle/ClientBuilder.php | 42 ++++++++++++ .../Guzzle/Middleware/Middleware.php | 34 ++++++++++ src/MockedClient/HandlerBuilder.php | 7 +- .../MockedGuzzleClientBuilder.php | 64 ------------------- .../ClientBuilderTest.php} | 39 +++++++++-- .../fixtures/api_parts_manufacturers.json | 0 tests/{ => Guzzle}/fixtures/countries.json | 0 tests/{ => Guzzle}/fixtures/country.json | 0 tests/Route/ConditionalRouteBuilderTest.php | 4 +- .../fixtures/api_parts_manufacturers.json | 18 ++++++ tests/Route/fixtures/countries.json | 12 ++++ 12 files changed, 151 insertions(+), 76 deletions(-) create mode 100644 src/MockedClient/Guzzle/ClientBuilder.php create mode 100644 src/MockedClient/Guzzle/Middleware/Middleware.php delete mode 100644 src/MockedClient/MockedGuzzleClientBuilder.php rename tests/{HandlerStackBuilderTest.php => Guzzle/ClientBuilderTest.php} (85%) rename tests/{ => Guzzle}/fixtures/api_parts_manufacturers.json (100%) rename tests/{ => Guzzle}/fixtures/countries.json (100%) rename tests/{ => Guzzle}/fixtures/country.json (100%) create mode 100644 tests/Route/fixtures/api_parts_manufacturers.json create mode 100644 tests/Route/fixtures/countries.json diff --git a/README.md b/README.md index 0c55b52..81d3566 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,10 @@ $ composer require doppiogancio/mocked-client guzzlehttp/guzzle php-http/discove This version requires a minimum PHP version 8.1 ## How to mock a client + ```php use DoppioGancio\MockedClient\HandlerBuilder; -use DoppioGancio\MockedClient\MockedGuzzleClientBuilder; +use DoppioGancio\MockedClient\ClientBuilder; use DoppioGancio\MockedClient\Route\RouteBuilder; use GuzzleHttp\Psr7\Response; use Http\Discovery\Psr17FactoryDiscovery; @@ -70,7 +71,7 @@ $handlerBuilder->addRoute( ->build() ); -$clientBuilder = new MockedGuzzleClientBuilder($handlerBuilder, new NullLogger()); +$clientBuilder = new ClientBuilder($handlerBuilder, new NullLogger()); $client = $clientBuilder->build(); ``` @@ -102,7 +103,7 @@ $handlerBuilder = new HandlerBuilder( // don't add any route for now... -$clientBuilder = new MockedGuzzleClientBuilder($handlerBuilder); +$clientBuilder = new ClientBuilder($handlerBuilder); $client = $clientBuilder->build(); ``` diff --git a/src/MockedClient/Guzzle/ClientBuilder.php b/src/MockedClient/Guzzle/ClientBuilder.php new file mode 100644 index 0000000..2837099 --- /dev/null +++ b/src/MockedClient/Guzzle/ClientBuilder.php @@ -0,0 +1,42 @@ + $middlewares */ + public function __construct( + private readonly HandlerBuilder $handlerBuilder, + private array $middlewares = [], + ) { + } + + public function addMiddleware(callable $middleware): self + { + $this->middlewares[] = $middleware; + + return $this; + } + + /** @param array $options */ + public function build(array $options = []): Client + { + $handler = $this->handlerBuilder->build(); + + $handlerStack = HandlerStack::create($handler); + + foreach ($this->middlewares as $middleware) { + $handlerStack->push($middleware); + } + + $options['handler'] = $handlerStack; + + return new Client($options); + } +} diff --git a/src/MockedClient/Guzzle/Middleware/Middleware.php b/src/MockedClient/Guzzle/Middleware/Middleware.php new file mode 100644 index 0000000..4f42bae --- /dev/null +++ b/src/MockedClient/Guzzle/Middleware/Middleware.php @@ -0,0 +1,34 @@ +request = $this->mapRequest($request); + + $response = $handler($this->request, $options); + + return $this->mapResponse($response); + }; + } + + protected function mapRequest(RequestInterface $request): RequestInterface + { + return $request; + } + + protected function mapResponse(PromiseInterface $response): PromiseInterface + { + return $response; + } +} diff --git a/src/MockedClient/HandlerBuilder.php b/src/MockedClient/HandlerBuilder.php index 042c7ea..8246012 100644 --- a/src/MockedClient/HandlerBuilder.php +++ b/src/MockedClient/HandlerBuilder.php @@ -7,10 +7,11 @@ use Closure; use DoppioGancio\MockedClient\Exception\RouteNotFound; use DoppioGancio\MockedClient\Route\Route; +use GuzzleHttp\Promise\FulfilledPromise; +use GuzzleHttp\Promise\PromiseInterface; use League\Route\Http\Exception\NotFoundException; use League\Route\Router; use Psr\Http\Message\RequestInterface; -use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestFactoryInterface; use Psr\Log\LoggerInterface; use Throwable; @@ -38,7 +39,7 @@ public function addRoute(Route $route): self public function build(): Closure { - return function (RequestInterface $request): ResponseInterface { + return function (RequestInterface $request): PromiseInterface { $router = new Router(); foreach ($this->routes as $route) { $router->map( @@ -79,7 +80,7 @@ public function build(): Closure ], ); - return $response; + return new FulfilledPromise($response); } catch (NotFoundException $e) { $this->logError($e, $request); diff --git a/src/MockedClient/MockedGuzzleClientBuilder.php b/src/MockedClient/MockedGuzzleClientBuilder.php deleted file mode 100644 index c0027f5..0000000 --- a/src/MockedClient/MockedGuzzleClientBuilder.php +++ /dev/null @@ -1,64 +0,0 @@ -handlerBuilder->build(); - - $callback = static function (RequestInterface $request) use ($handler): PromiseInterface { - $response = $handler($request); - assert($response instanceof Response); - if ($response->getStatusCode() >= 400 && $response->getStatusCode() < 500) { - throw new ClientException( - $response->getBody()->getContents(), - $request, - $response, - ); - } - - if ($response->getStatusCode() >= 500 && $response->getStatusCode() < 600) { - throw new ServerException( - $response->getBody()->getContents(), - $request, - $response, - ); - } - - return new FulfilledPromise($response); - }; - - $handlerStack = new HandlerStack($callback); - - $handlerStack->push(Middleware::log( - $this->logger, - new MessageFormatter(), - )); - - return new Client(['handler' => $handlerStack]); - } -} diff --git a/tests/HandlerStackBuilderTest.php b/tests/Guzzle/ClientBuilderTest.php similarity index 85% rename from tests/HandlerStackBuilderTest.php rename to tests/Guzzle/ClientBuilderTest.php index 98432e8..2e274f9 100644 --- a/tests/HandlerStackBuilderTest.php +++ b/tests/Guzzle/ClientBuilderTest.php @@ -2,10 +2,11 @@ declare(strict_types=1); -namespace DoppioGancio\MockedClient\Tests; +namespace DoppioGancio\MockedClient\Tests\Guzzle; +use DoppioGancio\MockedClient\Guzzle\ClientBuilder; +use DoppioGancio\MockedClient\Guzzle\Middleware\Middleware; use DoppioGancio\MockedClient\HandlerBuilder; -use DoppioGancio\MockedClient\MockedGuzzleClientBuilder; use DoppioGancio\MockedClient\Route\ConditionalRouteBuilder; use DoppioGancio\MockedClient\Route\RouteBuilder; use GuzzleHttp\Client; @@ -16,11 +17,12 @@ use GuzzleHttp\Psr7\Response; use Http\Discovery\Psr17FactoryDiscovery; use PHPUnit\Framework\TestCase; +use Psr\Http\Message\RequestInterface; use Psr\Log\NullLogger; use function json_decode; -class HandlerStackBuilderTest extends TestCase +class ClientBuilderTest extends TestCase { private HandlerBuilder $handlerBuilder; private RouteBuilder $routeBuilder; @@ -167,6 +169,13 @@ public function testRequestWithFormBody(): void $this->assertEquals('key1=value1&key2=value2', $body); } + public function testMiddlewares(): void + { + $response = $this->getMockedClient()->request('GET', '/middleware'); + $body = (string) $response->getBody(); + $this->assertEquals('x-value', $body); + } + private function getMockedClient(): Client { $this->handlerBuilder->addRoute( @@ -240,7 +249,29 @@ private function getMockedClient(): Client ->build(), ); - $clientBuilder = new MockedGuzzleClientBuilder($this->handlerBuilder, new NullLogger()); + $this->handlerBuilder->addRoute( + $this->routeBuilder->new() + ->withMethod('GET') + ->withPath('/middleware') + ->withHandler(static function (Request $request): Response { + return new Response(200, [], $request->getHeader('x-name')[0]); + }) + ->build(), + ); + + $clientBuilder = new ClientBuilder($this->handlerBuilder); + + // Anonymous middleware + $clientBuilder->addMiddleware(new class ('x-name', 'x-value') extends Middleware { + public function __construct(private readonly string $header, private readonly string $value) + { + } + + protected function mapRequest(RequestInterface $request): RequestInterface + { + return $request->withHeader($this->header, $this->value); + } + }); return $clientBuilder->build(); } diff --git a/tests/fixtures/api_parts_manufacturers.json b/tests/Guzzle/fixtures/api_parts_manufacturers.json similarity index 100% rename from tests/fixtures/api_parts_manufacturers.json rename to tests/Guzzle/fixtures/api_parts_manufacturers.json diff --git a/tests/fixtures/countries.json b/tests/Guzzle/fixtures/countries.json similarity index 100% rename from tests/fixtures/countries.json rename to tests/Guzzle/fixtures/countries.json diff --git a/tests/fixtures/country.json b/tests/Guzzle/fixtures/country.json similarity index 100% rename from tests/fixtures/country.json rename to tests/Guzzle/fixtures/country.json diff --git a/tests/Route/ConditionalRouteBuilderTest.php b/tests/Route/ConditionalRouteBuilderTest.php index a61ed32..10d50e4 100644 --- a/tests/Route/ConditionalRouteBuilderTest.php +++ b/tests/Route/ConditionalRouteBuilderTest.php @@ -46,7 +46,7 @@ public function testDefaultRouteNotFound(): void ->withConditionalResponse('page=2&code=it', new Response(201)) ->withConditionalResponse('code=de', new Response(301)) ->withConditionalResponse('page=4', new Response(401)) - ->withDefaultFileResponse(__DIR__ . '/../fixtures/countries.json') + ->withDefaultFileResponse(__DIR__ . '/fixtures/countries.json') ->build(); $response = $route->getHandler()(new Request('GET', '/country?nonce=12345&code=fr&page=3')); @@ -72,7 +72,7 @@ public function testQueryStringWithArrayNotation(): void ) ->withConditionalFileResponse( 'filters%5BhasContent%5D=1', - __DIR__ . '/../fixtures/api_parts_manufacturers.json', + __DIR__ . '/fixtures/api_parts_manufacturers.json', ) ->build(); diff --git a/tests/Route/fixtures/api_parts_manufacturers.json b/tests/Route/fixtures/api_parts_manufacturers.json new file mode 100644 index 0000000..c4fdd82 --- /dev/null +++ b/tests/Route/fixtures/api_parts_manufacturers.json @@ -0,0 +1,18 @@ +{ + "manufacturers": [ + { + "value": "a-1111", + "label": "Manufacturer #1, Inc.", + "count": 12, + "categoryCount": 4, + "partsWithOffersCount": 1111 + }, + { + "value": "b-222", + "label": "Manufacturer #2, Inc.", + "count": 22, + "categoryCount": 5, + "partsWithOffersCount": 2222 + } + ] +} diff --git a/tests/Route/fixtures/countries.json b/tests/Route/fixtures/countries.json new file mode 100644 index 0000000..d9be9c0 --- /dev/null +++ b/tests/Route/fixtures/countries.json @@ -0,0 +1,12 @@ +[ + { + "id": "+39", + "code": "IT", + "name": "Italy" + }, + { + "id": "+49", + "code": "DE", + "name": "Germany" + } +] \ No newline at end of file