diff --git a/internal/db/migrate_test.go b/internal/db/migrate_test.go index 24ae244..5804a49 100644 --- a/internal/db/migrate_test.go +++ b/internal/db/migrate_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "slices" "testing" ) @@ -41,6 +42,20 @@ func registeredMigrations(t *testing.T, ctx context.Context, db *sql.DB) []strin return ids } +// upMigrationIDs returns the ids of the embedded up-migrations in apply order. +func upMigrationIDs(t *testing.T) []string { + t.Helper() + migs, err := loadMigrations() + if err != nil { + t.Fatalf("loadMigrations: %v", err) + } + ids := make([]string, len(migs)) + for i, m := range migs { + ids[i] = m.id + } + return ids +} + // appMetaExists reports whether the app_meta table is present. func appMetaExists(t *testing.T, ctx context.Context, db *sql.DB) bool { t.Helper() @@ -60,15 +75,16 @@ func TestMigrateUp_FirstApply_正常(t *testing.T) { t.Fatalf("MigrateUp first call: %v", err) } + // Applies every embedded up-migration, in order. ids := registeredMigrations(t, ctx, db) - if len(ids) != 1 || ids[0] != "0001_init" { - t.Fatalf("schema_migrations = %v, want exactly [0001_init]", ids) + if want := upMigrationIDs(t); !slices.Equal(ids, want) { + t.Fatalf("schema_migrations = %v, want %v", ids, want) } if !appMetaExists(t, ctx, db) { t.Fatal("app_meta table should exist after MigrateUp") } - // applied_at must be recorded on the single row. + // applied_at must be recorded on the first migration row. var appliedAt string if err := db.QueryRowContext(ctx, "SELECT applied_at FROM schema_migrations WHERE id = ?", "0001_init").Scan(&appliedAt); err != nil { t.Fatalf("read applied_at: %v", err) @@ -87,13 +103,14 @@ func TestMigrateUp_Idempotent_正常(t *testing.T) { t.Fatalf("MigrateUp first call: %v", err) } - // Second call must not re-execute or duplicate the record. + // Second call must not re-execute or duplicate the records. if err := MigrateUp(ctx, db, testLogger()); err != nil { t.Fatalf("MigrateUp second call: %v", err) } + want := upMigrationIDs(t) ids := registeredMigrations(t, ctx, db) - if len(ids) != 1 { - t.Fatalf("schema_migrations rows = %d, want 1 (idempotent), ids=%v", len(ids), ids) + if !slices.Equal(ids, want) { + t.Fatalf("idempotent: schema_migrations = %v, want %v", ids, want) } if !appMetaExists(t, ctx, db) { t.Fatal("app_meta should still exist after idempotent re-run") @@ -105,16 +122,17 @@ func TestMigrateDown_ThenReUp_正常(t *testing.T) { ctx := context.Background() db := openMemory(t) + want := upMigrationIDs(t) if err := MigrateUp(ctx, db, testLogger()); err != nil { t.Fatalf("MigrateUp: %v", err) } - // Roll back the single applied migration. - if err := MigrateDown(ctx, db, 1, testLogger()); err != nil { - t.Fatalf("MigrateDown: %v", err) + // Roll back every applied migration. + if err := MigrateDown(ctx, db, len(want), testLogger()); err != nil { + t.Fatalf("MigrateDown(all): %v", err) } if appMetaExists(t, ctx, db) { - t.Fatal("app_meta should be dropped after MigrateDown") + t.Fatal("app_meta should be dropped after MigrateDown(all)") } if ids := registeredMigrations(t, ctx, db); len(ids) != 0 { t.Fatalf("schema_migrations should be empty after rollback, got %v", ids) @@ -127,8 +145,8 @@ func TestMigrateDown_ThenReUp_正常(t *testing.T) { if !appMetaExists(t, ctx, db) { t.Fatal("app_meta should exist again after re-MigrateUp") } - if ids := registeredMigrations(t, ctx, db); len(ids) != 1 || ids[0] != "0001_init" { - t.Fatalf("schema_migrations = %v, want exactly [0001_init]", ids) + if ids := registeredMigrations(t, ctx, db); !slices.Equal(ids, want) { + t.Fatalf("schema_migrations = %v, want %v", ids, want) } } diff --git a/internal/db/migrations/0002_memory.down.sql b/internal/db/migrations/0002_memory.down.sql new file mode 100644 index 0000000..5466a55 --- /dev/null +++ b/internal/db/migrations/0002_memory.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS memory_items; diff --git a/internal/db/migrations/0002_memory.up.sql b/internal/db/migrations/0002_memory.up.sql new file mode 100644 index 0000000..81fe218 --- /dev/null +++ b/internal/db/migrations/0002_memory.up.sql @@ -0,0 +1,21 @@ +-- M2: long-term memory items (DevDocs/design/03 §0/§3). +-- Canonical schema reference; internal/runtime/memory reuses this table. +CREATE TABLE IF NOT EXISTS memory_items ( + id TEXT PRIMARY KEY, + persona_id TEXT NOT NULL, + role_id TEXT NOT NULL DEFAULT '', + scope TEXT NOT NULL, + kind TEXT NOT NULL, + content TEXT NOT NULL, + tags TEXT NOT NULL DEFAULT '[]', + confidence REAL NOT NULL DEFAULT 1.0, + source_conversation TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL, + decay_weight REAL NOT NULL DEFAULT 1.0 +); + +CREATE INDEX IF NOT EXISTS idx_memory_items_persona_id + ON memory_items (persona_id); +CREATE INDEX IF NOT EXISTS idx_memory_items_persona_id_created_at + ON memory_items (persona_id, created_at);