Files
dance-lessons-coach/pkg/bdd/suite.go
Gabriel Radureau 70c2eb554e
Some checks failed
CI/CD Pipeline / Build Docker Cache (push) Successful in 10s
CI/CD Pipeline / CI Pipeline (push) Failing after 4m8s
🧪 test: implement per-scenario state isolation and enhance validate-test-suite.sh
- Add pkg/bdd/steps/scenario_state.go with thread-safe per-scenario state manager
- Update auth_steps.go, jwt_retention_steps.go to use per-scenario state accessors
- Add LastSecret and LastError fields to ScenarioState for JWT retention testing
- Update steps.go with SetScenarioKeyForAllSteps function
- Update suite.go to generate scenario keys and clear state properly
- Mark config hot-reload scenarios as @flaky (timing-sensitive)
- Fix validate-test-suite.sh: add -p 1 flag for sequential execution, filter JSON logs, add --count flag
- Add CONFIG_SCHEMA.md documenting configuration architecture
- Split greet tests into v1/v2 sub-tests with explicit v2 enable/disable

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-11 13:34:51 +02:00

151 lines
4.7 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
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 <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"
}
// 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)
}