Phase 2c — testing infrastructure (43 tests, CI gating, docker-compose)
Some checks failed
Docker Build / build-and-push-image (push) Has been cancelled
Some checks failed
Docker Build / build-and-push-image (push) Has been cancelled
Brings the project to a TDD/BDD-friendly state — apologies for shipping Phase 1.5 + Phase 2 code-first, that violated feedback_tdd_first_bdd_required. What's added : - helpers_test.go : FakeTelegram (httptest server that records sendMessage / deleteMessage / setWebhook / etc.), miniredis bootstrap, MakeUpdate / PostWebhook helpers. The same harness simulates 'a user DMing the bot' end-to-end without hitting Telegram cloud — answer to the user question. - 43 tests covering : allowlist parsing, telegram type helpers (UserID / ChatID / Text / messageID), secret_token constant-time compare, Backoff schedule, Auth (login wrong/right/logout/TTL/nil-receiver), EchoHandler, HTTPHandler (forward / timeout / non-2xx / empty body), AuthHandler (start / auth / whoami / logout / replay defense delete), Server (bad secret 401, unknown bot 404, allowlist drop, gated bot prompt, full /auth → echo → /logout flow, healthz/readyz). - All tests pass with -race in 1.6s, no external deps (miniredis + httptest in-process). Infra : - Updated .gitea/workflows/dockerimage.yaml : new 'test' job (go vet + go test -race) gates the build-and-push-image job. CI now also runs on pull_request. - docker-compose.yml : redis + postgres for full local stack. - Makefile : test-race, compose-up/down targets. - README updated with test + local-dev sections. Refs ~/.claude/plans/pour-les-notifications-on-inherited-seal.md § Phase 2.
This commit is contained in:
117
handler_auth_test.go
Normal file
117
handler_auth_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const testAuthCode = "topsecret"
|
||||
|
||||
func setupAuthHandler(t *testing.T) (*FakeTelegram, *AuthHandler, Bot) {
|
||||
t.Helper()
|
||||
ft := NewFakeTelegram(t)
|
||||
m := StartMiniRedis(t)
|
||||
a := NewTestAuth(t, m, testAuthCode, time.Hour)
|
||||
h := &AuthHandler{tg: ft.Client(), auth: a}
|
||||
bot := Bot{Slug: "factory", Token: "tok", Secret: "sec"}
|
||||
return ft, h, bot
|
||||
}
|
||||
|
||||
func TestAuthHandler_StartShowsHelp(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(1, 100, 100, 1, "/start"), bot)
|
||||
sent := ft.Sent()
|
||||
if len(sent) != 1 || !strings.Contains(sent[0].Text, "/auth <code>") {
|
||||
t.Fatalf("/start should welcome + show help, got %+v", sent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_AuthMissingCode(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(1, 100, 100, 1, "/auth"), bot)
|
||||
sent := ft.Sent()
|
||||
if len(sent) != 1 || !strings.Contains(sent[0].Text, "Usage") {
|
||||
t.Fatalf("bare /auth should print Usage, got %+v", sent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_AuthWrongCode_NoSession(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(1, 100, 100, 1, "/auth wrong"), bot)
|
||||
sent := ft.Sent()
|
||||
if len(sent) != 1 || !strings.Contains(sent[0].Text, "Mauvais code") {
|
||||
t.Fatalf("wrong code should reply error, got %+v", sent)
|
||||
}
|
||||
authed, _ := h.auth.IsAuthed(context.Background(), 100)
|
||||
if authed {
|
||||
t.Fatal("session must NOT be created on wrong code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_AuthRightCode_SessionAndDelete(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
upd := MakeUpdate(1, 100, 100, 42, "/auth "+testAuthCode)
|
||||
_ = h.Handle(bgCtx(), upd, bot)
|
||||
|
||||
sent := ft.Sent()
|
||||
if len(sent) != 1 || !strings.Contains(sent[0].Text, "Authentifié") {
|
||||
t.Fatalf("right code should welcome, got %+v", sent)
|
||||
}
|
||||
deleted := ft.Deleted()
|
||||
if len(deleted) != 1 || deleted[0].MessageID != 42 || deleted[0].ChatID != 100 {
|
||||
t.Fatalf("expected deleteMessage on the /auth message (id=42 chat=100), got %+v", deleted)
|
||||
}
|
||||
authed, _ := h.auth.IsAuthed(context.Background(), 100)
|
||||
if !authed {
|
||||
t.Fatal("session must exist after right code")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Whoami_NotAuthed(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(1, 200, 200, 1, "/whoami"), bot)
|
||||
sent := ft.Sent()
|
||||
if len(sent) != 1 || !strings.Contains(sent[0].Text, "non authentifié") {
|
||||
t.Fatalf("whoami without session should say so, got %+v", sent)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Whoami_Authed_ShowsTTL(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(1, 300, 300, 1, "/auth "+testAuthCode), bot)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(2, 300, 300, 2, "/whoami"), bot)
|
||||
sent := ft.Sent()
|
||||
last := sent[len(sent)-1].Text
|
||||
if !strings.Contains(last, "authentifié") || !strings.Contains(last, "user=300") {
|
||||
t.Fatalf("whoami auth answer wrong: %q", last)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Logout(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(1, 400, 400, 1, "/auth "+testAuthCode), bot)
|
||||
authed, _ := h.auth.IsAuthed(context.Background(), 400)
|
||||
if !authed {
|
||||
t.Fatal("setup: should be authed")
|
||||
}
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(2, 400, 400, 2, "/logout"), bot)
|
||||
authed, _ = h.auth.IsAuthed(context.Background(), 400)
|
||||
if authed {
|
||||
t.Fatal("logout should drop the session")
|
||||
}
|
||||
last := ft.Sent()[len(ft.Sent())-1].Text
|
||||
if !strings.Contains(last, "Déconnecté") {
|
||||
t.Fatalf("logout reply wrong: %q", last)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthHandler_Default_PrintsHelp(t *testing.T) {
|
||||
ft, h, bot := setupAuthHandler(t)
|
||||
_ = h.Handle(bgCtx(), MakeUpdate(1, 500, 500, 1, "salut !"), bot)
|
||||
last := ft.Sent()[0].Text
|
||||
if !strings.Contains(last, "Commandes disponibles") {
|
||||
t.Fatalf("default reply should print help, got %q", last)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user