summaryrefslogtreecommitdiffstats
path: root/internal/app
diff options
context:
space:
mode:
Diffstat (limited to 'internal/app')
-rw-r--r--internal/app/app.go11
-rw-r--r--internal/app/app_test.go50
-rw-r--r--internal/app/fakes_test.go20
3 files changed, 77 insertions, 4 deletions
diff --git a/internal/app/app.go b/internal/app/app.go
index 0b78baf..ccb8483 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -15,6 +15,7 @@ import (
"cep.is/telegrambot/internal/bot"
"cep.is/telegrambot/internal/config"
+ "cep.is/telegrambot/internal/lookup"
"cep.is/telegrambot/internal/telegram"
)
@@ -34,22 +35,28 @@ type App struct {
// The whole-call timeout of the cep.is client is the shutdown grace on purpose:
// handling one update is what a termination signal waits for, so a lookup must
// not be allowed to outlast that budget.
+//
+// The client goes to the bot wrapped in [lookup.Client], which is what keeps the
+// municipality the API names on a 404. The wrapper has its own short timeout,
+// deliberately far below the grace: it is a footnote on an answer that is
+// already writable.
func New(ctx context.Context, cfg config.Config, logger *slog.Logger) (*App, error) {
if logger == nil {
logger = slog.Default()
}
- lookups, err := cepis.New(
+ cepisClient, err := cepis.New(
cepis.WithBaseURL(cfg.CepisBaseURL),
cepis.WithTimeout(cfg.RequestTimeout),
cepis.WithMaxAttempts(cfg.MaxAttempts),
cepis.WithBackoff(cfg.BackoffBase, cfg.BackoffMax),
cepis.WithTotalTimeout(cfg.ShutdownGrace),
- cepis.WithUserAgent("cepis-telegram/0.1.0 (+https://cep.is)"),
+ cepis.WithUserAgent("cepis-telegram/0.2.0 (+https://cep.is)"),
)
if err != nil {
return nil, fmt.Errorf("app: cep.is client: %w", err)
}
+ lookups := lookup.New(cepisClient)
api, err := telegram.New(cfg.Token,
telegram.WithBaseURL(cfg.TelegramAPIBaseURL),
diff --git a/internal/app/app_test.go b/internal/app/app_test.go
index e770060..d430e11 100644
--- a/internal/app/app_test.go
+++ b/internal/app/app_test.go
@@ -291,6 +291,8 @@ func TestMalformedCEP(t *testing.T) {
}
}
+// A CEP in a numeric band the Correios never handed to any municipality: the
+// 404 carries no faixa, and the answer is the one the bot has always given.
func TestCEPNotFound(t *testing.T) {
tg, api := newFakeTelegram(t), newFakeCEPIS(t)
tg.push(privateMessage(1, 555, "99999999"))
@@ -300,6 +302,54 @@ func TestCEPNotFound(t *testing.T) {
if !strings.Contains(sent[0].Text, "não encontrado") {
t.Fatalf("reply = %q, want a not-found message", sent[0].Text)
}
+ if strings.Contains(sent[0].Text, "cairia em") {
+ t.Fatalf("named a municipality the API did not send: %q", sent[0].Text)
+ }
+}
+
+// The "-000" guess for Areal/RJ, which never received that number. The reply
+// still says the CEP does not exist, and adds where it would have been.
+func TestCEPNotFoundNamesTheMunicipality(t *testing.T) {
+ tg, api := newFakeTelegram(t), newFakeCEPIS(t)
+ tg.push(privateMessage(1, 555, "25845-000"))
+ start(t, testConfig(tg, api))
+
+ sent := waitForMessages(t, tg, 1)
+ text := sent[0].Text
+ for _, needed := range []string{
+ "não encontrado", "Areal/RJ", "118 CEPs", "25845-001", "25849-899", "esparsa",
+ } {
+ if !strings.Contains(text, needed) {
+ t.Fatalf("reply is missing %q: %q", needed, text)
+ }
+ }
+ if strings.Contains(text, "📮") {
+ t.Fatalf("a failure must not be formatted as a hit: %q", text)
+ }
+ if strings.Contains(text, "25845-000") {
+ t.Fatalf("the reply hands back the CEP that just failed: %q", text)
+ }
+}
+
+func TestInlineNotFoundNamesTheMunicipality(t *testing.T) {
+ tg, api := newFakeTelegram(t), newFakeCEPIS(t)
+ tg.push(inlineQuery(1, 555, "25845000"))
+ start(t, testConfig(tg, api))
+
+ waitFor(t, "inline answer", 10*time.Second, func() bool { return len(tg.inlineAnswers()) >= 1 })
+ results := tg.inlineAnswers()[0].Results
+ if len(results) != 1 {
+ t.Fatalf("got %d results, want 1", len(results))
+ }
+ if results[0].Title != "CEP não encontrado" {
+ t.Fatalf("title = %q", results[0].Title)
+ }
+ if !strings.Contains(results[0].Description, "Areal/RJ") {
+ t.Fatalf("description = %q, want the municipality named", results[0].Description)
+ }
+ if !strings.Contains(results[0].InputMessageContent.MessageText, "118 CEPs") {
+ t.Fatalf("message = %q", results[0].InputMessageContent.MessageText)
+ }
}
func TestAPIServerError(t *testing.T) {
diff --git a/internal/app/fakes_test.go b/internal/app/fakes_test.go
index 011d4af..9281a53 100644
--- a/internal/app/fakes_test.go
+++ b/internal/app/fakes_test.go
@@ -182,12 +182,16 @@ func (f *fakeTelegram) pendingCount() int {
// fakeCEPIS is a real HTTP server speaking the cep.is native contract, with
// enough of it to exercise every branch the bot has: an address with a street
-// coordinate, one that is a whole municipality, a 404, a 400 and a 500.
+// coordinate, one that is a whole municipality, a 404 that names the
+// municipality the CEP would fall in, a 404 that does not, a 400 and a 500.
type fakeCEPIS struct {
server *httptest.Server
- mu sync.Mutex
+ mu sync.Mutex
+ // bodies are the 200s, missing are the 404s that carry a faixa, and
+ // statuses forces a status for a CEP regardless of the other two.
bodies map[string]string
+ missing map[string]string
statuses map[string]int
requests int
}
@@ -208,6 +212,14 @@ func newFakeCEPIS(t *testing.T) *fakeCEPIS {
`"unidade":"","bairro":"Centro","localidade":"Linhares","uf":"ES","estado":"Espírito Santo",` +
`"regiao":"Sudeste","ibge":"3203205","gia":"","ddd":"27","siafi":"5665"}`,
},
+ // 25845000 is the CEP the "-000" guess produces for Areal/RJ, which
+ // is coded street by street and never received that number: the
+ // municipality exists, the CEP does not.
+ missing: map[string]string{
+ "25845000": `{"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}}`,
+ },
statuses: map[string]int{
"50000000": http.StatusInternalServerError,
},
@@ -225,6 +237,7 @@ func (f *fakeCEPIS) handle(w http.ResponseWriter, r *http.Request) {
cep := strings.TrimPrefix(r.URL.Path, "/")
status, forced := f.statuses[cep]
body, known := f.bodies[cep]
+ absent, attributed := f.missing[cep]
f.mu.Unlock()
w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -235,6 +248,9 @@ func (f *fakeCEPIS) handle(w http.ResponseWriter, r *http.Request) {
case known:
w.WriteHeader(http.StatusOK)
w.Write([]byte(body))
+ case attributed:
+ w.WriteHeader(http.StatusNotFound)
+ w.Write([]byte(absent))
case len(cep) != 8:
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"erro":"cep_invalido","mensagem":"CEP deve ter 8 dígitos"}`))