diff --git a/apps/api/internal/httpapi/core_flow_integration_test.go b/apps/api/internal/httpapi/core_flow_integration_test.go index eff633d..1e72b97 100644 --- a/apps/api/internal/httpapi/core_flow_integration_test.go +++ b/apps/api/internal/httpapi/core_flow_integration_test.go @@ -2009,6 +2009,7 @@ func applyMigration(t *testing.T, ctx context.Context, databaseURL string) { t.Fatalf("connect migration db: %v", err) } defer pool.Close() + requireDedicatedIntegrationTestDatabase(t, ctx, pool) if _, err := pool.Exec(ctx, ` CREATE TABLE IF NOT EXISTS schema_migrations ( version text PRIMARY KEY, diff --git a/apps/api/internal/httpapi/integration_database_safety_test.go b/apps/api/internal/httpapi/integration_database_safety_test.go new file mode 100644 index 0000000..cf35662 --- /dev/null +++ b/apps/api/internal/httpapi/integration_database_safety_test.go @@ -0,0 +1,54 @@ +package httpapi + +import ( + "context" + "strings" + "testing" + + "github.com/jackc/pgx/v5/pgxpool" +) + +func requireDedicatedIntegrationTestDatabase(t *testing.T, ctx context.Context, pool *pgxpool.Pool) { + t.Helper() + var databaseName string + if err := pool.QueryRow(ctx, `SELECT current_database()`).Scan(&databaseName); err != nil { + t.Fatalf("read integration test database name: %v", err) + } + if !isDedicatedIntegrationTestDatabase(databaseName) { + t.Fatalf( + "AI_GATEWAY_TEST_DATABASE_URL must reference a dedicated test database; refusing to use %q", + databaseName, + ) + } +} + +func isDedicatedIntegrationTestDatabase(databaseName string) bool { + normalized := strings.ToLower(strings.TrimSpace(databaseName)) + return normalized == "test" || + strings.HasPrefix(normalized, "test_") || + strings.HasSuffix(normalized, "_test") || + strings.Contains(normalized, "_test_") +} + +func TestDedicatedIntegrationTestDatabaseName(t *testing.T) { + t.Parallel() + for _, testCase := range []struct { + name string + database string + allowed bool + }{ + {name: "documented local test database", database: "easyai_ai_gateway_test_codex", allowed: true}, + {name: "test prefix", database: "test_gateway", allowed: true}, + {name: "test suffix", database: "gateway_test", allowed: true}, + {name: "production database", database: "easyai_ai_gateway", allowed: false}, + {name: "similar production name", database: "gateway_contest", allowed: false}, + } { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + if got := isDedicatedIntegrationTestDatabase(testCase.database); got != testCase.allowed { + t.Fatalf("isDedicatedIntegrationTestDatabase(%q) = %v, want %v", testCase.database, got, testCase.allowed) + } + }) + } +}