- Add BDD_SCHEMA_ISOLATION env var to enable PostgreSQL schema-per-scenario isolation
- Generate unique schema names: test_{sha256(feature:scenario)[:8]}
- Implement SetupScenarioSchema() and TeardownScenarioSchema() with search_path handling
- Add CASCADE drop to clean up all scenario-created DB objects
- Add isSchemaIsolationEnabled() helpers to both suite.go and server.go
- Skip table clearing when schema isolation is active (schema drop replaces it)
- Update validate-test-suite.sh to set FIXED_TEST_PORT and BDD_SCHEMA_ISOLATION
- Add isCleanupLoggingEnabled() for optional CLEANUP: prefixed logs
- Add ADR 0025 documenting all isolation strategies and decision rationale
Activation:
BDD_SCHEMA_ISOLATION=true - Enable schema-per-scenario isolation
BDD_ENABLE_CLEANUP_LOGS=true - Enable verbose cleanup/isolation logging
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
118 lines
3.6 KiB
Go
118 lines
3.6 KiB
Go
package bdd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"dance-lessons-coach/pkg/bdd/steps"
|
|
"dance-lessons-coach/pkg/bdd/testserver"
|
|
|
|
"github.com/cucumber/godog"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
var sharedServer *testserver.Server
|
|
|
|
// isCleanupLoggingEnabled returns true if BDD_ENABLE_CLEANUP_LOGS environment variable is set to "true"
|
|
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
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
sharedServer = testserver.NewServer()
|
|
if err := sharedServer.Start(); err != nil {
|
|
// Improved error message for port conflicts
|
|
if strings.Contains(err.Error(), "address already in use") {
|
|
panic(fmt.Sprintf("Port conflict: %v. Try running 'lsof -i :9191' and 'kill -9 <PID>' to free the port", err))
|
|
}
|
|
panic(fmt.Sprintf("Failed to start test server: %v", err))
|
|
}
|
|
})
|
|
|
|
sc := ctx.ScenarioContext()
|
|
sc.BeforeScenario(func(s *godog.Scenario) {
|
|
// Get feature name from environment - falls back to "bdd" for multi-feature tests
|
|
feature := os.Getenv("FEATURE")
|
|
if feature == "" {
|
|
feature = "bdd"
|
|
}
|
|
|
|
if isCleanupLoggingEnabled() {
|
|
log.Info().Str("feature", feature).Str("scenario", s.Name).Msg("CLEANUP: Scenario starting")
|
|
}
|
|
|
|
// Setup schema isolation if enabled
|
|
if sharedServer != nil {
|
|
// Include scenario Uri for disambiguation when multiple features run
|
|
scenarioKey := s.Name
|
|
if s.Uri != "" {
|
|
scenarioKey = fmt.Sprintf("%s:%s", s.Uri, s.Name)
|
|
}
|
|
if err := sharedServer.SetupScenarioSchema(feature, scenarioKey); err != nil {
|
|
if isCleanupLoggingEnabled() {
|
|
log.Warn().Err(err).Str("feature", feature).Str("scenario", scenarioKey).Msg("ISOLATION: Failed to setup scenario schema")
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
sc.AfterScenario(func(s *godog.Scenario, err error) {
|
|
if isCleanupLoggingEnabled() {
|
|
log.Info().Str("scenario", s.Name).Str("status", "completed").Err(err).Msg("CLEANUP: Scenario completed")
|
|
}
|
|
|
|
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 (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")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
ctx.AfterSuite(func() {
|
|
if sharedServer != nil {
|
|
// Final cleanup
|
|
if err := sharedServer.Stop(); err != nil {
|
|
log.Warn().Err(err).Msg("Failed to shutdown HTTP server")
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
steps.CleanupAllTestConfigFiles()
|
|
})
|
|
}
|
|
|
|
func InitializeScenario(ctx *godog.ScenarioContext) {
|
|
client := testserver.NewClient(sharedServer)
|
|
steps.InitializeAllSteps(ctx, client)
|
|
}
|