Stage 1: backend foundation (Postgres, sessions, accounts, OTel)
- 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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user