diff options
Diffstat (limited to 'tests/RetryTest.php')
| -rw-r--r-- | tests/RetryTest.php | 127 |
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')); + } + } +} |