transport); } public function testAnAlternativeTransportIsUsedEndToEnd(): void { $client = new Client(baseUrl: $this->server->url, transport: new StreamTransport()); $this->server->respond('/01001000', [self::json(self::SE_ADDRESS)]); $address = $client->lookup('01001000'); self::assertSame('São Paulo', $address->localidade); self::assertSame(1, $this->server->countCalls('/01001000')); } public function testTheClientSendsItsIdentifyingHeaders(): void { $transport = new RecordingTransport([new Response(200, [], '{"cep":"01001000"}')]); $client = new Client(baseUrl: 'https://example.invalid', timeout: 3.5, transport: $transport); $client->lookup('01001000'); self::assertSame('https://example.invalid/01001000', $transport->calls[0]['url']); self::assertStringContainsString('cepis-php/', $transport->calls[0]['headers']['User-Agent']); self::assertArrayHasKey('Accept', $transport->calls[0]['headers']); self::assertSame(3.5, $transport->calls[0]['timeout']); } public function testATransportThrowingNetworkExceptionIsRetried(): void { $transport = new RecordingTransport([]); $client = new Client( baseUrl: 'https://example.invalid', maxAttempts: 3, backoffBase: 0.001, backoffMax: 0.01, transport: $transport, ); try { $client->lookup('01001000'); self::fail('expected NetworkException'); } catch (NetworkException) { self::assertCount(3, $transport->calls); } } public function testATransportReturningA503IsRetriedNotThrown(): void { $transport = new RecordingTransport([ new Response(503, [], '{}'), new Response(503, [], '{}'), new Response(200, [], json_encode(self::SE_ADDRESS, JSON_THROW_ON_ERROR)), ]); $client = new Client( baseUrl: 'https://example.invalid', backoffBase: 0.001, backoffMax: 0.01, transport: $transport, ); self::assertSame('São Paulo', $client->lookup('01001000')->localidade); self::assertCount(3, $transport->calls); } public function testA404FromATransportIsNotRetried(): void { $transport = new RecordingTransport([new Response(404, [], '{}')]); $client = new Client(baseUrl: 'https://example.invalid', transport: $transport); try { $client->lookup('99999999'); self::fail('expected CepNotFoundException'); } catch (CepNotFoundException) { self::assertCount(1, $transport->calls); } } public function testRetryAfterIsReadFromATransportSuppliedHeader(): void { $transport = new RecordingTransport([new Response(429, ['retry-after' => ['5']], '{}')]); $client = new Client( baseUrl: 'https://example.invalid', maxAttempts: 1, transport: $transport, ); try { $client->lookup('01001000'); self::fail('expected RateLimitedException'); } catch (RateLimitedException $exception) { self::assertEqualsWithDelta(5.0, $exception->retryAfter, 1e-9); } } public function testResponseHeaderLookupIsCaseInsensitiveAndTakesListValues(): void { $response = new Response(200, ['Retry-After' => '7', 'X-Multi' => ['a', 'b']], ''); self::assertSame('7', $response->header('retry-after')); self::assertSame('7', $response->header('RETRY-AFTER')); self::assertSame('a', $response->header('x-multi')); self::assertNull($response->header('absent')); } public function testServerErrorStillSurfacesThroughAnAlternativeTransport(): void { $client = new Client( baseUrl: $this->server->url, backoffBase: 0.001, backoffMax: 0.01, transport: new StreamTransport(), ); $this->server->respond('/01001000', [self::raw(500, '{}')]); $this->expectException(ServerErrorException::class); $client->lookup('01001000'); } }