//go:build integration // Package testutil contains opt-in helpers for isolated integration databases. package testutil import ( "crypto/rand" "encoding/hex" "fmt" "net/url" "os" "strings" "testing" "github.com/stretchr/testify/require" "gorm.io/driver/postgres" "gorm.io/gorm" ) // OpenIsolatedPostgres creates a random schema and binds every pooled test // connection to it through search_path. It never reads the production DSN. func OpenIsolatedPostgres(t testing.TB, config *gorm.Config) *gorm.DB { t.Helper() dsn := strings.TrimSpace(os.Getenv("TEST_DATABASE_URL")) require.NotEmpty(t, dsn, "TEST_DATABASE_URL is required for integration tests and must point to an isolated database") schema := randomSchemaName(t) admin, err := gorm.Open(postgres.Open(dsn), config) require.NoError(t, err) adminSQL, err := admin.DB() require.NoError(t, err) require.NoError(t, admin.Exec(fmt.Sprintf(`CREATE SCHEMA "%s"`, schema)).Error) schemaDSN, err := postgresSchemaDSN(dsn, schema) require.NoError(t, err) database, err := gorm.Open(postgres.Open(schemaDSN), config) require.NoError(t, err) isolationSQL, err := database.DB() require.NoError(t, err) t.Cleanup(func() { _ = isolationSQL.Close() _ = admin.Exec(fmt.Sprintf(`DROP SCHEMA IF EXISTS "%s" CASCADE`, schema)).Error _ = adminSQL.Close() }) return database } func randomSchemaName(t testing.TB) string { t.Helper() value := make([]byte, 12) _, err := rand.Read(value) require.NoError(t, err) return "senlin_test_" + hex.EncodeToString(value) } func postgresSchemaDSN(dsn, schema string) (string, error) { if strings.Contains(dsn, "://") { parsed, err := url.Parse(dsn) if err != nil { return "", err } query := parsed.Query() query.Set("search_path", schema) parsed.RawQuery = query.Encode() return parsed.String(), nil } return strings.TrimSpace(dsn) + " search_path=" + schema, nil }