package store import ( "database/sql" "errors" "os" "path/filepath" "strings" "testing" ) func TestSystemIdentityEnsureVerifySQLite(t *testing.T) { st, err := OpenSQLite(filepath.Join(t.TempDir(), "id.db")) if err != nil { t.Fatal(err) } defer st.Close() if err := st.VerifySystemIdentity("svc-a-aaaaaa", "sqlite"); !errors.Is(err, ErrSystemIdentityMissing) { t.Fatalf("empty verify: want missing, got %v", err) } if err := st.EnsureSystemIdentity("svc-a-aaaaaa", "sqlite"); err != nil { t.Fatal(err) } if err := st.EnsureSystemIdentity("svc-a-aaaaaa", "sqlite"); err != nil { t.Fatalf("idempotent ensure: %v", err) } if err := st.VerifySystemIdentity("svc-a-aaaaaa", "sqlite"); err != nil { t.Fatal(err) } if err := st.EnsureSystemIdentity("svc-b-bbbbbb", "sqlite"); !errors.Is(err, ErrSystemIdentityMismatch) { t.Fatalf("wrong id ensure: want mismatch, got %v", err) } if err := st.VerifySystemIdentity("svc-a-aaaaaa", "pg:other"); !errors.Is(err, ErrSystemIdentityMismatch) { t.Fatalf("wrong dbref verify: want mismatch, got %v", err) } row, err := st.GetSystemIdentity() if err != nil { t.Fatal(err) } if row.InstanceID != "svc-a-aaaaaa" || row.DBRef != "sqlite" { t.Fatalf("row=%+v", row) } if row.SchemaVersion != latestMigrationVersion() { t.Fatalf("schema_version=%d want %d", row.SchemaVersion, latestMigrationVersion()) } } func TestSystemIdentityMigrationCreatesTable(t *testing.T) { st, err := OpenSQLite(filepath.Join(t.TempDir(), "mig.db")) if err != nil { t.Fatal(err) } defer st.Close() versions, err := st.MigrationStatus() if err != nil { t.Fatal(err) } found := false for _, v := range versions { if v == 27 { found = true break } } if !found { t.Fatalf("migration v27 not applied: %v", versions) } var name string if err := st.DB().QueryRow(`SELECT name FROM sqlite_master WHERE type='table' AND name='agent_system_identity'`).Scan(&name); err != nil || name != "agent_system_identity" { t.Fatalf("table missing: name=%q err=%v", name, err) } cols := identityColumnNames(t, st.DB()) if cols != IdentityColumns { t.Fatalf("身份表列已偏离冻结契约:%s", cols) } } func TestOpenIdentityReadsFutureSchemaWithoutMigrating(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "agent-system.db") raw, err := sql.Open("sqlite", "file:"+path) if err != nil { t.Fatal(err) } stmts := []string{ `CREATE TABLE schema_migrations (version INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL)`, `INSERT INTO schema_migrations(version, name, applied_at) VALUES (99, 'future', 't')`, `CREATE TABLE agent_system_identity ( instance_id TEXT NOT NULL PRIMARY KEY, dbref TEXT NOT NULL, schema_version INTEGER NOT NULL, created_at TEXT NOT NULL)`, `INSERT INTO agent_system_identity(instance_id, dbref, schema_version, created_at) VALUES ('svc-future-aaaa', 'sqlite', 99, 't')`, } for _, stmt := range stmts { if _, err := raw.Exec(stmt); err != nil { t.Fatal(err) } } raw.Close() st, err := OpenIdentity("", dir) if err != nil { t.Fatal(err) } defer st.Close() if err := st.VerifySystemIdentity("svc-future-aaaa", "sqlite"); err != nil { t.Fatal(err) } ver, err := st.SchemaVersion() if err != nil || ver != 99 { t.Fatalf("schema=%d err=%v,身份打开不得改写迁移版本", ver, err) } if cols := identityColumnNames(t, st.DB()); cols != IdentityColumns { t.Fatalf("列=%s", cols) } } func TestOpenIdentityMissingFileOrTable(t *testing.T) { dir := t.TempDir() if _, err := OpenIdentity("", filepath.Join(dir, "missing")); !errors.Is(err, ErrIdentityDBMissing) { t.Fatalf("缺文件:%v", err) } if _, err := os.Stat(filepath.Join(dir, "missing")); !os.IsNotExist(err) { t.Fatal("缺库时创建了目录") } path := filepath.Join(dir, "agent-system.db") raw, err := sql.Open("sqlite", "file:"+path) if err != nil { t.Fatal(err) } if _, err := raw.Exec(`CREATE TABLE schema_migrations (version INTEGER PRIMARY KEY, name TEXT NOT NULL, applied_at TEXT NOT NULL)`); err != nil { t.Fatal(err) } raw.Close() parent := filepath.Dir(path) if _, err := OpenIdentity("", parent); !errors.Is(err, ErrIdentityTableMissing) { t.Fatalf("缺表:%v", err) } } func identityColumnNames(t *testing.T, db *sql.DB) string { t.Helper() rows, err := db.Query(`SELECT name FROM pragma_table_info('agent_system_identity') ORDER BY cid`) if err != nil { t.Fatal(err) } defer rows.Close() var names []string for rows.Next() { var name string if err := rows.Scan(&name); err != nil { t.Fatal(err) } names = append(names, name) } return strings.Join(names, ",") }