summaryrefslogtreecommitdiffstats
path: root/tests/RetryTest.php
diff options
context:
space:
mode:
Diffstat (limited to 'tests/RetryTest.php')
-rw-r--r--tests/RetryTest.php127
1 files changed, 127 insertions, 0 deletions
diff --git a/tests/RetryTest.php b/tests/RetryTest.php
new file mode 100644
index 0000000..1b922b6
--- /dev/null
+++ b/tests/RetryTest.php
@@ -0,0 +1,127 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Cepis\Tests;
+
+use Cepis\Client;
+use Cepis\NetworkException;
+use Cepis\RateLimitedException;
+use Cepis\ServerErrorException;
+
+final class RetryTest extends ServerTestCase
+{
+ public function testRetriesOn500ThenSucceeds(): void
+ {
+ $this->server->respond('/01001000', [
+ self::json(['erro' => 'interno'], 500),
+ self::json(self::SE_ADDRESS),
+ ]);
+
+ self::assertSame('São Paulo', $this->client->lookup('01001000')->localidade);
+ self::assertSame(2, $this->server->countCalls('/01001000'));
+ }
+
+ public function testExhaustsAttemptsThenThrows(): void
+ {
+ $this->server->respond('/01001000', [self::json(['erro' => 'interno'], 503)]);
+
+ try {
+ $this->client->lookup('01001000');
+ self::fail('expected ServerErrorException');
+ } catch (ServerErrorException $exception) {
+ self::assertSame(503, $exception->status);
+ self::assertSame(3, $this->server->countCalls('/01001000'));
+ }
+ }
+
+ public function testMaxAttemptsOneDisablesRetry(): void
+ {
+ $client = new Client(baseUrl: $this->server->url, timeout: 5.0, maxAttempts: 1);
+ $this->server->respond('/01001000', [self::raw(500, '{}')]);
+
+ try {
+ $client->lookup('01001000');
+ self::fail('expected ServerErrorException');
+ } catch (ServerErrorException) {
+ self::assertSame(1, $this->server->countCalls('/01001000'));
+ }
+ }
+
+ public function test429BecomesRateLimitedWithRetryAfter(): void
+ {
+ $this->server->respond('/01001000', [self::raw(429, '{}', ['Retry-After' => '7'])]);
+
+ try {
+ $this->client->lookup('01001000');
+ self::fail('expected RateLimitedException');
+ } catch (RateLimitedException $exception) {
+ self::assertNotNull($exception->retryAfter);
+ self::assertEqualsWithDelta(7.0, $exception->retryAfter, 1e-9);
+ }
+ }
+
+ public function testRetryAfterAcceptsAnHttpDate(): void
+ {
+ $date = gmdate('D, d M Y H:i:s \G\M\T', time() + 4);
+ $this->server->respond('/01001000', [self::raw(429, '{}', ['Retry-After' => $date])]);
+
+ try {
+ $this->client->lookup('01001000');
+ self::fail('expected RateLimitedException');
+ } catch (RateLimitedException $exception) {
+ self::assertNotNull($exception->retryAfter);
+ self::assertGreaterThan(0.0, $exception->retryAfter);
+ self::assertLessThanOrEqual(5.0, $exception->retryAfter);
+ }
+ }
+
+ public function testRetryAfterHonoursTheCap(): void
+ {
+ $this->server->respond('/01001000', [self::raw(429, '{}', ['Retry-After' => '3600'])]);
+ $started = self::monotonic();
+
+ try {
+ $this->client->lookup('01001000');
+ self::fail('expected RateLimitedException');
+ } catch (RateLimitedException) {
+ self::assertLessThan(5.0, self::monotonic() - $started);
+ }
+ }
+
+ public function testNetworkErrorWhenNothingListens(): void
+ {
+ $client = new Client(
+ baseUrl: 'http://127.0.0.1:1',
+ timeout: 1.0,
+ maxAttempts: 2,
+ backoffBase: 0.001,
+ backoffMax: 0.01,
+ );
+
+ $this->expectException(NetworkException::class);
+ $client->lookup('01001000');
+ }
+
+ public function testTotalTimeoutBoundsTheWholeCall(): void
+ {
+ $client = new Client(
+ baseUrl: $this->server->url,
+ timeout: 5.0,
+ maxAttempts: 50,
+ backoffBase: 0.05,
+ backoffMax: 0.05,
+ totalTimeout: 0.3,
+ );
+ $this->server->respond('/01001000', [self::raw(500, '{}')]);
+ $started = self::monotonic();
+
+ try {
+ $client->lookup('01001000');
+ self::fail('expected ServerErrorException');
+ } catch (ServerErrorException) {
+ self::assertLessThan(5.0, self::monotonic() - $started);
+ self::assertLessThan(50, $this->server->countCalls('/01001000'));
+ }
+ }
+}