package web // testdb_test.go exposes a setupTestDB helper for the web package integration // tests. The implementation is a verbatim copy of the auth package's // setupTestDB (~20 LOC kernel) so the web package does not import a test-only // function from another package (Go does not allow importing _test.go files). // // Decision: duplication chosen over a shared non-test helper to keep the // coupling surface minimal and avoid moving test infra into production code. // Recorded in 02-04-SUMMARY.md. import ( "context" "database/sql" "fmt" "os" "path/filepath" "runtime" "sync" "testing" "time" "github.com/google/uuid" _ "github.com/jackc/pgx/v5/stdlib" "github.com/jackc/pgx/v5/pgxpool" "github.com/pressly/goose/v3" ) func migrationsDir() string { _, filename, _, _ := runtime.Caller(0) // filename: .../backend/internal/web/testdb_test.go // migrations: .../backend/migrations return filepath.Join(filepath.Dir(filename), "..", "..", "migrations") } var webGooseMu sync.Mutex func setupTestDB(t *testing.T) (*pgxpool.Pool, func()) { t.Helper() dsn := os.Getenv("TEST_DATABASE_URL") if dsn == "" { dsn = os.Getenv("DATABASE_URL") } if dsn == "" { t.Skip("TEST_DATABASE_URL (or DATABASE_URL) not set — integration test skipped") return nil, nil } rawID := uuid.New().String()[:12] cleanID := make([]byte, len(rawID)) for i := 0; i < len(rawID); i++ { if rawID[i] == '-' { cleanID[i] = '_' } else { cleanID[i] = rawID[i] } } schemaName := "test_" + string(cleanID) bootstrapDB, err := sql.Open("pgx", dsn) if err != nil { t.Fatalf("setupTestDB: sql.Open bootstrap: %v", err) } if err := bootstrapDB.Ping(); err != nil { bootstrapDB.Close() t.Fatalf("setupTestDB: ping: %v", err) } if _, err := bootstrapDB.Exec(fmt.Sprintf("CREATE SCHEMA %q", schemaName)); err != nil { bootstrapDB.Close() t.Fatalf("setupTestDB: CREATE SCHEMA: %v", err) } bootstrapDB.Close() sep := "?" for i := 0; i < len(dsn); i++ { if dsn[i] == '?' { sep = "&" break } } schemaDSN := fmt.Sprintf("%s%ssearch_path=%s,public", dsn, sep, schemaName) versionTable := schemaName + "_goose_version" { webGooseMu.Lock() defer webGooseMu.Unlock() prevTable := goose.TableName() goose.SetTableName(versionTable) defer goose.SetTableName(prevTable) goose.SetBaseFS(nil) if err := goose.SetDialect("postgres"); err != nil { webDropSchema(dsn, schemaName) t.Fatalf("setupTestDB: goose.SetDialect: %v", err) } schemaDB, err := sql.Open("pgx", schemaDSN) if err != nil { webDropSchema(dsn, schemaName) t.Fatalf("setupTestDB: sql.Open schema-scoped: %v", err) } if err := goose.Up(schemaDB, migrationsDir()); err != nil { schemaDB.Close() webDropSchema(dsn, schemaName) t.Fatalf("setupTestDB: goose.Up: %v", err) } schemaDB.Close() } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cfg, err := pgxpool.ParseConfig(schemaDSN) if err != nil { webDropSchema(dsn, schemaName) t.Fatalf("setupTestDB: pgxpool.ParseConfig: %v", err) } cfg.MaxConns = 5 pool, err := pgxpool.NewWithConfig(ctx, cfg) if err != nil { webDropSchema(dsn, schemaName) t.Fatalf("setupTestDB: pgxpool.NewWithConfig: %v", err) } cleanup := func() { pool.Close() webDropSchema(dsn, schemaName) } return pool, cleanup } func webDropSchema(dsn, schemaName string) { db, err := sql.Open("pgx", dsn) if err != nil { return } defer db.Close() db.Exec(fmt.Sprintf("DROP SCHEMA IF EXISTS %q CASCADE", schemaName)) //nolint:errcheck }