This was supposed to land in d63f195 but the prior Write didn't apply.
CI now runs unit + integration tests on every push and PR ; the docker
image is only pushed on main, after tests pass.
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.
Adds the async dispatch infrastructure :
- Postgres pool + embedded migration (CREATE TABLE/INDEX IF NOT EXISTS
gateway_jobs). Auto-applied at boot. lib/pq driver (matches webapp
convention).
- queue.go : Enqueue (idempotent on UNIQUE(bot_slug, update_id) — handles
Telegram redelivery), Pop with FOR UPDATE SKIP LOCKED, MarkDone,
MarkFailed with exponential backoff (30s → 2m → 10m → 1h → dead at 5).
- worker.go : goroutine that drains the queue, dispatches via the same
Handler interface as sync, schedules retries on failure, notifies the
user once when a job goes to dead.
- BotConfig gains `async: bool`. Registry refuses bots with async=true
if DATABASE_URL is unset (queue=nil).
- Server : when bot.Async, the webhook ack is immediate ; the update
payload is enqueued for the worker.
When DATABASE_URL is unset (current default), queue/worker stay disabled
and only sync handlers (echo, http, auth) work — no breaking change to
the running cluster.
Refs ~/.claude/plans/pour-les-notifications-on-inherited-seal.md § Phase 2.
The http handler POSTs the Telegram Update JSON to a configurable
internal URL and expects a JSON {text} reply, which it sends back via
sendMessage. Sync : the webhook ack waits for the upstream answer
(timeout default 5s, capped at 30s — Telegram itself closes around 60s).
For slow / unreliable backends use the Phase 3 async handlers once the
queue is in place.
YAML config :
bots:
webappbot:
handler: http
http:
url: http://webapp.webapp.svc.cluster.local:8080/telegram/update
timeout: 5s
Refs ~/.claude/plans/pour-les-notifications-on-inherited-seal.md § Phase 2.
Adds an authentication layer in front of the bot handlers :
- Auth handler on the principal bot (@arcodange_factory_bot, slug
factory) parses /start, /auth <code>, /whoami, /logout. On a
successful /auth, the message containing the code is best-effort
deleted from the user's chat (replay defense).
- Redis-backed sessions (key tg-gw:auth:<from.id>, TTL 24h, configurable
via AUTH_SESSION_TTL). Constant-time secret compare via crypto/subtle.
- ALLOWED_USERS env (CSV of Telegram user IDs) — silent-drops anyone
not in the list before the auth gate runs.
- New per-bot field 'requireAuth' (pointer-bool). Default = true (secure
by default). Auto-forced to false for handler=auth (chicken-and-egg).
- Server gates: allowlist first, then requireAuth before handler dispatch.
- Fail-at-startup if a bot is configured with handler=auth or
requireAuth: true while AUTH_SECRET is unset.
Design: factory/docs/adr/20260509-telegram-gateway-auth.md (in factory PR).
User docs: AUTH.md (new), HOWTO_ADD_BOT.md (Cas 2 updated for default
true and gated flow).
New deps: github.com/redis/go-redis/v9.
Refs ~/.claude/plans/pour-les-notifications-on-inherited-seal.md § Phase 1.5.
DisallowUnknownFields rejected real Telegram payloads (entities, from,
date, etc. that our minimal structs don't cover). Lenient decode is the
right default for an upstream webhook we don't control.
Aligns the project name with the public URL (tg.arcodange.fr) and the
Arcodange organization conventions. The 'homelab-gateway' name was too
generic.
Touches: chart name + helpers, image registry path, Go module path,
secret/configmap names, deployment mountPath, all docs.