package store import ( "context" "testing" ) func userVersion(t *testing.T, s *Store) int { t.Helper() var v int if err := s.db.QueryRowContext(context.Background(), "PRAGMA user_version").Scan(&v); err != nil { t.Fatalf("read user_version: %v", err) } return v } func TestMigrateAppliesOnceAndIsIdempotent(t *testing.T) { ctx := context.Background() s := newTestStore(t) // The 1 migration in the built-in slice (tools scope) was applied on Open. startVer := len(migrations) if v := userVersion(t, s); v != startVer { t.Fatalf("fresh DB user_version = %d, want %d", v, startVer) } // Append a fake migration and run it: creates a throwaway table, bumps by 1. migrations = append(migrations, `CREATE TABLE migrate_probe (id INTEGER PRIMARY KEY)`) t.Cleanup(func() { migrations = migrations[:len(migrations)-1] }) if err := migrate(ctx, s.db); err != nil { t.Fatalf("migrate: %v", err) } want := startVer + 1 if v := userVersion(t, s); v != want { t.Fatalf("after migrate user_version = %d, want %d", v, want) } if _, err := s.db.ExecContext(ctx, "INSERT INTO migrate_probe DEFAULT VALUES"); err != nil { t.Fatalf("probe table not created: %v", err) } // Second run is a no-op — re-running the CREATE would error (no IF NOT EXISTS). if err := migrate(ctx, s.db); err != nil { t.Fatalf("migrate second run not idempotent: %v", err) } if v := userVersion(t, s); v != want { t.Fatalf("after re-migrate user_version = %d, want %d", v, want) } }