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
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user