eeaad62b10
- 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.
59 lines
1.9 KiB
Go
59 lines
1.9 KiB
Go
package server
|
|
|
|
import (
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
|
|
"go.uber.org/zap/zaptest"
|
|
)
|
|
|
|
// 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)
|
|
}
|
|
}
|