defmodule Cepis.LookupTest do use Cepis.ServerCase test "resolves a CEP", %{server: server, client: client} do FakeServer.respond(server, "/01001000", {200, json(address_fixture()), []}) assert {:ok, address} = Cepis.lookup("01001-000", client) assert address.logradouro == "Praça da Sé" assert address.localidade == "São Paulo" assert address.uf == "SP" assert Cepis.Address.formatted_cep(address) == "01001-000" assert address.geo == nil end test "reads the coordinate when the API sends one", %{server: server, client: client} do body = address_fixture(%{ "geo" => %{"lat" => -23.5, "lon" => -46.6, "raio_m" => 120, "precisao" => "logradouro"} }) FakeServer.respond(server, "/01310100", {200, json(body), []}) assert {:ok, address} = Cepis.lookup("01310100", client) assert address.geo.lat == -23.5 assert address.geo.lon == -46.6 assert address.geo.raio_m == 120 assert Cepis.Geo.precise?(address.geo) end test "a municipality centroid is not precise", %{server: server, client: client} do body = address_fixture(%{ "geo" => %{"lat" => -23.5, "lon" => -46.6, "raio_m" => 22_000, "precisao" => "localidade"} }) FakeServer.respond(server, "/01001000", {200, json(body), []}) assert {:ok, address} = Cepis.lookup("01001000", client) refute Cepis.Geo.precise?(address.geo) end test "a malformed geo block does not lose the address", %{server: server, client: client} do body = address_fixture(%{ "geo" => %{ "lat" => "not a number", "lon" => "-46.6", "raio_m" => "120", "precisao" => "logradouro" } }) FakeServer.respond(server, "/01001000", {200, json(body), []}) assert {:ok, address} = Cepis.lookup("01001000", client) assert address.geo == nil assert address.localidade == "São Paulo" end test "a truncated geo block does not lose the address", %{server: server, client: client} do FakeServer.respond(server, "/01001000", {200, json(address_fixture(%{"geo" => %{}})), []}) assert {:ok, address} = Cepis.lookup("01001000", client) assert address.geo == nil assert address.uf == "SP" end test "a field the API grows is kept in raw", %{server: server, client: client} do body = address_fixture(%{"campo_do_futuro" => "value"}) FakeServer.respond(server, "/01001000", {200, json(body), []}) assert {:ok, address} = Cepis.lookup("01001000", client) assert address.raw["campo_do_futuro"] == "value" assert address.localidade == "São Paulo" end test "a missing CEP is a not-found error", %{server: server, client: client} do FakeServer.respond( server, "/99999999", {404, json(%{"erro" => "cep_nao_encontrado", "mensagem" => "gone"}), []} ) assert {:error, %Cepis.NotFoundError{cep: "99999999"} = error} = Cepis.lookup("99999999", client) assert Exception.message(error) =~ "99999999" end test "a not-found names the municipality the CEP would belong to", %{ server: server, client: client } do body = not_found_fixture(%{"faixa" => faixa_fixture()}) FakeServer.respond(server, "/25845000", {404, json(body), []}) assert {:error, %Cepis.NotFoundError{cep: "25845000", faixa: faixa} = error} = Cepis.lookup("25845-000", client) assert faixa.localidade == "Areal" assert faixa.uf == "RJ" assert faixa.estado == "Rio de Janeiro" assert faixa.regiao == "Sudeste" assert faixa.ibge == "3300225" assert faixa.cep_ini == "25845001" assert faixa.cep_fim == "25849899" assert faixa.ceps == 118 assert Exception.message(error) =~ "Areal/RJ" assert Exception.message(error) =~ "25845000 does not exist" end test "a not-found in a band no municipality owns has no faixa", %{ server: server, client: client } do FakeServer.respond(server, "/99999999", {404, json(not_found_fixture()), []}) assert {:error, %Cepis.NotFoundError{faixa: nil} = error} = Cepis.lookup("99999999", client) assert Exception.message(error) == "CEP 99999999 does not exist in the DNE database" end test "a malformed faixa does not become a parse failure", %{server: server, client: client} do bodies = [ not_found_fixture(%{"faixa" => %{}}), not_found_fixture(%{"faixa" => %{"uf" => "RJ"}}), not_found_fixture(%{"faixa" => %{"localidade" => ""}}), not_found_fixture(%{"faixa" => "Areal"}), # the API omits the field rather than nulling it, but a null must not # cost the caller the error either not_found_fixture(%{"faixa" => :null}) ] for {body, index} <- Enum.with_index(bodies) do path = "/2584500#{index}" FakeServer.respond(server, path, {404, json(body), []}) assert {:error, %Cepis.NotFoundError{faixa: nil}} = Cepis.lookup(String.trim_leading(path, "/"), client) end end test "a 404 body that is not JSON is still a clean not-found", %{ server: server, client: client } do FakeServer.respond(server, "/99999999", {404, "gateway", []}) assert {:error, %Cepis.NotFoundError{cep: "99999999", faixa: nil}} = Cepis.lookup("99999999", client) end test "a faixa keeps whatever the API sends, even partially", %{server: server, client: client} do body = not_found_fixture(%{"faixa" => %{"localidade" => "Areal", "ceps" => "118"}}) FakeServer.respond(server, "/25845000", {404, json(body), []}) assert {:error, %Cepis.NotFoundError{faixa: faixa}} = Cepis.lookup("25845000", client) assert faixa.localidade == "Areal" assert faixa.ceps == 118 assert faixa.cep_ini == "" assert Cepis.Faixa.density(faixa) == 0.0 end test "a 404 is never retried", %{server: server, client: client} do FakeServer.respond(server, "/99999999", {404, json(%{"erro" => "cep_nao_encontrado"}), []}) assert {:error, %Cepis.NotFoundError{}} = Cepis.lookup("99999999", client) assert FakeServer.count(server, "/99999999") == 1 end test "a malformed CEP never leaves the machine", %{server: server, client: client} do assert {:error, %Cepis.InvalidCEPError{}} = Cepis.lookup("123", client) assert FakeServer.calls(server) == [] end test "a 400 carries the API's own message", %{server: server, client: client} do FakeServer.respond( server, "/00000000", {400, json(%{"erro" => "cep_invalido", "mensagem" => "rejected by the API"}), []} ) assert {:error, %Cepis.InvalidCEPError{} = error} = Cepis.lookup("00000000", client) assert Exception.message(error) =~ "rejected by the API" end test "a 400 is never retried", %{server: server, client: client} do FakeServer.respond(server, "/00000000", {400, json(%{"erro" => "cep_invalido"}), []}) assert {:error, %Cepis.InvalidCEPError{}} = Cepis.lookup("00000000", client) assert FakeServer.count(server, "/00000000") == 1 end test "a captive portal answering instead of the API is diagnosed", %{ server: server, client: client } do FakeServer.respond(server, "/01001000", {200, "captive portal", []}) assert {:error, %Cepis.InvalidResponseError{} = error} = Cepis.lookup("01001000", client) assert Exception.message(error) =~ "captive portal" end test "a JSON body that is not an object is rejected", %{server: server, client: client} do FakeServer.respond(server, "/01001000", {200, "[1,2,3]", []}) assert {:error, %Cepis.InvalidResponseError{}} = Cepis.lookup("01001000", client) end test "the bang variant returns the address and raises on failure", %{ server: server, client: client } do FakeServer.respond(server, "/01001000", {200, json(address_fixture()), []}) assert %Cepis.Address{uf: "SP"} = Cepis.lookup!("01001000", client) assert_raise Cepis.NotFoundError, fn -> Cepis.lookup!("99999999", client) end end test "keyword options work in place of a client struct", %{server: server} do FakeServer.respond(server, "/01001000", {200, json(address_fixture()), []}) assert {:ok, address} = Cepis.lookup("01001000", base_url: server.url, timeout: 5_000) assert address.localidade == "São Paulo" end end