Stage 1: backend foundation (Postgres, sessions, accounts, OTel)
Tests · Go / test (push) Successful in 11s
Tests · Integration / integration (push) Successful in 8s

- internal/postgres: pgx-over-database/sql pool (otelsql), embedded goose
  migrations into schema 'backend', committed go-jet code + cmd/jetgen tool.
- internal/account: durable accounts + unified telegram/email identities
  (UUIDv7 keys), find-or-create provisioning with unique-conflict handling.
- internal/session: opaque 256-bit tokens stored as a SHA-256 hash, revoke-only
  (no TTL); write-through cache gating /readyz; store + service.
- internal/telemetry: OTel tracer/meter providers (none/stdout) + request-timing
  middleware; internal/config gains Postgres + OTel env loading.
- internal/server: /api/v1 {public,user,internal,admin} skeleton + X-User-ID
  middleware; /readyz checks DB ping + cache; main wires
  telemetry -> db+migrate -> warm cache -> server.
- Tests: unit + integration (build tag 'integration', testcontainers
  postgres:17) for migrations, accounts, sessions, readyz; new integration.yaml.
- Docs: ARCHITECTURE, TESTING, PLAN refinements, root + backend READMEs.

Session/account REST handlers deferred to Stage 6 (gateway); OTLP + dashboards
to Stage 11.
This commit is contained in:
Ilia Denisov
2026-06-02 13:52:26 +02:00
parent da079b2bc6
commit eeaad62b10
45 changed files with 3461 additions and 92 deletions
+46 -10
View File
@@ -8,15 +8,51 @@ import (
"go.uber.org/zap/zaptest"
)
// TestProbes verifies that the infrastructure probes answer 200 OK.
func TestProbes(t *testing.T) {
srv := New(":0", zaptest.NewLogger(t))
for _, path := range []string{"/healthz", "/readyz"} {
req := httptest.NewRequest(http.MethodGet, path, nil)
rec := httptest.NewRecorder()
srv.http.Handler.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("%s: status = %d, want %d", path, rec.Code, http.StatusOK)
}
// get serves a GET request against the server's handler and returns the recorder.
func get(srv *Server, path string) *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodGet, path, nil)
rec := httptest.NewRecorder()
srv.Handler().ServeHTTP(rec, req)
return rec
}
// TestHealthz verifies that /healthz answers 200 OK.
func TestHealthz(t *testing.T) {
srv := New(":0", Deps{Logger: zaptest.NewLogger(t)})
if rec := get(srv, "/healthz"); rec.Code != http.StatusOK {
t.Fatalf("/healthz status = %d, want %d", rec.Code, http.StatusOK)
}
}
// TestReadyzReadyWithoutDeps verifies that, with no database and no session
// readiness gate wired, /readyz answers 200 OK.
func TestReadyzReadyWithoutDeps(t *testing.T) {
srv := New(":0", Deps{Logger: zaptest.NewLogger(t)})
if rec := get(srv, "/readyz"); rec.Code != http.StatusOK {
t.Fatalf("/readyz status = %d, want %d", rec.Code, http.StatusOK)
}
}
// TestReadyzNotReadyWhenSessionsCold verifies that /readyz answers 503 while the
// session cache reports not-ready.
func TestReadyzNotReadyWhenSessionsCold(t *testing.T) {
srv := New(":0", Deps{
Logger: zaptest.NewLogger(t),
SessionsReady: func() bool { return false },
})
if rec := get(srv, "/readyz"); rec.Code != http.StatusServiceUnavailable {
t.Fatalf("/readyz status = %d, want %d", rec.Code, http.StatusServiceUnavailable)
}
}
// TestReadyzReadyWhenSessionsWarm verifies that /readyz answers 200 once the
// session cache reports ready (and no database is wired).
func TestReadyzReadyWhenSessionsWarm(t *testing.T) {
srv := New(":0", Deps{
Logger: zaptest.NewLogger(t),
SessionsReady: func() bool { return true },
})
if rec := get(srv, "/readyz"); rec.Code != http.StatusOK {
t.Fatalf("/readyz status = %d, want %d", rec.Code, http.StatusOK)
}
}