From 09176a81a0d6de330d3791ea751964048344be82 Mon Sep 17 00:00:00 2001 From: parsilver Date: Tue, 29 Aug 2023 17:51:46 +0700 Subject: [PATCH] Add request --- composer.json | 3 +- src/Contracts/ResponseInterface.php | 2 +- src/Exceptions/ResponseExceptionFactory.php | 2 +- src/Request.php | 22 +++ src/Response.php | 2 +- src/Traits/PsrRequestTrait.php | 113 ++++++++++++++ tests/RequestTest.php | 151 ++++++++++++++++++ tests/ResponseTest.php | 165 ++++++++------------ tests/TransportTest.php | 47 ++++++ 9 files changed, 405 insertions(+), 102 deletions(-) create mode 100644 src/Request.php create mode 100644 src/Traits/PsrRequestTrait.php create mode 100644 tests/RequestTest.php create mode 100644 tests/TransportTest.php diff --git a/composer.json b/composer.json index 8262401..098ee99 100644 --- a/composer.json +++ b/composer.json @@ -18,11 +18,10 @@ "guzzlehttp/guzzle": "^7.7", "guzzlehttp/psr7": "^2.5", "psr/http-client": "^1.0", - "psr/http-message": "^1.0|^2.0" + "psr/http-message": "^2.0" }, "require-dev": { "laravel/pint": "^1.2", - "mockery/mockery": "^1.5", "pestphp/pest": "^1.20", "spatie/ray": "^1.28" }, diff --git a/src/Contracts/ResponseInterface.php b/src/Contracts/ResponseInterface.php index b2287b9..c1434af 100644 --- a/src/Contracts/ResponseInterface.php +++ b/src/Contracts/ResponseInterface.php @@ -30,7 +30,7 @@ public function isSuccessfull(): bool; /** * Return the json decoded response. */ - public function json(?string $key = null): mixed; + public function json(string $key = null): mixed; /** * Throw an exception if the response is not successfull. diff --git a/src/Exceptions/ResponseExceptionFactory.php b/src/Exceptions/ResponseExceptionFactory.php index c6c6220..af7d125 100644 --- a/src/Exceptions/ResponseExceptionFactory.php +++ b/src/Exceptions/ResponseExceptionFactory.php @@ -13,7 +13,7 @@ class ResponseExceptionFactory /** * Create a new exception instance. */ - public static function create(ResponseInterface $response, ?Throwable $previous = null) + public static function create(ResponseInterface $response, Throwable $previous = null) { $statusCode = $response->statusCode(); diff --git a/src/Request.php b/src/Request.php new file mode 100644 index 0000000..afdf2c7 --- /dev/null +++ b/src/Request.php @@ -0,0 +1,22 @@ +request = new GuzzleRequest($method, $uri, $headers, $body, $version); + } +} diff --git a/src/Response.php b/src/Response.php index 43b0cb9..2a611fc 100644 --- a/src/Response.php +++ b/src/Response.php @@ -73,7 +73,7 @@ public function isSuccessfull(): bool /** * Return the json decoded response. */ - public function json(?string $key = null): mixed + public function json(string $key = null): mixed { if (is_null($this->jsonDecoded)) { $this->jsonDecoded = @json_decode($this->body(), true) ?: false; diff --git a/src/Traits/PsrRequestTrait.php b/src/Traits/PsrRequestTrait.php new file mode 100644 index 0000000..4840044 --- /dev/null +++ b/src/Traits/PsrRequestTrait.php @@ -0,0 +1,113 @@ +request->getRequestTarget(); + } + + public function withRequestTarget($requestTarget): PsrRequestInterface + { + $this->request = $this->request->withRequestTarget($requestTarget); + + return $this; + } + + public function getMethod(): string + { + return $this->request->getMethod(); + } + + public function withMethod($method): PsrRequestInterface + { + $this->request = $this->request->withMethod($method); + + return $this; + } + + public function getUri(): UriInterface + { + return $this->request->getUri(); + } + + public function withUri($uri, $preserveHost = false): PsrRequestInterface + { + $this->request = $this->request->withUri($uri, $preserveHost); + + return $this; + } + + public function getProtocolVersion(): string + { + return $this->request->getProtocolVersion(); + } + + public function withProtocolVersion($version): PsrRequestInterface + { + $this->request = $this->request->withProtocolVersion($version); + + return $this; + } + + public function getHeaders(): array + { + return $this->request->getHeaders(); + } + + public function hasHeader($name): bool + { + return $this->request->hasHeader($name); + } + + public function getHeader($name): array + { + return $this->request->getHeader($name); + } + + public function getHeaderLine($name): string + { + return $this->request->getHeaderLine($name); + } + + public function withHeader($name, $value): PsrRequestInterface + { + $this->request = $this->request->withHeader($name, $value); + + return $this; + } + + public function withAddedHeader($name, $value): PsrRequestInterface + { + $this->request = $this->request->withAddedHeader($name, $value); + + return $this; + } + + public function withoutHeader($name): PsrRequestInterface + { + $this->request = $this->request->withoutHeader($name); + + return $this; + } + + public function getBody(): StreamInterface + { + return $this->request->getBody(); + } + + public function withBody(StreamInterface $body): PsrRequestInterface + { + $this->request = $this->request->withBody($body); + + return $this; + } +} diff --git a/tests/RequestTest.php b/tests/RequestTest.php new file mode 100644 index 0000000..5ae3100 --- /dev/null +++ b/tests/RequestTest.php @@ -0,0 +1,151 @@ +getMethod())->toBe('GET'); +}); + +it('can set the request method', function () { + $request = new Request('GET', new Uri('https://example.com')); + + $newRequest = $request->withMethod('POST'); + + expect($newRequest->getMethod())->toBe('POST'); +}); + +it('can get request target', function () { + $request = new Request('GET', new Uri('https://example.com/foo')); + + expect($request->getRequestTarget())->toBe('/foo'); +}); + +it('can set request target', function () { + $request = new Request('GET', new Uri('https://example.com')); + + $newRequest = $request->withRequestTarget('/foo'); + + expect($newRequest->getRequestTarget())->toBe('/foo'); +}); + +it('can get the request URI', function () { + $uri = new Uri('https://example.com'); + $request = new Request('GET', $uri); + + expect($request->getUri())->toBe($uri); +}); + +it('can set the request URI', function () { + $uri = new Uri('https://example.com'); + $request = new Request('GET', $uri); + + $newUri = new Uri('https://example.org'); + $newRequest = $request->withUri($newUri); + + expect($newRequest->getUri())->toBe($newUri); +}); + +it('can get the request protocol version', function () { + $request = new Request('GET', new Uri('https://example.com')); + + expect($request->getProtocolVersion())->toBe('1.1'); +}); + +it('can set the request protocol version', function () { + $request = new Request('GET', new Uri('https://example.com')); + + $newRequest = $request->withProtocolVersion('2.0'); + + expect($newRequest->getProtocolVersion())->toBe('2.0'); +}); + +it('can get the request headers', function () { + $request = new Request('GET', new Uri('https://example.com'), [ + 'Content-Type' => 'application/json', + ]); + + expect($request->getHeaders())->toBe([ + 'Host' => ['example.com'], + 'Content-Type' => ['application/json'], + ]); +}); + +it('can set a request header', function () { + $request = new Request('GET', new Uri('https://example.com')); + + $newRequest = $request->withHeader('Content-Type', 'application/json'); + + expect($newRequest->getHeaders())->toBe([ + 'Host' => ['example.com'], + 'Content-Type' => ['application/json'], + ]); +}); + +it('can add a request header', function () { + $request = new Request('GET', new Uri('https://example.com'), [ + 'Content-Type' => 'application/json', + ]); + + $newRequest = $request->withAddedHeader('Accept', 'application/json'); + + expect($newRequest->getHeaders())->toBe([ + 'Host' => ['example.com'], + 'Content-Type' => ['application/json'], + 'Accept' => ['application/json'], + ]); +}); + +it('can check exists header', function () { + $request = new Request('GET', new Uri('https://example.com'), [ + 'Content-Type' => 'application/json', + ]); + + expect($request->hasHeader('Content-Type'))->toBeTrue(); + expect($request->hasHeader('Accept'))->toBeFalse(); +}); + +it('can get header', function () { + $request = new Request('GET', new Uri('https://example.com'), [ + 'Content-Type' => 'application/json', + ]); + + expect($request->getHeader('Content-Type'))->toBe(['application/json']); + expect($request->getHeader('Accept'))->toBe([]); +}); + +it('can remove a request header', function () { + $request = new Request('GET', new Uri('https://example.com'), [ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + ]); + + $newRequest = $request->withoutHeader('Accept'); + + expect($newRequest->getHeaders())->toBe([ + 'Host' => ['example.com'], + 'Content-Type' => ['application/json'], + ]); +}); + +it('can get a request header line', function () { + $request = new Request('GET', new Uri('https://example.com'), [ + 'Content-Type' => 'application/json', + ]); + + expect($request->getHeaderLine('Content-Type'))->toBe('application/json'); +}); + +it('can set the request body', function () { + $request = new Request('GET', new Uri('https://example.com')); + + $stream = $this->createMock(StreamInterface::class); + $stream->method('__toString')->willReturn('{"foo":"bar"}'); + + $newRequest = $request->withBody($stream); + + expect((string) $newRequest->getBody())->toBe('{"foo":"bar"}'); +}); diff --git a/tests/ResponseTest.php b/tests/ResponseTest.php index 6dc3272..1d476b8 100644 --- a/tests/ResponseTest.php +++ b/tests/ResponseTest.php @@ -1,166 +1,137 @@ createMock(RequestInterface::class); + + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getStatusCode')->willReturn(200); + $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getStatusCode') - ->once() - ->andReturn(200) - ->getMock() + $baseRequest, + $baseResponse, ); - $this->assertEquals(200, $response->statusCode()); + expect($response->statusCode())->toBe(200); + expect($response->isSuccessfull())->toBeTrue(); }); it('can get the response body', function () { - $stream = Mock::mock(StreamInterface::class) - ->shouldReceive('getContents') - ->once() - ->andReturn('{"foo":"bar"}') - ->getMock(); + $stream = $this->createMock(StreamInterface::class); + $stream->method('getContents')->willReturn('{"foo":"bar"}'); + + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getBody')->willReturn($stream); $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getBody') - ->once() - ->andReturn($stream) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, ); - $this->assertEquals('{"foo":"bar"}', $response->body()); + expect($response->body())->toBe('{"foo":"bar"}'); }); it('can get the response headers', function () { - $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getHeaders') - ->once() - ->andReturn(['Content-Type' => ['application/json']]) - ->getMock() - ); - - $this->assertEquals(['Content-Type' => ['application/json']], $response->headers()); -}); + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getHeaders')->willReturn(['Content-Type' => ['application/json']]); -it('can check if the response is successfull', function () { $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getStatusCode') - ->andReturn(200) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, ); - $this->assertTrue($response->isSuccessfull()); + expect($response->headers())->toBe(['Content-Type' => ['application/json']]); }); it('can check if the response is not successfull', function () { + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getStatusCode')->willReturn(400); + $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getStatusCode') - ->andReturn(400) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, ); - $this->assertFalse($response->isSuccessfull()); + expect($response->isSuccessfull())->toBeFalse(); + expect($response->statusCode())->toBe(400); }); it('can get json body as array', function () { - $stream = Mock::mock(StreamInterface::class) - ->shouldReceive('getContents') - ->once() - ->andReturn('{"foo":"bar"}') - ->getMock(); + $stream = $this->createMock(StreamInterface::class); + $stream->method('getContents')->willReturn('{"foo":"bar"}'); + + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getBody')->willReturn($stream); $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getBody') - ->once() - ->andReturn($stream) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, ); - $this->assertEquals(['foo' => 'bar'], $response->json()); + expect($response->json())->toBe(['foo' => 'bar']); }); it('cannot get json when invalid json format', function () { - $stream = Mock::mock(StreamInterface::class) - ->shouldReceive('getContents') - ->once() - ->andReturn('{"foo":"bar"') - ->getMock(); + $stream = $this->createMock(StreamInterface::class); + $stream->method('getContents')->willReturn('{"foo":"bar"'); + + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getBody')->willReturn($stream); $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getBody') - ->once() - ->andReturn($stream) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, ); expect($response->json())->toBeNull(); }); it('can specify key name to get json body', function () { - $stream = Mock::mock(StreamInterface::class) - ->shouldReceive('getContents') - ->once() - ->andReturn('{"foo":"bar"}') - ->getMock(); + $stream = $this->createMock(StreamInterface::class); + $stream->method('getContents')->willReturn('{"foo":"bar"}'); + + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getBody')->willReturn($stream); $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getBody') - ->once() - ->andReturn($stream) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, ); - $this->assertEquals('bar', $response->json('foo')); + expect($response->json('foo'))->toBe('bar'); }); it('can throw error if response status is not success', function () { - $stream = Mock::mock(StreamInterface::class) - ->shouldReceive('getContents') - ->once() - ->andReturn('{"error": "invalid_request"}') - ->getMock(); + $stream = $this->createMock(StreamInterface::class); + $stream->method('getContents')->willReturn('{"error": "invalid_request"}'); + + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getStatusCode')->willReturn(400); + $baseResponse->method('getBody')->willReturn($stream); $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getStatusCode') - ->andReturn(400) - ->shouldReceive('getBody') - ->once() - ->andReturn($stream) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, + ); $response->throw(); })->throws(\GuzzleHttp\Exception\BadResponseException::class); it('should not throw error if response status is success', function () { + $baseResponse = $this->createMock(ResponseInterface::class); + $baseResponse->method('getStatusCode')->willReturn(200); + $response = new Response( - Mock::mock(RequestInterface::class), - Mock::mock(ResponseInterface::class) - ->shouldReceive('getStatusCode') - ->andReturn(200) - ->getMock() + $this->createMock(RequestInterface::class), + $baseResponse, ); $response->throw(); + + expect($response->isSuccessfull())->toBeTrue(); }); diff --git a/tests/TransportTest.php b/tests/TransportTest.php new file mode 100644 index 0000000..dabc149 --- /dev/null +++ b/tests/TransportTest.php @@ -0,0 +1,47 @@ +createMock(ClientInterface::class); + $client->expects($this->once()) + ->method('sendRequest') + ->willReturn(new Response(200, [], 'Hello World')); + + $transport = new Transport($client); + + $request = new Request('GET', new Uri('https://example.com')); + + $response = $transport->sendRequest($request); + + expect($response)->toBeInstanceOf(Response::class); + + expect($response->getStatusCode())->toBe(200); + expect($response->getBody()->getContents())->toBe('Hello World'); +}); + +it('can get the base URI', function () { + $client = new Client(); + + $transport = new Transport($client); + + expect($transport->getUri())->toBe('/'); +}); + +it('can build transport with builder', function () { + $transport = TransportBuilder::make() + ->setClient(new Client()) + ->setLogger(new NullLogger()) + ->build(); + + expect($transport)->toBeInstanceOf(Transport::class); + + expect($transport->getUri())->toBe('/'); +});