summaryrefslogtreecommitdiffstats
path: root/internal/lookup/lookup_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lookup/lookup_test.go')
-rw-r--r--internal/lookup/lookup_test.go279
1 files changed, 0 insertions, 279 deletions
diff --git a/internal/lookup/lookup_test.go b/internal/lookup/lookup_test.go
deleted file mode 100644
index 6eb7b4e..0000000
--- a/internal/lookup/lookup_test.go
+++ /dev/null
@@ -1,279 +0,0 @@
-package lookup
-
-import (
- "context"
- "errors"
- "net/http"
- "net/http/httptest"
- "sync/atomic"
- "testing"
- "time"
-
- cepis "cep.is/go"
-)
-
-const areal404 = `{"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}}`
-
-const plain404 = `{"erro":"cep_nao_encontrado","mensagem":"CEP não existe na base do DNE"}`
-
-const address200 = `{"cep":"01001000","logradouro":"Praça da Sé","bairro":"Sé",
- "localidade":"São Paulo","uf":"SP","estado":"São Paulo","regiao":"Sudeste","ddd":"11"}`
-
-// fakeAPI answers every request with the same status and body, and counts how
-// many arrived: the count is what proves the extra request happens only on the
-// 404 path.
-type fakeAPI struct {
- server *httptest.Server
- requests atomic.Int64
-
- // delay is applied from the delayFrom-th request on, which is how the
- // second request — the one that reads the faixa — is made slow without
- // slowing the lookup that precedes it.
- delay time.Duration
- delayFrom int64
-}
-
-func newFakeAPI(t *testing.T, status int, body string) *fakeAPI {
- t.Helper()
- f := &fakeAPI{delayFrom: 1}
- f.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- n := f.requests.Add(1)
- if f.delay > 0 && n >= f.delayFrom {
- select {
- case <-time.After(f.delay):
- case <-r.Context().Done():
- return
- }
- }
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
- w.WriteHeader(status)
- w.Write([]byte(body))
- }))
- t.Cleanup(f.server.Close)
- return f
-}
-
-func newClient(t *testing.T, f *fakeAPI, opts ...Option) *Client {
- t.Helper()
- api, err := cepis.New(
- cepis.WithBaseURL(f.server.URL),
- cepis.WithMaxAttempts(1),
- cepis.WithTimeout(2*time.Second),
- )
- if err != nil {
- t.Fatalf("cepis.New: %v", err)
- }
- return New(api, opts...)
-}
-
-func TestNotFoundCarriesTheMunicipality(t *testing.T) {
- f := newFakeAPI(t, http.StatusNotFound, areal404)
- _, err := newClient(t, f).Lookup(context.Background(), "25845-000")
-
- if !errors.Is(err, cepis.ErrCEPNotFound) {
- t.Fatalf("err = %v, want a not-found error", err)
- }
- faixa := FaixaOf(err)
- if faixa == nil {
- t.Fatal("the faixa on the 404 was dropped")
- }
- if faixa.Place() != "Areal/RJ" {
- t.Fatalf("place = %q, want Areal/RJ", faixa.Place())
- }
- if faixa.CEPs != 118 {
- t.Fatalf("ceps = %d, want 118", faixa.CEPs)
- }
- if faixa.CEPIni != "25845001" || faixa.CEPFim != "25849899" {
- t.Fatalf("range = %s..%s, want 25845001..25849899", faixa.CEPIni, faixa.CEPFim)
- }
- if faixa.IBGE != "3300225" || faixa.Estado != "Rio de Janeiro" || faixa.Regiao != "Sudeste" {
- t.Fatalf("faixa lost fields: %+v", faixa)
- }
-}
-
-func TestNotFoundWithoutFaixaIsUntouched(t *testing.T) {
- f := newFakeAPI(t, http.StatusNotFound, plain404)
- _, err := newClient(t, f).Lookup(context.Background(), "99999999")
-
- if !errors.Is(err, cepis.ErrCEPNotFound) {
- t.Fatalf("err = %v, want a not-found error", err)
- }
- if faixa := FaixaOf(err); faixa != nil {
- t.Fatalf("invented a municipality: %+v", faixa)
- }
- var notFound *NotFoundError
- if errors.As(err, &notFound) {
- t.Fatal("a 404 without a faixa should come back as the library's own error")
- }
-}
-
-// A faixa the bot cannot trust must cost nothing: the reply falls back to the
-// plain one, and never to a panic or an unanswered message.
-func TestMalformedFaixaFallsBack(t *testing.T) {
- bodies := map[string]string{
- "empty body": ``,
- "not JSON": `<html>captive portal</html>`,
- "truncated": `{"erro":"cep_nao_encontrado","faixa":{"localidade":"Areal"`,
- "faixa is null": `{"erro":"cep_nao_encontrado","faixa":null}`,
- "faixa is a string": `{"erro":"cep_nao_encontrado","faixa":"Areal/RJ"}`,
- "faixa is an array": `{"erro":"cep_nao_encontrado","faixa":[{"localidade":"Areal"}]}`,
- "no localidade": `{"faixa":{"uf":"RJ","cep_ini":"25845001","cep_fim":"25849899"}}`,
- "blank localidade": `{"faixa":{"localidade":" ","uf":"RJ"}}`,
- "ceps is a string": `{"faixa":{"localidade":"Areal","uf":"RJ","ceps":"118"}}`,
- "whole body is null": `null`,
- "whole body is array": `[]`,
- }
-
- for name, body := range bodies {
- t.Run(name, func(t *testing.T) {
- f := newFakeAPI(t, http.StatusNotFound, body)
- _, err := newClient(t, f).Lookup(context.Background(), "25845000")
- if !errors.Is(err, cepis.ErrCEPNotFound) {
- t.Fatalf("err = %v, want a not-found error", err)
- }
- if faixa := FaixaOf(err); faixa != nil {
- t.Fatalf("trusted a broken faixa: %+v", faixa)
- }
- })
- }
-}
-
-// A negative count is nonsense the formatter would print verbatim, so it is
-// flattened to "unknown" while the municipality itself is kept.
-func TestNegativeCountIsDropped(t *testing.T) {
- f := newFakeAPI(t, http.StatusNotFound, `{"faixa":{"localidade":"Areal","uf":"RJ","ceps":-3}}`)
- _, err := newClient(t, f).Lookup(context.Background(), "25845000")
-
- faixa := FaixaOf(err)
- if faixa == nil {
- t.Fatal("the municipality should have survived a bad count")
- }
- if faixa.CEPs != 0 {
- t.Fatalf("ceps = %d, want 0", faixa.CEPs)
- }
-}
-
-func TestSuccessCostsOneRequest(t *testing.T) {
- f := newFakeAPI(t, http.StatusOK, address200)
- address, err := newClient(t, f).Lookup(context.Background(), "01001000")
-
- if err != nil {
- t.Fatalf("Lookup: %v", err)
- }
- if address.Localidade != "São Paulo" {
- t.Fatalf("localidade = %q", address.Localidade)
- }
- if got := f.requests.Load(); got != 1 {
- t.Fatalf("the API saw %d requests for a hit, want 1", got)
- }
-}
-
-func TestOtherFailuresAskOnlyOnce(t *testing.T) {
- f := newFakeAPI(t, http.StatusInternalServerError, `{"erro":"interno"}`)
- _, err := newClient(t, f).Lookup(context.Background(), "50000000")
-
- if !errors.Is(err, cepis.ErrServer) {
- t.Fatalf("err = %v, want a server error", err)
- }
- if got := f.requests.Load(); got != 1 {
- t.Fatalf("the API saw %d requests for a 500, want 1", got)
- }
-}
-
-// The faixa is worth one short wait and no more. A slow API still gets an
-// answer out to the user, without the municipality.
-func TestSlowFaixaIsAbandoned(t *testing.T) {
- f := newFakeAPI(t, http.StatusNotFound, areal404)
- client := newClient(t, f, WithTimeout(20*time.Millisecond))
- f.delay, f.delayFrom = 500*time.Millisecond, 2
-
- started := time.Now()
- _, err := client.Lookup(context.Background(), "25845000")
- elapsed := time.Since(started)
-
- if !errors.Is(err, cepis.ErrCEPNotFound) {
- t.Fatalf("err = %v, want a not-found error", err)
- }
- if faixa := FaixaOf(err); faixa != nil {
- t.Fatalf("used a faixa that arrived too late: %+v", faixa)
- }
- if elapsed > 400*time.Millisecond {
- t.Fatalf("waited %v for the faixa, want it abandoned quickly", elapsed)
- }
-}
-
-// A CEP that stopped being a 404 between the two requests is not a
-// municipality: the second answer is ignored rather than mixed into the first.
-func TestFaixaIgnoresANonNotFoundSecondAnswer(t *testing.T) {
- var count atomic.Int64
- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/json; charset=utf-8")
- if count.Add(1) == 1 {
- w.WriteHeader(http.StatusNotFound)
- w.Write([]byte(plain404))
- return
- }
- w.WriteHeader(http.StatusOK)
- w.Write([]byte(address200))
- }))
- t.Cleanup(server.Close)
-
- api, err := cepis.New(cepis.WithBaseURL(server.URL), cepis.WithMaxAttempts(1))
- if err != nil {
- t.Fatalf("cepis.New: %v", err)
- }
-
- _, err = New(api).Lookup(context.Background(), "25845000")
- if !errors.Is(err, cepis.ErrCEPNotFound) {
- t.Fatalf("err = %v, want a not-found error", err)
- }
- if faixa := FaixaOf(err); faixa != nil {
- t.Fatalf("read a municipality out of a 200: %+v", faixa)
- }
-}
-
-func TestFaixaOfAcceptsAnything(t *testing.T) {
- for name, err := range map[string]error{
- "nil": nil,
- "unrelated": errors.New("boom"),
- "not found": cepis.ErrCEPNotFound,
- "no faixa": &NotFoundError{Err: cepis.ErrCEPNotFound},
- "wrapped": &NotFoundError{Faixa: &Faixa{Localidade: "Areal", UF: "RJ"}, Err: cepis.ErrCEPNotFound},
- "empty type": &NotFoundError{},
- } {
- if name == "wrapped" {
- if got := FaixaOf(err); got == nil || got.Place() != "Areal/RJ" {
- t.Fatalf("%s: FaixaOf = %+v", name, got)
- }
- continue
- }
- if got := FaixaOf(err); got != nil {
- t.Fatalf("%s: FaixaOf = %+v, want nil", name, got)
- }
- }
-}
-
-// The zero value still matches the sentinel, so no construction of this type
-// can ever produce an error the bot fails to recognise as a not-found.
-func TestEmptyNotFoundErrorStillMatches(t *testing.T) {
- var err error = &NotFoundError{}
- if !errors.Is(err, cepis.ErrCEPNotFound) {
- t.Fatal("an empty NotFoundError should still be a not-found")
- }
- if err.Error() == "" {
- t.Fatal("an empty NotFoundError should still say something")
- }
-}
-
-func TestPlaceWithoutUF(t *testing.T) {
- faixa := &Faixa{Localidade: "Areal"}
- if faixa.Place() != "Areal" {
- t.Fatalf("place = %q, want Areal", faixa.Place())
- }
- var missing *Faixa
- if missing.Place() != "" {
- t.Fatalf("place = %q, want empty", missing.Place())
- }
-}