'Areal', 'uf' => 'RJ', 'estado' => 'Rio de Janeiro', 'regiao' => 'Sudeste', 'ibge' => '3300225', 'cep_ini' => '25845001', 'cep_fim' => '25849899', 'ceps' => 118, ]; public function testSuccessfulLookup(): void { $this->server->respond('/01001000', [self::json(self::SE_ADDRESS)]); $address = $this->client->lookup('01001-000'); self::assertSame('Praça da Sé', $address->logradouro); self::assertSame('São Paulo', $address->localidade); self::assertSame('SP', $address->uf); self::assertSame('01001000', $address->cep); self::assertSame('01001-000', $address->formattedCep()); self::assertNull($address->geo); } public function testReadsTheCoordinateWhenPresent(): void { $body = self::SE_ADDRESS + []; $body['geo'] = ['lat' => -23.567202, 'lon' => -46.649659, 'raio_m' => 1084, 'precisao' => 'logradouro']; $this->server->respond('/01310100', [self::json($body)]); $address = $this->client->lookup('01310100'); self::assertNotNull($address->geo); self::assertEqualsWithDelta(-23.567202, $address->geo->lat, 1e-9); self::assertEqualsWithDelta(-46.649659, $address->geo->lon, 1e-9); self::assertSame(1084, $address->geo->raio_m); self::assertSame('logradouro', $address->geo->precisao); self::assertTrue($address->geo->isPrecise()); } public function testMunicipalityCoordinateIsNotPrecise(): void { $body = self::SE_ADDRESS; $body['geo'] = ['lat' => -23.5, 'lon' => -46.6, 'raio_m' => 22000, 'precisao' => 'localidade']; $this->server->respond('/01001000', [self::json($body)]); $geo = $this->client->lookup('01001000')->geo; self::assertNotNull($geo); self::assertFalse($geo->isPrecise()); } public function testMalformedGeoDoesNotLoseTheAddress(): void { $body = self::SE_ADDRESS; $body['geo'] = ['lat' => 'not a number']; $this->server->respond('/01001000', [self::json($body)]); $address = $this->client->lookup('01001000'); self::assertNull($address->geo); self::assertSame('São Paulo', $address->localidade); } public function testNewApiFieldDoesNotBreak(): void { $body = self::SE_ADDRESS; $body['campo_do_futuro'] = 'value'; $this->server->respond('/01001000', [self::json($body)]); $address = $this->client->lookup('01001000'); self::assertSame('value', $address->raw['campo_do_futuro']); self::assertSame('value', $address->field('campo_do_futuro')); self::assertNull($address->field('nao_existe')); self::assertSame('São Paulo', $address->localidade); } public function testMissingFieldsBecomeEmptyStringsNotNull(): void { $this->server->respond('/01001000', [self::json(['cep' => '01001000'])]); $address = $this->client->lookup('01001000'); self::assertSame('', $address->logradouro); self::assertSame('', $address->uf); } public function testNotFound(): void { $this->server->respond('/99999999', [self::json( ['erro' => 'cep_nao_encontrado', 'mensagem' => 'CEP não existe na base do DNE'], 404, )]); try { $this->client->lookup('99999999'); self::fail('expected CepNotFoundException'); } catch (CepNotFoundException $exception) { self::assertStringContainsString('99999999', $exception->getMessage()); self::assertNull($exception->faixa); } } public function testNotFoundCarriesTheFaixaWhenTheApiSendsOne(): void { $this->server->respond('/25845000', [self::json( [ 'erro' => 'cep_nao_encontrado', 'mensagem' => 'CEP não existe na base do DNE', 'faixa' => self::AREAL_FAIXA, ], 404, )]); try { $this->client->lookup('25845-000'); self::fail('expected CepNotFoundException'); } catch (CepNotFoundException $exception) { $faixa = $exception->faixa; self::assertNotNull($faixa); self::assertSame('Areal', $faixa->localidade); self::assertSame('RJ', $faixa->uf); self::assertSame('Rio de Janeiro', $faixa->estado); self::assertSame('Sudeste', $faixa->regiao); self::assertSame('3300225', $faixa->ibge); self::assertSame(118, $faixa->ceps); self::assertSame('Areal/RJ', $faixa->label()); // The bounds are the CEPs that exist, not the ones the Correios // publish: the CEP that just failed is the DNE's own start for // Areal and is below cep_ini for exactly that reason. self::assertSame('25845001', $faixa->cep_ini); self::assertSame('25849899', $faixa->cep_fim); self::assertTrue($faixa->cep_ini > '25845000'); // 118 CEPs over ~4,900 numbers: the pair is a range, never a list. self::assertLessThan( (int) $faixa->cep_fim - (int) $faixa->cep_ini, $faixa->ceps, ); self::assertStringContainsString('Areal/RJ', $exception->getMessage()); self::assertStringContainsString('25845000', $exception->getMessage()); } } public function testFaixaIsStillANotFoundAndNotAnAddress(): void { $this->server->respond('/25845000', [self::json( ['erro' => 'cep_nao_encontrado', 'faixa' => self::AREAL_FAIXA], 404, )]); $this->expectException(CepNotFoundException::class); $this->client->lookup('25845000'); } /** * A `faixa` this client cannot read costs nothing: the answer is the same * not-found it would have been without one. Anything else would trade a * useful exception for an unhelpful parse failure. */ public function testUnreadableFaixaStillGivesACleanNotFound(): void { $shapes = [ 'null' => null, 'empty object' => [], 'a string' => 'Areal', 'a number' => 42, 'a list' => ['Areal', 'RJ'], 'no municipality' => ['uf' => 'RJ', 'cep_ini' => '25845001'], 'empty municipality' => ['localidade' => '', 'uf' => 'RJ'], 'nested junk' => ['localidade' => ['Areal'], 'ceps' => 'many'], ]; $specs = []; foreach ($shapes as $faixa) { $specs[] = self::json(['erro' => 'cep_nao_encontrado', 'faixa' => $faixa], 404); } $this->server->respond('/25845000', $specs); foreach (array_keys($shapes) as $name) { try { $this->client->lookup('25845000'); self::fail('expected CepNotFoundException for ' . $name); } catch (CepNotFoundException $exception) { self::assertNull($exception->faixa, $name); self::assertStringContainsString('25845000', $exception->getMessage(), $name); } } } public function testPartialFaixaKeepsWhatItCanRead(): void { $this->server->respond('/25845000', [self::json( ['erro' => 'cep_nao_encontrado', 'faixa' => ['localidade' => 'Areal', 'ceps' => 'many']], 404, )]); try { $this->client->lookup('25845000'); self::fail('expected CepNotFoundException'); } catch (CepNotFoundException $exception) { self::assertNotNull($exception->faixa); self::assertSame('Areal', $exception->faixa->localidade); self::assertSame('', $exception->faixa->uf); self::assertSame('', $exception->faixa->cep_ini); self::assertSame(0, $exception->faixa->ceps); self::assertSame('Areal', $exception->faixa->label()); } } public function testUnreadableNotFoundBodyStillGivesACleanNotFound(): void { $this->server->respond('/25845000', [self::raw(404, 'proxy says no')]); try { $this->client->lookup('25845000'); self::fail('expected CepNotFoundException'); } catch (CepNotFoundException $exception) { self::assertNull($exception->faixa); } } public function testNotFoundIsNotRetried(): void { $this->server->respond('/99999999', [self::json(['erro' => 'cep_nao_encontrado'], 404)]); $this->expectException(CepNotFoundException::class); try { $this->client->lookup('99999999'); } finally { self::assertSame(1, $this->server->countCalls('/99999999')); } } public function testInvalidCepNeverLeavesTheMachine(): void { try { $this->client->lookup('123'); self::fail('expected InvalidCepException'); } catch (InvalidCepException) { self::assertSame([], $this->server->calls()); } } public function testApi400BecomesInvalidCep(): void { $this->server->respond('/00000000', [self::json( ['erro' => 'cep_invalido', 'mensagem' => 'rejected by the API'], 400, )]); try { $this->client->lookup('00000000'); self::fail('expected InvalidCepException'); } catch (InvalidCepException $exception) { self::assertSame('rejected by the API', $exception->getMessage()); self::assertSame(1, $this->server->countCalls('/00000000')); } } public function testResponseThatIsNotJson(): void { $this->server->respond('/01001000', [self::raw(200, 'captive portal')]); $this->expectException(InvalidResponseException::class); $this->client->lookup('01001000'); } public function testJsonArrayIsNotAnObject(): void { $this->server->respond('/01001000', [self::raw(200, '[1, 2, 3]')]); $this->expectException(InvalidResponseException::class); $this->client->lookup('01001000'); } }