From 1fc80ffe232758d4a0cc339a8618602ae09c390d Mon Sep 17 00:00:00 2001 From: easyai Date: Fri, 24 Jul 2026 23:27:17 +0800 Subject: [PATCH] =?UTF-8?q?test(http):=20=E9=98=B2=E6=AD=A2=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=B5=8B=E8=AF=95=E8=AF=AF=E7=94=A8=E9=9D=9E=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=95=B0=E6=8D=AE=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原因:共享迁移辅助函数会直接对 AI_GATEWAY_TEST_DATABASE_URL 指向的数据库执行迁移,需要在任何写入前阻止误连非测试库。 影响:HTTP 集成测试仅允许名称明确包含 test 边界的数据库;不影响未配置数据库环境变量时的普通单元测试。 风险:自定义测试库名称若不符合规则会被拒绝,需要改用 test、test_*、*_test 或包含 _test_ 的名称。 验证:gofmt 无差异;env -u AI_GATEWAY_TEST_DATABASE_URL go test ./... -count=1 通过。 --- .../httpapi/core_flow_integration_test.go | 1 + .../integration_database_safety_test.go | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 apps/api/internal/httpapi/integration_database_safety_test.go 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) + } + }) + } +}