package postgres import ( "os" "strconv" "strings" "testing" ) func TestMigrationsAreEmbeddedInOrder(t *testing.T) { entries, err := migrationsFS.ReadDir("migrations") if err != nil { t.Fatalf("failed to read migrations directory: %v", err) } var upFiles []string var downFiles []string for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() if strings.HasSuffix(name, ".up.sql") { upFiles = append(upFiles, name) } else if strings.HasSuffix(name, ".down.sql") { downFiles = append(downFiles, name) } } if len(upFiles) == 0 { t.Error("expected at least one up migration file, found none") } if len(downFiles) == 0 { t.Error("expected at least one down migration file, found none") } if len(upFiles) != len(downFiles) { t.Errorf("mismatched migration count: %d up files, %d down files", len(upFiles), len(downFiles)) } // Verify up and down files match and are in numerical order for i := 0; i < len(upFiles); i++ { up := upFiles[i] partsUp := strings.Split(up, "_") if len(partsUp) < 2 { t.Errorf("invalid up migration file name: %s", up) continue } versionUp, err := strconv.ParseInt(partsUp[0], 10, 64) if err != nil { t.Errorf("failed to parse up version from %s: %v", up, err) } if versionUp != int64(i+1) { t.Errorf("expected version %d for file %s, got %d", i+1, up, versionUp) } // Find corresponding down file var foundDown bool for _, down := range downFiles { if strings.Split(down, "_")[0] == partsUp[0] { foundDown = true break } } if !foundDown { t.Errorf("missing corresponding down migration for: %s", up) } } } func TestMigrationStatementsAreNamed(t *testing.T) { // Check if migrate.go FS variable is populated entries, err := migrationsFS.ReadDir("migrations") if err != nil { t.Fatalf("failed to read migrations: %v", err) } if len(entries) == 0 { t.Error("migrations FS has no entries") } } func TestLiveAuditEventsMigrationPairing(t *testing.T) { up, err := migrationsFS.ReadFile("migrations/000004_live_audit_events.up.sql") if err != nil { t.Fatalf("failed to read 000004 up migration: %v", err) } down, err := migrationsFS.ReadFile("migrations/000004_live_audit_events.down.sql") if err != nil { t.Fatalf("failed to read 000004 down migration: %v", err) } for _, col := range []string{"event_id", "account_id", "event_type", "payload", "created_at"} { if !strings.Contains(string(up), col) { t.Errorf("000004 up migration missing column %q", col) } } if !strings.Contains(string(down), "live_audit_events") { t.Error("000004 down migration must drop live_audit_events table") } } func TestAnalysisResultColumnsAddedInVersionedMigration(t *testing.T) { // The analysis result columns must arrive through the v2 migration so databases // already at version 1 receive them, rather than being edited into 000001. content, err := migrationsFS.ReadFile("migrations/000002_backtest_analysis_results.up.sql") if err != nil { t.Fatalf("failed to read 000002 up migration: %v", err) } up := string(content) for _, column := range []string{"total_return", "trade_count", "equity_curve"} { if !strings.Contains(up, column) { t.Errorf("000002 up migration missing column %q", column) } } // The originally applied 000001 migration must not carry the analysis columns. backbone, err := migrationsFS.ReadFile("migrations/000001_worker_backbone.up.sql") if err != nil { t.Fatalf("failed to read 000001 up migration: %v", err) } for _, column := range []string{"total_return", "trade_count", "equity_curve"} { if strings.Contains(string(backbone), column) { t.Errorf("000001 up migration should not contain analysis column %q", column) } } } func TestMarketDataSchemaKeepsBarsNormalized(t *testing.T) { // Backtest input bars must persist normalized OHLCV fields only. Provider raw // payloads can be decoded upstream, but the bars table/query contract must not // grow a default raw JSON payload column. backbone, err := migrationsFS.ReadFile("migrations/000001_worker_backbone.up.sql") if err != nil { t.Fatalf("failed to read 000001 up migration: %v", err) } queries, err := os.ReadFile("queries/queries.sql") if err != nil { t.Fatalf("failed to read queries.sql: %v", err) } schema := string(backbone) queryText := string(queries) for _, required := range []string{"instrument_id", "timeframe", "timestamp", "open", "high", "low", "close", "volume"} { if !strings.Contains(schema, required) { t.Errorf("bars schema missing normalized column %q", required) } if !strings.Contains(queryText, required) { t.Errorf("bars queries missing normalized column %q", required) } } for _, forbidden := range []string{"raw_payload", "raw_response", "provider_payload"} { if strings.Contains(schema, forbidden) { t.Errorf("bars schema must not include raw provider payload column %q", forbidden) } if strings.Contains(queryText, forbidden) { t.Errorf("bars queries must not include raw provider payload column %q", forbidden) } } }