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 var sharedStepContext *steps.StepContext // 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 ' 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" } // Generate scenario key for state isolation scenarioKey := s.Name if s.Uri != "" { scenarioKey = fmt.Sprintf("%s:%s", s.Uri, s.Name) } // Set scenario key on all step instances for state isolation if sharedStepContext != nil { steps.SetScenarioKeyForAllSteps(sharedStepContext, scenarioKey) // Also clear state for this scenario to ensure clean start steps.ClearScenarioState(scenarioKey) } if isCleanupLoggingEnabled() { log.Info().Str("feature", feature).Str("scenario", s.Name).Msg("CLEANUP: Scenario starting") } // Trace scenario start testserver.TraceStateScenarioStart(feature, scenarioKey) // Setup schema isolation if enabled if sharedServer != nil { 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) { // 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("scenario", s.Name).Str("status", "completed").Err(err).Msg("CLEANUP: Scenario completed") } // Trace scenario end scenarioKey := s.Name if s.Uri != "" { scenarioKey = fmt.Sprintf("%s:%s", s.Uri, s.Name) } testserver.TraceStateScenarioEnd(feature, scenarioKey, err) 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") } } else { testserver.TraceStateJWTSecretOperation(feature, scenarioKey, "RESET", "ok") } // 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") } } else { testserver.TraceStateDBCleanup(feature, scenarioKey, "all_tables") } } } }) 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) } // Clear all scenario states steps.ClearAllScenarioStates() steps.CleanupAllTestConfigFiles() }) } func InitializeScenario(ctx *godog.ScenarioContext) { client := testserver.NewClient(sharedServer) // Create and store the step context for scenario isolation sharedStepContext = steps.NewStepContext(client) steps.InitializeAllSteps(ctx, client, sharedStepContext) }