feat: add UsetrenoHttpClient
!fixup bb244e40f5fa42294acc7cc7f925796325ada4c5
This commit is contained in:
parent
f541d55e05
commit
bf65ce058d
8
src/Entity/Remote/Usetreno/AuthRequest.php
Normal file
8
src/Entity/Remote/Usetreno/AuthRequest.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Entity\Remote\Usetreno;
|
||||||
|
|
||||||
|
readonly class AuthRequest
|
||||||
|
{
|
||||||
|
public function __construct(public string $username, public string $password) {}
|
||||||
|
}
|
8
src/Service/Remote/Exception/AuthorizeException.php
Normal file
8
src/Service/Remote/Exception/AuthorizeException.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service\Remote\Exception;
|
||||||
|
|
||||||
|
class AuthorizeException extends \RuntimeException
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
145
src/Service/Remote/UsetrenoHttpClient.php
Normal file
145
src/Service/Remote/UsetrenoHttpClient.php
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Service\Remote;
|
||||||
|
|
||||||
|
use App\Entity\Remote\Usetreno\AuthRequest;
|
||||||
|
use App\Service\Remote\Exception\AuthorizeException;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\ResponseInterface;
|
||||||
|
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
|
||||||
|
|
||||||
|
class UsetrenoHttpClient implements HttpClientInterface
|
||||||
|
{
|
||||||
|
const AUTHORIZE_API = "https://topapi.top-test.cz/chameleon/api/v1/token";
|
||||||
|
|
||||||
|
protected ?string $authorizationToken = null;
|
||||||
|
|
||||||
|
public function __construct(protected HttpClientInterface $innerClient, protected readonly LoggerInterface $logger, protected readonly string $username,
|
||||||
|
protected readonly string $password) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $method
|
||||||
|
* @param string $url
|
||||||
|
* @param array<string, mixed> $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<array-key, ResponseInterface> $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<string, mixed> $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;
|
||||||
|
}
|
||||||
|
}
|
18
tests/Common/LoggerTrait.php
Normal file
18
tests/Common/LoggerTrait.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Common;
|
||||||
|
|
||||||
|
use Monolog\Handler\TestHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
|
||||||
|
trait LoggerTrait
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Logger
|
||||||
|
*/
|
||||||
|
protected function getLogger(): Logger {
|
||||||
|
$logger = new Logger('test');
|
||||||
|
$logger->pushHandler(new TestHandler());
|
||||||
|
return $logger;
|
||||||
|
}
|
||||||
|
}
|
54
tests/Service/Remote/UsetrenoHttpClientTest.php
Normal file
54
tests/Service/Remote/UsetrenoHttpClientTest.php
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Tests\Service\Remote;
|
||||||
|
|
||||||
|
use App\Service\Remote\Exception\AuthorizeException;
|
||||||
|
use App\Service\Remote\UsetrenoHttpClient;
|
||||||
|
use App\Tests\Common\LoggerTrait;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
use Symfony\Component\HttpClient\MockHttpClient;
|
||||||
|
use Symfony\Component\HttpClient\Response\JsonMockResponse;
|
||||||
|
|
||||||
|
class UsetrenoHttpClientTest extends TestCase
|
||||||
|
{
|
||||||
|
use LoggerTrait;
|
||||||
|
|
||||||
|
public function testRequestWillDoAutomaticAuthorization() {
|
||||||
|
$authMockResponse = new JsonMockResponse([
|
||||||
|
"data" => [
|
||||||
|
"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/");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user