summaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorMurilo Ijanc <[email protected]>2026-07-27 20:42:18 -0300
committerMurilo Ijanc <[email protected]>2026-07-27 20:42:18 -0300
commit218bc8fbfbbdc2520130ef43652a6cd22a6e67fb (patch)
tree4291a5bb3a235a21b44cfbc51bf1d0c6b9af0c3e /test
parent5598ea80735a51cb7b3690837b2b7d4240331b2d (diff)
downloadocaml-218bc8fbfbbdc2520130ef43652a6cd22a6e67fb.tar.gz
Carry the municipality range on a not-found CEPHEADv0.2.0main
A CEP that fails a lookup is rarely a random number. It is usually someone who knows the city and guessed the digits, and the classic guess is the -000 that 957 municipalities never received. The API now answers those 404s with a faixa naming the municipality whose band the number falls into, so a caller can ask "did you mean somewhere in Areal/RJ?" instead of returning a bare "not found". Cep_not_found becomes an inline record so the context rides along with the message, matching how Rate_limited and Server_error already carry theirs. It is still a 404: faixa is context on the failure and never promotes it to an address, and its bounds are the smallest and largest CEP that actually exist there — sparse across the interval, which is what the ceps count is for — not the bounds the Correios publish. Parsing is deliberately lenient. A missing, null, malformed or municipality-less faixa yields None rather than an error: a 404 is a clean answer, and no shape of this field should turn it into an Invalid_response.
Diffstat (limited to 'test')
-rw-r--r--test/test_cepis.ml67
1 files changed, 66 insertions, 1 deletions
diff --git a/test/test_cepis.ml b/test/test_cepis.ml
index cc98c4a..a1c575f 100644
--- a/test/test_cepis.ml
+++ b/test/test_cepis.ml
@@ -180,6 +180,15 @@ let is_error name predicate = function
| Ok _ -> check name false
| Error e -> check name (predicate e)
+let contains haystack needle =
+ let n = String.length haystack and m = String.length needle in
+ let rec go i =
+ if i + m > n then false
+ else if String.sub haystack i m = needle then true
+ else go (i + 1)
+ in
+ go 0
+
let test_normalisation () =
List.iter
(fun value ->
@@ -262,11 +271,65 @@ let test_not_found () =
with_server (fun srv client ->
respond srv "/99999999"
[ reply 404 {|{"erro":"cep_nao_encontrado","mensagem":"gone"}|} ];
+ let result = Cepis.Client.lookup client "99999999" in
is_error "404 becomes Cep_not_found"
(function Cepis.Cep_not_found _ -> true | _ -> false)
- (Cepis.Client.lookup client "99999999");
+ result;
+ is_error "a 404 without faixa yields None"
+ (function
+ | Cepis.Cep_not_found { faixa = None; _ } -> true | _ -> false)
+ result;
check "404 is not retried" (count srv "/99999999" = 1))
+let areal_404 =
+ {|{"erro":"cep_nao_encontrado","mensagem":"CEP não 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}}|}
+
+let test_not_found_with_faixa () =
+ with_server (fun srv client ->
+ respond srv "/25845000" [ reply 404 areal_404 ];
+ match Cepis.Client.lookup client "25845000" with
+ | Ok _ -> check "a faixa does not turn a 404 into an address" false
+ | Error (Cepis.Cep_not_found { faixa = Some f; _ } as e) ->
+ check "faixa names the municipality" (f.Cepis.localidade = "Areal");
+ check "faixa carries the uf" (f.Cepis.uf = "RJ");
+ check "faixa carries the ibge code" (f.Cepis.ibge = "3300225");
+ check "faixa carries the region" (f.Cepis.regiao = "Sudeste");
+ check "faixa bounds are the CEPs that exist, not the DNE's"
+ (f.Cepis.cep_ini = "25845001" && f.Cepis.cep_fim = "25849899");
+ check "faixa counts how sparse the range is" (f.Cepis.ceps = 118);
+ check "the rendered error mentions the municipality"
+ (contains (Cepis.string_of_error e) "Areal");
+ check "the rendered error still says not found"
+ (contains (Cepis.string_of_error e) "CEP not found")
+ | Error _ -> check "a 404 with faixa is still Cep_not_found" false)
+
+let test_not_found_faixa_is_defensive () =
+ with_server (fun srv client ->
+ List.iter
+ (fun (path, body, label) ->
+ respond srv path [ reply 404 body ];
+ is_error
+ (Printf.sprintf "%s yields no faixa, never a parse failure" label)
+ (function
+ | Cepis.Cep_not_found { faixa = None; _ } -> true | _ -> false)
+ (Cepis.Client.lookup client (String.sub path 1 8)))
+ [
+ ( "/25845002",
+ {|{"erro":"cep_nao_encontrado","faixa":null}|},
+ "an explicit null" );
+ ( "/25845003",
+ {|{"erro":"cep_nao_encontrado","faixa":{}}|},
+ "an empty object" );
+ ( "/25845004",
+ {|{"erro":"cep_nao_encontrado","faixa":"Areal"}|},
+ "a bare string" );
+ ( "/25845005",
+ {|{"erro":"cep_nao_encontrado","faixa":{"uf":"RJ","ceps":"muitos"}}|},
+ "a faixa with no municipality" );
+ ("/25845006", "<html>captive portal</html>", "a body that is not JSON");
+ ("/25845007", "", "an empty body");
+ ])
+
let test_local_validation_makes_no_request () =
with_server (fun srv client ->
is_error "malformed CEP is rejected locally"
@@ -544,6 +607,8 @@ let () =
test_malformed_geo ();
test_unknown_field ();
test_not_found ();
+ test_not_found_with_faixa ();
+ test_not_found_faixa_is_defensive ();
test_local_validation_makes_no_request ();
test_api_400 ();
test_not_json ();