feat: implement schema-per-scenario isolation for BDD tests
All checks were successful
CI/CD Pipeline / Build Docker Cache (push) Successful in 14s
CI/CD Pipeline / CI Pipeline (push) Successful in 3m52s

- Add BDD_SCHEMA_ISOLATION env var to enable schema-per-scenario mode
- Generate unique schema names using SHA256 hash of feature+scenario
- Implement SetupScenarioSchema() and TeardownScenarioSchema() methods
- Handle search_path configuration for schema isolation
- Use CASCADE drop to clean up all scenario-created DB objects
- Add isSchemaIsolationEnabled() helper to suite.go
- Update cleanup flow: skip table clearing when schema isolation is active
- Add ADR 0025 documenting isolation strategies and decision rationale

Activation: Set BDD_SCHEMA_ISOLATION=true to enable
Debug: Set BDD_ENABLE_CLEANUP_LOGS=true for verbose isolation logging

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
This commit is contained in:
2026-04-10 22:45:43 +02:00
parent 0b7f52b08d
commit 0aedf829de
4 changed files with 398 additions and 14 deletions

View File

@@ -20,6 +20,11 @@ func isCleanupLoggingEnabled() bool {
return os.Getenv("BDD_ENABLE_CLEANUP_LOGS") == "true"
}
// isSchemaIsolationEnabled returns true if BDD_SCHEMA_ISOLATION environment variable is set to "true"
func isSchemaIsolationEnabled() bool {
return os.Getenv("BDD_SCHEMA_ISOLATION") == "true"
}
func InitializeTestSuite(ctx *godog.TestSuiteContext) {
ctx.BeforeSuite(func() {
// Small delay to ensure any previous server instances are fully cleaned up
@@ -37,8 +42,24 @@ func InitializeTestSuite(ctx *godog.TestSuiteContext) {
sc := ctx.ScenarioContext()
sc.BeforeScenario(func(s *godog.Scenario) {
// Get feature name from context or environment
feature := os.Getenv("FEATURE")
if feature == "" {
// Try to extract feature from scenario tags or path
feature = "unknown"
}
if isCleanupLoggingEnabled() {
log.Info().Str("scenario", s.Name).Msg("CLEANUP: Scenario starting")
log.Info().Str("feature", feature).Str("scenario", s.Name).Msg("CLEANUP: Scenario starting")
}
// Setup schema isolation if enabled
if sharedServer != nil {
if err := sharedServer.SetupScenarioSchema(feature, s.Name); err != nil {
if isCleanupLoggingEnabled() {
log.Warn().Err(err).Msg("ISOLATION: Failed to setup scenario schema")
}
}
}
})
@@ -48,17 +69,27 @@ func InitializeTestSuite(ctx *godog.TestSuiteContext) {
}
if sharedServer != nil {
// Teardown schema isolation if enabled
if teardownErr := sharedServer.TeardownScenarioSchema(); teardownErr != nil {
if isCleanupLoggingEnabled() {
log.Warn().Err(teardownErr).Msg("ISOLATION: Failed to teardown scenario schema")
}
}
// Reset JWT secrets after every scenario to prevent pollution
// Note: This is still needed for in-memory state even with schema isolation
if resetErr := sharedServer.ResetJWTSecrets(); resetErr != nil {
if isCleanupLoggingEnabled() {
log.Warn().Err(resetErr).Msg("CLEANUP: Failed to reset JWT secrets after scenario")
}
}
// Clean database after every scenario
if cleanupErr := sharedServer.CleanupDatabase(); cleanupErr != nil {
if isCleanupLoggingEnabled() {
log.Warn().Err(cleanupErr).Msg("CLEANUP: Failed to cleanup database after scenario")
// Clean database after every scenario (only if schema isolation is disabled)
if !isSchemaIsolationEnabled() {
if cleanupErr := sharedServer.CleanupDatabase(); cleanupErr != nil {
if isCleanupLoggingEnabled() {
log.Warn().Err(cleanupErr).Msg("CLEANUP: Failed to cleanup database after scenario")
}
}
}
}