From bf65ce058dc75ba0259fa11afa9b49b5e161d35d Mon Sep 17 00:00:00 2001 From: Ondrej Vlach Date: Thu, 18 Jan 2024 00:47:17 +0100 Subject: [PATCH] feat: add UsetrenoHttpClient !fixup bb244e40f5fa42294acc7cc7f925796325ada4c5 --- src/Entity/Remote/Usetreno/AuthRequest.php | 8 + .../Remote/Exception/AuthorizeException.php | 8 + src/Service/Remote/UsetrenoHttpClient.php | 145 ++++++++++++++++++ tests/Common/LoggerTrait.php | 18 +++ .../Service/Remote/UsetrenoHttpClientTest.php | 54 +++++++ 5 files changed, 233 insertions(+) create mode 100644 src/Entity/Remote/Usetreno/AuthRequest.php create mode 100644 src/Service/Remote/Exception/AuthorizeException.php create mode 100644 src/Service/Remote/UsetrenoHttpClient.php create mode 100644 tests/Common/LoggerTrait.php create mode 100644 tests/Service/Remote/UsetrenoHttpClientTest.php diff --git a/src/Entity/Remote/Usetreno/AuthRequest.php b/src/Entity/Remote/Usetreno/AuthRequest.php new file mode 100644 index 0000000..2d475d5 --- /dev/null +++ b/src/Entity/Remote/Usetreno/AuthRequest.php @@ -0,0 +1,8 @@ + $options + * @return ResponseInterface + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function request(string $method, string $url, array $options = []): ResponseInterface { + if ($this->authorizationToken === null) { + $this->authorize(); + } + + return $this->innerClient->request($method, $url, $options); + } + + /** + * @param ResponseInterface|iterable $responses One or more responses created by the current HTTP client + * @param float|null $timeout + * @return ResponseStreamInterface + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function stream(iterable|ResponseInterface $responses, float $timeout = null): ResponseStreamInterface + { + if ($this->authorizationToken === null) { + $this->authorize(); + } + + return $this->innerClient->stream($responses, $timeout); + } + + /** + * @param array $options + * @return static(UsetrenoHttpClient) + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function withOptions(array $options): static + { + if ($this->authorizationToken === null) { + $this->authorize(); + } + + $this->innerClient = $this->innerClient->withOptions($options); + return $this; + } + + /** + * @throws TransportExceptionInterface + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + */ + protected function authorize(): void { + $this->logger->debug("trying authorize request", [ + "AUTHORIZE_API" => static::AUTHORIZE_API + ]); + + $rq = $this->innerClient->request( + "POST", + static::AUTHORIZE_API, + [ + 'json' => new AuthRequest($this->username, $this->password), + ] + ); + + $statusCode = $rq->getStatusCode(); + $responseData = $rq->getContent(false); + + if ($statusCode != 200) { + $this->logger->error("authorization request status code is not ok", [ + "AUTHORIZE_API" => static::AUTHORIZE_API, + "statusCode" => $statusCode, + "content" => $responseData, + "username" => $this->username + ]); + + throw new AuthorizeException("Return code is not 200 OK (got: code: $statusCode)"); + } + + $this->authorizationToken = $this->processAuthorizeResponse($responseData); + $this->innerClient = $this->innerClient->withOptions([ + 'headers' => [ + 'Authorization' => "Bearer " . $this->authorizationToken + ] + ]); + } + + /** + * @param string $responseData + * @return string Bearer token + * @throws AuthorizeException + */ + protected function processAuthorizeResponse(string $responseData): string { + $data = json_decode($responseData); + + if (!is_object($data)) { + $this->logger->error("authorize: received null response data", [ + "responseData" => $responseData + ]); + throw new AuthorizeException("Can't decode json response"); + } + + if (!isset($data->data->token)) { + $this->logger->error("authorize: empty token in response data", [ + "responseData" => $responseData + ]); + throw new AuthorizeException("Got invalid json response"); + } + + return $data->data->token; + } +} diff --git a/tests/Common/LoggerTrait.php b/tests/Common/LoggerTrait.php new file mode 100644 index 0000000..b625754 --- /dev/null +++ b/tests/Common/LoggerTrait.php @@ -0,0 +1,18 @@ +pushHandler(new TestHandler()); + return $logger; + } +} diff --git a/tests/Service/Remote/UsetrenoHttpClientTest.php b/tests/Service/Remote/UsetrenoHttpClientTest.php new file mode 100644 index 0000000..92d7ef8 --- /dev/null +++ b/tests/Service/Remote/UsetrenoHttpClientTest.php @@ -0,0 +1,54 @@ + [ + "expires_in" => 1000, + "token" => "foobarfoobar" + ], + "timestamp" => "2024-01-17T22:47:59+01:00", + "uri" => "/api/v1/token" + ]); + $authorizedRequestResponse = clone $authMockResponse; + $mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]); + $client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar"); + $client->request("POST", "https://www.root.cz/"); + $this->assertEquals("https://topapi.top-test.cz/chameleon/api/v1/token", $authMockResponse->getRequestUrl()); + $headers = $authorizedRequestResponse->getRequestOptions()['headers']; + $this->assertEquals("https://www.root.cz/", $authorizedRequestResponse->getRequestUrl()); + $this->assertTrue(in_array("Authorization: Bearer foobarfoobar", $headers), "missing bearer authorization header"); + } + + public function testRequestFailedAuthorization() { + $this->expectException(AuthorizeException::class); + $authMockResponse = new JsonMockResponse([ + "errors" => [ + [ + "message" => "Bad credentials, please verify that your username/password are correctly set.", + "specificType" => "bad_credentials", + "type" => "security_error" + ] + ], + "timestamp" => "2024-01-17T23:55:25+01:00", + "uri" => "/api/v1/token" + ]); + + $authorizedRequestResponse = clone $authMockResponse; + $mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]); + $client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar"); + $client->request("POST", "https://www.root.cz/"); + } +}