Files
Andy Dixon 6374e174c6 feat: add configuration, logging, metrics, scheduler and storage foundation
- config: defaults -> JSON file -> NOEMA_* env with validation and redaction
- observability: slog with secret-redacting handler; Prometheus registry with
  the metric set from the plan (events, workspace, memory, LLM, tools, queues)
- scheduler: supervised Group and bounded Pool so no goroutine is unowned
- storage: pgx pool with statement timeout, embedded migration runner
  (advisory lock, checksums, up/down/status), per-test schema helper
- migrations/0001: append-only guard trigger, users, sessions, api tokens,
  recovery codes, audit log, settings
- pkg/ids: UUIDv7 with monotonic counter
2026-06-14 23:08:37 +01:00

54 lines
1.5 KiB
Go

package storage
import (
"context"
"testing"
"testing/fstest"
"github.com/andydixon/noema/migrations"
)
func TestLoadMigrationsValidation(t *testing.T) {
if _, err := LoadMigrations(migrations.FS); err != nil {
t.Fatalf("embedded migrations invalid: %v", err)
}
bad := fstest.MapFS{"0002_x.up.sql": {Data: []byte("select 1")}}
if _, err := LoadMigrations(bad); err == nil {
t.Fatal("expected contiguity error")
}
}
func TestMigrateUpDownAndAppendOnly(t *testing.T) {
pool := TestPool(t, migrations.FS)
ctx := context.Background()
st, err := Status(ctx, pool, migrations.FS)
if err != nil {
t.Fatal(err)
}
for _, s := range st {
if !s.Applied || s.Drift {
t.Fatalf("migration %d not cleanly applied: %+v", s.Version, s)
}
}
// Append-only trigger on audit_log.
if _, err := pool.Exec(ctx, `INSERT INTO audit_log (id, actor_kind, action) VALUES (gen_random_uuid(),'system','test')`); err != nil {
t.Fatal(err)
}
if _, err := pool.Exec(ctx, `UPDATE audit_log SET action='x'`); err == nil {
t.Fatal("update on audit_log should be rejected")
}
if _, err := pool.Exec(ctx, `DELETE FROM audit_log`); err == nil {
t.Fatal("delete on audit_log should be rejected")
}
// Roll everything back and forward again.
for i := len(st); i > 0; i-- {
if _, err := MigrateDown(ctx, pool, migrations.FS); err != nil {
t.Fatalf("down %d: %v", i, err)
}
}
applied, err := MigrateUp(ctx, pool, migrations.FS)
if err != nil || len(applied) != len(st) {
t.Fatalf("re-apply: %v (%d)", err, len(applied))
}
}