summaryrefslogtreecommitdiffstats
path: root/Tests/CepisTests
diff options
context:
space:
mode:
Diffstat (limited to 'Tests/CepisTests')
-rw-r--r--Tests/CepisTests/CepisTests.swift165
1 files changed, 163 insertions, 2 deletions
diff --git a/Tests/CepisTests/CepisTests.swift b/Tests/CepisTests/CepisTests.swift
index 46699bc..deabead 100644
--- a/Tests/CepisTests/CepisTests.swift
+++ b/Tests/CepisTests/CepisTests.swift
@@ -14,6 +14,14 @@ let seAddressJSON = """
"ddd":"11","siafi":"7107"}
"""
+/// The 404 for `25845000`: the CEP the DNE itself lists as Areal's first, and
+/// which nonetheless does not exist.
+let arealNotFoundJSON = """
+ {"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 csvHeader =
"cep,logradouro,complemento,unidade,bairro,localidade,uf,estado,regiao,ibge,gia,ddd,siafi"
@@ -65,7 +73,7 @@ final class NormalisationTests: XCTestCase {
func testInvalidCEPIsNotRetriable() {
XCTAssertFalse(CepisError.invalidCEP("x").isRetriable)
- XCTAssertFalse(CepisError.cepNotFound("x").isRetriable)
+ XCTAssertFalse(CepisError.cepNotFound(message: "x", faixa: nil).isRetriable)
XCTAssertTrue(CepisError.networkError(message: "x").isRetriable)
}
}
@@ -142,8 +150,9 @@ final class LookupTests: ServerBackedTestCase {
do {
_ = try await client.lookup("99999999")
XCTFail("expected cepNotFound")
- } catch CepisError.cepNotFound(let message) {
+ } catch CepisError.cepNotFound(let message, let faixa) {
XCTAssertTrue(message.contains("99999999"))
+ XCTAssertNil(faixa)
}
}
@@ -188,6 +197,158 @@ final class LookupTests: ServerBackedTestCase {
}
}
+final class NotFoundFaixaTests: ServerBackedTestCase {
+ /// Serves `body` as the 404 for `cep` and returns the faixa that survived.
+ private func faixaFromNotFound(_ body: String, cep: String = "25845000") async throws -> Faixa?
+ {
+ routes.respond("/\(cep)", status: 404, body: body)
+ do {
+ _ = try await client.lookup(cep)
+ XCTFail("expected cepNotFound")
+ return nil
+ } catch CepisError.cepNotFound(_, let faixa) {
+ return faixa
+ }
+ }
+
+ func testNamesTheMunicipalityTheCEPWouldBelongTo() async throws {
+ let decoded = try await faixaFromNotFound(arealNotFoundJSON)
+ let faixa = try XCTUnwrap(decoded)
+ XCTAssertEqual(faixa.localidade, "Areal")
+ XCTAssertEqual(faixa.uf, "RJ")
+ XCTAssertEqual(faixa.estado, "Rio de Janeiro")
+ XCTAssertEqual(faixa.regiao, "Sudeste")
+ XCTAssertEqual(faixa.ibge, "3300225")
+ XCTAssertEqual(faixa.localidadeUF, "Areal/RJ")
+ }
+
+ /// The faixa decorates the 404; it must never promote it to a success.
+ func testStillThrowsAndReturnsNoAddress() async throws {
+ routes.respond("/25845000", status: 404, body: arealNotFoundJSON)
+ do {
+ let address = try await client.lookup("25845000")
+ XCTFail("a faixa must not turn a not-found into \(address)")
+ } catch CepisError.cepNotFound(let message, let faixa) {
+ XCTAssertTrue(message.contains("25845000"))
+ XCTAssertTrue(message.contains("does not exist"))
+ XCTAssertNotNil(faixa)
+ }
+ }
+
+ /// `cep_ini` is the first CEP that *exists*, not the DNE's published start
+ /// — which for Areal is `25845000`, the very CEP that just failed.
+ func testBoundsAreTheCEPsThatExistNotTheDNEStart() async throws {
+ let decoded = try await faixaFromNotFound(arealNotFoundJSON)
+ let faixa = try XCTUnwrap(decoded)
+ XCTAssertEqual(faixa.cepIni, "25845001")
+ XCTAssertEqual(faixa.cepFim, "25849899")
+ XCTAssertNotEqual(faixa.cepIni, "25845000")
+ }
+
+ /// The pair is not a list: 118 CEPs across ~4,900 numbers.
+ func testRangeIsSparse() async throws {
+ let decoded = try await faixaFromNotFound(arealNotFoundJSON)
+ let faixa = try XCTUnwrap(decoded)
+ XCTAssertEqual(faixa.ceps, 118)
+
+ let first = try XCTUnwrap(Int(faixa.cepIni))
+ let last = try XCTUnwrap(Int(faixa.cepFim))
+ let span = last - first + 1
+ XCTAssertEqual(span, 4899)
+ XCTAssertLessThan(faixa.ceps, span / 10)
+ }
+
+ func testAbsentWhenNoMunicipalityOwnsTheBand() async throws {
+ let body = #"{"erro":"cep_nao_encontrado","mensagem":"CEP não existe na base do DNE"}"#
+ let decoded = try await faixaFromNotFound(body)
+ XCTAssertNil(decoded)
+ }
+
+ func testNullFaixaIsNil() async throws {
+ let decoded = try await faixaFromNotFound(#"{"erro":"cep_nao_encontrado","faixa":null}"#)
+ XCTAssertNil(decoded)
+ }
+
+ func testEmptyFaixaIsNil() async throws {
+ let decoded = try await faixaFromNotFound(#"{"erro":"cep_nao_encontrado","faixa":{}}"#)
+ XCTAssertNil(decoded)
+ }
+
+ func testFaixaWithoutAMunicipalityIsNil() async throws {
+ for value in [#""""#, #"" ""#, "123", "null", "[]"] {
+ let body = #"{"erro":"cep_nao_encontrado","faixa":{"localidade":\#(value),"uf":"RJ"}}"#
+ let decoded = try await faixaFromNotFound(body)
+ XCTAssertNil(decoded, "localidade \(value)")
+ }
+ }
+
+ func testMalformedFaixaDoesNotBecomeADecodingFailure() async throws {
+ let bodies = [
+ #"{"erro":"cep_nao_encontrado","faixa":"Areal"}"#,
+ #"{"erro":"cep_nao_encontrado","faixa":[{"localidade":"Areal"}]}"#,
+ #"{"erro":"cep_nao_encontrado","faixa":{"localidade":{"nome":"Areal"}}}"#,
+ ]
+ for body in bodies {
+ let decoded = try await faixaFromNotFound(body)
+ XCTAssertNil(decoded, body)
+ }
+ }
+
+ /// A 404 body that is not JSON at all is still a clean not-found, never an
+ /// `invalidResponse`.
+ func testNonJSONBodyStillYieldsNotFound() async throws {
+ for body in ["<html>captive portal</html>", ""] {
+ let decoded = try await faixaFromNotFound(body)
+ XCTAssertNil(decoded, body)
+ }
+ }
+
+ func testToleratesOddFieldTypesAroundTheMunicipality() async throws {
+ let body = """
+ {"erro":"cep_nao_encontrado","faixa":{"localidade":"Areal","uf":"RJ",
+ "ibge":3300225,"cep_ini":"25845001","ceps":118.0}}
+ """
+ let decoded = try await faixaFromNotFound(body)
+ let faixa = try XCTUnwrap(decoded)
+ XCTAssertEqual(faixa.localidade, "Areal")
+ XCTAssertEqual(faixa.ceps, 118)
+ XCTAssertEqual(faixa.ibge, "")
+ XCTAssertEqual(faixa.cepFim, "")
+ }
+
+ func testDescriptionMentionsTheMunicipality() async throws {
+ routes.respond("/25845000", status: 404, body: arealNotFoundJSON)
+ do {
+ _ = try await client.lookup("25845000")
+ XCTFail("expected cepNotFound")
+ } catch let error as CepisError {
+ XCTAssertTrue(error.description.contains("Areal/RJ"), error.description)
+ XCTAssertEqual(error.errorDescription, error.description)
+ XCTAssertEqual(error.faixa?.localidade, "Areal")
+ // `message` stays what it always was, for callers matching on it.
+ XCTAssertFalse(error.message.contains("Areal"))
+ }
+ }
+
+ func testDescriptionWithoutAFaixaIsJustTheMessage() {
+ let error = CepisError.cepNotFound(message: "CEP 99999999 does not exist", faixa: nil)
+ XCTAssertEqual(error.description, error.message)
+ XCTAssertEqual(error.errorDescription, error.message)
+ XCTAssertNil(error.faixa)
+ }
+
+ func testFaixaAccessorIsNilForTheOtherCases() {
+ XCTAssertNil(CepisError.invalidCEP("x").faixa)
+ XCTAssertNil(CepisError.networkError(message: "x").faixa)
+ XCTAssertNil(CepisError.serverError(message: "x", status: 500).faixa)
+ }
+
+ func testAFaixaIsStillNotRetriable() {
+ let faixa = Faixa(localidade: "Areal", uf: "RJ")
+ XCTAssertFalse(CepisError.cepNotFound(message: "x", faixa: faixa).isRetriable)
+ }
+}
+
final class RetryTests: ServerBackedTestCase {
func testRetriesOn500ThenSucceeds() async throws {
routes.respond(