diff options
| author | Murilo Ijanc <[email protected]> | 2026-07-27 20:37:05 -0300 |
|---|---|---|
| committer | Murilo Ijanc <[email protected]> | 2026-07-27 20:44:20 -0300 |
| commit | eb15ec9b2715783fcd2bf4f834595d1bd549c09d (patch) | |
| tree | cd9c6e80892b409db346fde3fe8334f3c37a1130 /tests | |
| parent | e2c6ae13af335b311da2429c903032124f926df2 (diff) | |
| download | c-eb15ec9b2715783fcd2bf4f834595d1bd549c09d.tar.gz | |
A CEP that fails is rarely a random number. It is usually someone who
knows the city and guessed the last digits, and the classic guess is the
-000 that 957 municipalities never received. The API now answers those
404s with a faixa block naming the municipality the CEP would have
belonged to, and telling a caller "that CEP does not exist, did you mean
somewhere in Areal/RJ?" is worth far more than telling them "no".
It rides on cepis_error_t rather than on cepis_address_t because it is
context for a failure, not an address: the lookup still returns
CEPIS_ERR_NOT_FOUND and still hands back no address. Embedded by value
with a has_faixa flag, so the error keeps owning nothing and keeps
living on the stack with nothing to free; cepis_error_faixa gives the
absent case the same NULL-or-not shape as cepis_address_t::geo, which is
what the API's omit-never-null encoding calls for.
Parsing is total on purpose. A body that is not JSON, a faixa that is
null, a string, an array, or an object with no localidade all mean "the
API named no municipality". A 404 is already the answer, and failing to
read the garnish must never turn it into a different one.
The bounds need reading with care, hence the length of the comments:
cep_ini and cep_fim are the smallest and largest CEP that actually
exist, not the range the Correios publish -- the DNE's own start for
Areal is 25845000, exactly the CEP that just failed -- and the range is
sparse, 118 CEPs scattered over the ~4900 numbers up to 25849899. That
is what ceps is for.
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_cepis.c | 124 |
1 files changed, 124 insertions, 0 deletions
diff --git a/tests/test_cepis.c b/tests/test_cepis.c index ee23305..b17ab67 100644 --- a/tests/test_cepis.c +++ b/tests/test_cepis.c @@ -310,6 +310,126 @@ static void test_not_found_is_not_retried(void) fixture_down(&fixture); } +static void test_not_found_names_the_municipality(void) +{ + fixture_t fixture; + cepis_address_t *address = NULL; + cepis_error_t err; + const cepis_faixa_t *faixa; + + fixture_up(&fixture); + ts_respond(fixture.server, "/25845000", 404, + "{\"erro\":\"cep_nao_encontrado\",\"mensagem\":" + "\"CEP n\xC3\xA3o existe na base do DNE\",\"faixa\":" + "{\"localidade\":\"Areal\",\"uf\":\"RJ\",\"estado\":" + "\"Rio de Janeiro\",\"regiao\":\"Sudeste\",\"ibge\":" + "\"3300225\",\"cep_ini\":\"25845001\",\"cep_fim\":" + "\"25849899\",\"ceps\":118}}", + NULL); + + /* Still a 404: the block is context for the failure, not an address. */ + CHECK(cepis_lookup(fixture.client, "25845-000", &address, &err) == + CEPIS_ERR_NOT_FOUND); + CHECK(address == NULL); + CHECK(err.http_status == 404); + + CHECK(err.has_faixa); + faixa = cepis_error_faixa(&err); + CHECK(faixa == &err.faixa); + if (faixa != NULL) { + CHECK_STR(faixa->localidade, "Areal"); + CHECK_STR(faixa->uf, "RJ"); + CHECK_STR(faixa->estado, "Rio de Janeiro"); + CHECK_STR(faixa->regiao, "Sudeste"); + CHECK_STR(faixa->ibge, "3300225"); + /* + * Not the DNE's own start, which is 25845000 -- the CEP that + * just failed. These are the smallest and largest that really + * exist, and the 118 are sparse across the ~4900 between them. + */ + CHECK_STR(faixa->cep_ini, "25845001"); + CHECK_STR(faixa->cep_fim, "25849899"); + CHECK(faixa->ceps == 118); + } + fixture_down(&fixture); +} + +static void test_not_found_without_faixa_leaves_it_absent(void) +{ + fixture_t fixture; + cepis_address_t *address = NULL; + cepis_error_t err; + + fixture_up(&fixture); + ts_respond(fixture.server, "/99999999", 404, + "{\"erro\":\"cep_nao_encontrado\",\"mensagem\":" + "\"CEP n\xC3\xA3o existe na base do DNE\"}", + NULL); + + /* A band no municipality owns: the API omits the block, never nulls + * it, and the caller must be able to tell absent from present. */ + CHECK(cepis_lookup(fixture.client, "99999999", &address, &err) == + CEPIS_ERR_NOT_FOUND); + CHECK(!err.has_faixa); + CHECK(cepis_error_faixa(&err) == NULL); + fixture_down(&fixture); +} + +static void test_malformed_faixa_is_still_a_clean_not_found(void) +{ + static const char *bodies[] = { + "{\"erro\":\"x\",\"faixa\":null}", + "{\"erro\":\"x\",\"faixa\":\"Areal\"}", + "{\"erro\":\"x\",\"faixa\":[]}", + "{\"erro\":\"x\",\"faixa\":{}}", + "{\"erro\":\"x\",\"faixa\":{\"localidade\":\"\"}}", + "{\"erro\":\"x\",\"faixa\":{\"uf\":\"RJ\",\"ceps\":118}}", + "{\"erro\":\"x\",\"faixa\":{\"localidade\":42}}", + "not json at all", + ""}; + fixture_t fixture; + cepis_error_t err; + size_t i; + + fixture_up(&fixture); + for (i = 0; i < sizeof(bodies) / sizeof(bodies[0]); i++) { + cepis_address_t *address = NULL; + + ts_respond(fixture.server, "/99999999", 404, bodies[i], NULL); + CHECK(cepis_lookup(fixture.client, "99999999", &address, + &err) == CEPIS_ERR_NOT_FOUND); + CHECK(address == NULL); + CHECK(!err.has_faixa); + CHECK(cepis_error_faixa(&err) == NULL); + } + fixture_down(&fixture); +} + +static void test_faixa_does_not_survive_the_next_error(void) +{ + fixture_t fixture; + cepis_address_t *address = NULL; + cepis_error_t err; + + fixture_up(&fixture); + ts_respond(fixture.server, "/25845000", 404, + "{\"erro\":\"cep_nao_encontrado\",\"faixa\":" + "{\"localidade\":\"Areal\",\"uf\":\"RJ\",\"cep_ini\":" + "\"25845001\",\"cep_fim\":\"25849899\",\"ceps\":118}}", + NULL); + CHECK(cepis_lookup(fixture.client, "25845000", &address, &err) == + CEPIS_ERR_NOT_FOUND); + CHECK(err.has_faixa); + + /* Same error struct reused: a stale block would be read as a + * municipality this second failure never named. */ + CHECK(cepis_lookup(fixture.client, "123", &address, &err) == + CEPIS_ERR_INVALID_CEP); + CHECK(!err.has_faixa); + CHECK(cepis_error_faixa(&err) == NULL); + fixture_down(&fixture); +} + static void test_invalid_cep_never_leaves_the_machine(void) { fixture_t fixture; @@ -903,6 +1023,10 @@ int main(void) RUN(test_malformed_geo_does_not_lose_the_address); RUN(test_new_api_field_does_not_break); RUN(test_not_found_is_not_retried); + RUN(test_not_found_names_the_municipality); + RUN(test_not_found_without_faixa_leaves_it_absent); + RUN(test_malformed_faixa_is_still_a_clean_not_found); + RUN(test_faixa_does_not_survive_the_next_error); RUN(test_invalid_cep_never_leaves_the_machine); RUN(test_api_400_becomes_invalid_cep); RUN(test_response_that_is_not_json); |