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:
@@ -0,0 +1,52 @@
|
||||
package session
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// TestCache exercises the write-through cache's add/get/remove/size/ready cycle.
|
||||
func TestCache(t *testing.T) {
|
||||
c := NewCache()
|
||||
if c.Ready() {
|
||||
t.Error("a fresh cache must not report ready")
|
||||
}
|
||||
if _, ok := c.Get("h1"); ok {
|
||||
t.Error("get on empty cache must miss")
|
||||
}
|
||||
|
||||
s := Session{ID: uuid.New(), AccountID: uuid.New(), TokenHash: "h1", Status: StatusActive}
|
||||
c.Add(s)
|
||||
if got, ok := c.Get("h1"); !ok || got.ID != s.ID {
|
||||
t.Fatalf("get after add: got %v ok=%v", got.ID, ok)
|
||||
}
|
||||
if c.Size() != 1 {
|
||||
t.Errorf("size = %d, want 1", c.Size())
|
||||
}
|
||||
|
||||
c.Remove("h1")
|
||||
if _, ok := c.Get("h1"); ok {
|
||||
t.Error("get after remove must miss")
|
||||
}
|
||||
if c.Size() != 0 {
|
||||
t.Errorf("size = %d, want 0", c.Size())
|
||||
}
|
||||
}
|
||||
|
||||
// TestCacheNilSafe checks that the cache methods are safe on a nil receiver,
|
||||
// which the readiness probe relies on before the cache is constructed.
|
||||
func TestCacheNilSafe(t *testing.T) {
|
||||
var c *Cache
|
||||
if c.Ready() {
|
||||
t.Error("nil cache must not be ready")
|
||||
}
|
||||
if _, ok := c.Get("x"); ok {
|
||||
t.Error("nil cache get must miss")
|
||||
}
|
||||
if c.Size() != 0 {
|
||||
t.Error("nil cache size must be 0")
|
||||
}
|
||||
c.Add(Session{TokenHash: "x"}) // must not panic
|
||||
c.Remove("x") // must not panic
|
||||
}
|
||||
Reference in New Issue
Block a user