Compare commits

..

1 Commits

Author SHA1 Message Date
acd9bfc2b3
feat: add sentry 2024-01-18 18:59:31 +01:00
12 changed files with 41 additions and 251 deletions

View File

@ -10,7 +10,6 @@
"ext-iconv": "*", "ext-iconv": "*",
"grpc/grpc": "^1.57", "grpc/grpc": "^1.57",
"guzzlehttp/promises": "*", "guzzlehttp/promises": "*",
"nubium/this-should-never-happen-exception": "^1.0",
"nyholm/psr7": "*", "nyholm/psr7": "*",
"open-telemetry/exporter-otlp": "^1.0", "open-telemetry/exporter-otlp": "^1.0",
"open-telemetry/opentelemetry-auto-symfony": "^1.0@beta", "open-telemetry/opentelemetry-auto-symfony": "^1.0@beta",

43
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "60708df22ad1ee4eca712d46f8e02c3b", "content-hash": "61ab226932ff1cf0567a22bbf802e20c",
"packages": [ "packages": [
{ {
"name": "clue/stream-filter", "name": "clue/stream-filter",
@ -658,47 +658,6 @@
], ],
"time": "2023-10-27T15:32:31+00:00" "time": "2023-10-27T15:32:31+00:00"
}, },
{
"name": "nubium/this-should-never-happen-exception",
"version": "v1.0",
"source": {
"type": "git",
"url": "https://github.com/nubium/this-should-never-happen-exception.git",
"reference": "3ed1b6f725881c527050c235e2503a8300427b86"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nubium/this-should-never-happen-exception/zipball/3ed1b6f725881c527050c235e2503a8300427b86",
"reference": "3ed1b6f725881c527050c235e2503a8300427b86",
"shasum": ""
},
"require-dev": {
"jakub-onderka/php-parallel-lint": "~1.0",
"phpstan/phpstan": "~0.9"
},
"type": "library",
"autoload": {
"psr-4": {
"Nubium\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jiri Travnicek",
"email": "jiri.travnicek@nubium.cz"
}
],
"description": "Extend this exception and throw it anytime something unexpected happens.",
"support": {
"issues": "https://github.com/nubium/this-should-never-happen-exception/issues",
"source": "https://github.com/nubium/this-should-never-happen-exception/tree/master"
},
"time": "2018-03-27T10:16:09+00:00"
},
{ {
"name": "nyholm/psr7", "name": "nyholm/psr7",
"version": "1.8.1", "version": "1.8.1",

View File

@ -39,13 +39,6 @@ services:
arguments: arguments:
$username: '%app.usetreno.username%' $username: '%app.usetreno.username%'
$password: '%app.usetreno.password%' $password: '%app.usetreno.password%'
$retryCount: 2
$retryWaitSeconds: 0.5
App\Service\Remote\UsetrenoQRCodeProvider:
arguments:
$retryCount: 2
$retryWaitSeconds: 0.5
App\Service\CachedQRCodeGenerator: App\Service\CachedQRCodeGenerator:
arguments: arguments:

View File

@ -11,7 +11,7 @@ use Symfony\Component\Routing\Attribute\Route;
class ExceptionController extends AbstractController class ExceptionController extends AbstractController
{ {
#[Route("give-me-error-please/exception")] #[Route("give-me-error-please/exception")]
public function makeException(): void { public function makeException() {
throw new \InvalidArgumentException("There is exception"); throw new \InvalidArgumentException("There is exception");
} }
} }

View File

@ -1,5 +1,4 @@
<?php <?php
declare(strict_types=1);
namespace App\Entity\Remote\Usetreno; namespace App\Entity\Remote\Usetreno;

View File

@ -1,9 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Service\Remote\Exception;
class UsetrenoQRCodeRemoteServerErrorException extends UsetrenoQRCodeException
{
}

View File

@ -1,59 +0,0 @@
<?php
namespace App\Service\Remote;
use App\Service\Remote\Exception\AuthorizeException;
use Exception;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
trait RetryingFailClientTrait
{
/**
* Lepsi zopakovat request kvuli drobnemu vypadku site nebo sluzby (u nedestruktivni operace)
* nez hodit klientovi rovnou 500
*
* @param int $count
* @param float $sleep
* @param array<string> $catchableExceptions
* @param LoggerInterface $logger
* @param callable $callback
* @return mixed
* @throws Exception
*/
protected function retryingFailRequest(
int $count,
float $sleep,
array $catchableExceptions,
LoggerInterface $logger,
callable $callback
): mixed {
for ($i = 0; ; $i++) {
try {
return $callback();
} catch (Exception $e) {
foreach ($catchableExceptions as $exceptionClass) {
if ($e instanceof $exceptionClass) {
$logger->error("transport: fail request retrying... got catchable exception", [
'exception' => $e,
'try' => $i
]);
usleep((int) ($sleep * 1_000_000));
if ($i == $count) {
throw $e;
}
continue 2;
}
}
throw $e;
}
}
// phpstan fail
return null;
}
}

View File

@ -5,9 +5,7 @@ namespace App\Service\Remote;
use App\Entity\Remote\Usetreno\AuthRequest; use App\Entity\Remote\Usetreno\AuthRequest;
use App\Service\Remote\Exception\AuthorizeException; use App\Service\Remote\Exception\AuthorizeException;
use Nubium\Exception\ThisShouldNeverHappenException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface; use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface; use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface; use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
@ -18,16 +16,12 @@ use Symfony\Contracts\HttpClient\ResponseStreamInterface;
class UsetrenoHttpClient implements HttpClientInterface class UsetrenoHttpClient implements HttpClientInterface
{ {
use RetryingFailClientTrait;
const AUTHORIZE_API = "https://topapi.top-test.cz/chameleon/api/v1/token"; const AUTHORIZE_API = "https://topapi.top-test.cz/chameleon/api/v1/token";
protected ?string $authorizationToken = null; protected ?string $authorizationToken = null;
public function __construct(protected HttpClientInterface $innerClient, protected readonly LoggerInterface $logger, protected readonly string $username, public function __construct(protected HttpClientInterface $innerClient, protected readonly LoggerInterface $logger, protected readonly string $username,
protected readonly string $password, protected readonly string $password) { }
protected readonly float $retryWaitSeconds,
protected readonly int $retryCount) { }
/** /**
* @param string $method * @param string $method
@ -94,35 +88,26 @@ class UsetrenoHttpClient implements HttpClientInterface
"AUTHORIZE_API" => static::AUTHORIZE_API "AUTHORIZE_API" => static::AUTHORIZE_API
]); ]);
$responseData = $this->retryingFailRequest($this->retryCount, $this->retryWaitSeconds, $rq = $this->innerClient->request(
[AuthorizeException::class, TransportException::class], $this->logger, function() { "POST",
$rq = $this->innerClient->request( static::AUTHORIZE_API,
"POST", [
static::AUTHORIZE_API, 'json' => new AuthRequest($this->username, $this->password),
[ ]
'json' => new AuthRequest($this->username, $this->password), );
]
);
$statusCode = $rq->getStatusCode(); $statusCode = $rq->getStatusCode();
$responseData = $rq->getContent(false); $responseData = $rq->getContent(false);
if ($statusCode != 200) { if ($statusCode != 200) {
$this->logger->error("authorization request status code is not ok", [ $this->logger->error("authorization request status code is not ok", [
"AUTHORIZE_API" => static::AUTHORIZE_API, "AUTHORIZE_API" => static::AUTHORIZE_API,
"statusCode" => $statusCode, "statusCode" => $statusCode,
"content" => $responseData, "content" => $responseData,
"username" => $this->username "username" => $this->username
]); ]);
throw new AuthorizeException("Return code is not 200 OK (got: code: $statusCode)"); throw new AuthorizeException("Return code is not 200 OK (got: code: $statusCode)");
}
return $responseData;
});
if (!is_string($responseData)) {
throw new ThisShouldNeverHappenException("responseData is not a string");
} }
$this->authorizationToken = $this->processAuthorizeResponse($responseData); $this->authorizationToken = $this->processAuthorizeResponse($responseData);

View File

@ -7,23 +7,16 @@ use App\Entity\DTO\QRCode\QRCode;
use App\Service\CacheableQRCodeGeneratorInterface; use App\Service\CacheableQRCodeGeneratorInterface;
use App\Service\Remote\Edge\QRCodeEntityConverter; use App\Service\Remote\Edge\QRCodeEntityConverter;
use App\Service\Remote\Exception\UsetrenoQRCodeException; use App\Service\Remote\Exception\UsetrenoQRCodeException;
use App\Service\Remote\Exception\UsetrenoQRCodeRemoteServerErrorException;
use Nubium\Exception\ThisShouldNeverHappenException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
class UsetrenoQRCodeProvider implements CacheableQRCodeGeneratorInterface class UsetrenoQRCodeProvider implements CacheableQRCodeGeneratorInterface
{ {
use RetryingFailClientTrait;
const QRCODE_API = 'https://topapi.top-test.cz/chameleon/api/v1/qr-code/create-for-bank-account-payment'; const QRCODE_API = 'https://topapi.top-test.cz/chameleon/api/v1/qr-code/create-for-bank-account-payment';
const QRCODE_METHOD = 'POST'; const QRCODE_METHOD = 'POST';
public function __construct(protected readonly LoggerInterface $logger, public function __construct(protected readonly LoggerInterface $logger,
protected readonly UsetrenoHttpClient $client, protected readonly UsetrenoHttpClient $client,
protected readonly QRCodeEntityConverter $codeEntityConverter, protected readonly QRCodeEntityConverter $codeEntityConverter) { }
protected readonly float $retryWaitSeconds,
protected readonly int $retryCount) { }
public function generateQRCodeFromEntity(QRCode $entity): string public function generateQRCodeFromEntity(QRCode $entity): string
{ {
@ -34,41 +27,27 @@ class UsetrenoQRCodeProvider implements CacheableQRCodeGeneratorInterface
"edgeEntity" => $edgeEntity "edgeEntity" => $edgeEntity
]); ]);
$responseData = $this->retryingFailRequest($this->retryCount, $this->retryWaitSeconds, $response = $this->client->request(static::QRCODE_METHOD, static::QRCODE_API, [
[TransportException::class, UsetrenoQRCodeRemoteServerErrorException::class], $this->logger, 'json' => $edgeEntity,
function() use ($edgeEntity) { ]);
$response = $this->client->request(static::QRCODE_METHOD, static::QRCODE_API, [
'json' => $edgeEntity,
]);
$statusCode = $response->getStatusCode(); $statusCode = $response->getStatusCode();
$responseData = $response->getContent(false); $responseData = $response->getContent(false);
if ($statusCode != 200) { if ($statusCode != 200) {
$this->logger->error("qrcode request status code is not ok", [ $this->logger->error("qrcode request status code is not ok", [
"AUTHORIZE_API" => static::QRCODE_API, "AUTHORIZE_API" => static::QRCODE_API,
"statusCode" => $statusCode, "statusCode" => $statusCode,
"content" => $responseData, "content" => $responseData,
]); ]);
if ($statusCode > 500) { throw new UsetrenoQRCodeException("Return code is not 200 OK (got: code: $statusCode)");
throw new UsetrenoQRCodeRemoteServerErrorException("Return code is not 200 OK (got: code: $statusCode)");
}
throw new UsetrenoQRCodeException("Return code is not 200 OK (got: code: $statusCode)");
}
$this->logger->debug("QRCode generation success", [
"responseContent" => $responseData,
]);
return $responseData;
});
if (!is_string($responseData)) {
throw new ThisShouldNeverHappenException("responseData is not a string");
} }
$this->logger->debug("QRCode generation success", [
"responseContent" => $responseData,
]);
return $this->parseBase64String($this->processQRCodeResponseEntity($responseData)); return $this->parseBase64String($this->processQRCodeResponseEntity($responseData));
} }

View File

@ -1,56 +0,0 @@
<?php
namespace App\Tests\Service\Remote;
use App\Service\Remote\RetryingFailClientTrait;
use App\Tests\Common\LoggerTrait;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
class RetryingFailClientTraitTest extends TestCase {
use LoggerTrait;
private int $callCount = 0;
protected function setUp(): void
{
parent::setUp(); // TODO: Change the autogenerated stub
$this->callCount = 0;
}
public function testSuccess() {
$trait = new class {
use RetryingFailClientTrait {
retryingFailRequest as public; // make the method public
}
};
$result = $trait->retryingFailRequest(2, 0, [\RuntimeException::class], $this->getLogger(), function () {
$this->callCount = $this->callCount + 1;
return 'foo';
});
$this->assertEquals(1, $this->callCount);
$this->assertEquals('foo', $result);
}
public function testRetyingFail() {
$trait = new class {
use RetryingFailClientTrait {
retryingFailRequest as public; // make the method public
}
};
try {
$trait->retryingFailRequest(2, 0, [\RuntimeException::class], $this->getLogger(), function () {
$this->callCount = $this->callCount + 1;
throw new \RuntimeException("test");
});
} catch (\RuntimeException) {
// do nothing
}
$this->assertEquals(3, $this->callCount);
}
}

View File

@ -24,7 +24,7 @@ class UsetrenoHttpClientTest extends TestCase
]); ]);
$authorizedRequestResponse = clone $authMockResponse; $authorizedRequestResponse = clone $authMockResponse;
$mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]); $mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]);
$client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar", 0, 0); $client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar");
$client->request("POST", "https://www.root.cz/"); $client->request("POST", "https://www.root.cz/");
$this->assertEquals("https://topapi.top-test.cz/chameleon/api/v1/token", $authMockResponse->getRequestUrl()); $this->assertEquals("https://topapi.top-test.cz/chameleon/api/v1/token", $authMockResponse->getRequestUrl());
$headers = $authorizedRequestResponse->getRequestOptions()['headers']; $headers = $authorizedRequestResponse->getRequestOptions()['headers'];
@ -48,7 +48,7 @@ class UsetrenoHttpClientTest extends TestCase
$authorizedRequestResponse = clone $authMockResponse; $authorizedRequestResponse = clone $authMockResponse;
$mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]); $mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]);
$client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar", 0, 0); $client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar");
$client->request("POST", "https://www.root.cz/"); $client->request("POST", "https://www.root.cz/");
} }
} }

View File

@ -75,7 +75,7 @@ class UsetrenoQRCodeProviderTest extends TestCase
->method('convert') ->method('convert')
->will($this->returnValue($edgeEntity)); ->will($this->returnValue($edgeEntity));
return new UsetrenoQRCodeProvider($this->getLogger(), $mock, $converterMock, 0, 0); return new UsetrenoQRCodeProvider($this->getLogger(), $mock, $converterMock);
} }
protected function createQRCodeEntityPair() { protected function createQRCodeEntityPair() {