🧪 test: add JWT secret rotation BDD scenarios and step implementations (#12)
✨ merge: implement JWT secret rotation with BDD scenario isolation - Implement JWT secret rotation mechanism (closes #8) - Add per-scenario state isolation for BDD tests (closes #14) - Validate password reset workflow via BDD tests (closes #7) - Fix port conflicts in test validation - Add state tracer for debugging test execution - Document BDD isolation strategies in ADR 0025 - Fix PostgreSQL configuration environment variables Generated by Mistral Vibe. Co-Authored-By: Mistral Vibe <vibe@mistral.ai> Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #12.
This commit is contained in:
100
pkg/bdd/steps/scenario_state.go
Normal file
100
pkg/bdd/steps/scenario_state.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package steps
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ScenarioState holds per-scenario state for step definitions
|
||||
// This prevents state pollution between scenarios running in the same test process
|
||||
type ScenarioState struct {
|
||||
LastToken string
|
||||
FirstToken string
|
||||
LastUserID uint
|
||||
LastSecret string
|
||||
LastError string
|
||||
// Add more fields as needed for other step types
|
||||
}
|
||||
|
||||
// scenarioStateManager manages per-scenario state isolation
|
||||
type scenarioStateManager struct {
|
||||
mu sync.RWMutex
|
||||
states map[string]*ScenarioState
|
||||
}
|
||||
|
||||
var globalStateManager *scenarioStateManager
|
||||
var once sync.Once
|
||||
|
||||
// GetScenarioStateManager returns the singleton scenario state manager
|
||||
func GetScenarioStateManager() *scenarioStateManager {
|
||||
once.Do(func() {
|
||||
globalStateManager = &scenarioStateManager{
|
||||
states: make(map[string]*ScenarioState),
|
||||
}
|
||||
})
|
||||
return globalStateManager
|
||||
}
|
||||
|
||||
// scenarioKey generates a unique key for a scenario
|
||||
func scenarioKey(scenario string) string {
|
||||
// Use SHA256 hash to create a consistent, bounded-length key
|
||||
hash := sha256.Sum256([]byte(scenario))
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
// GetState returns the state for a given scenario, creating it if necessary
|
||||
func (sm *scenarioStateManager) GetState(scenario string) *ScenarioState {
|
||||
sm.mu.RLock()
|
||||
key := scenarioKey(scenario)
|
||||
state, exists := sm.states[key]
|
||||
sm.mu.RUnlock()
|
||||
|
||||
if exists {
|
||||
return state
|
||||
}
|
||||
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
|
||||
// Double-check after acquiring write lock
|
||||
if state, exists = sm.states[key]; exists {
|
||||
return state
|
||||
}
|
||||
|
||||
state = &ScenarioState{}
|
||||
sm.states[key] = state
|
||||
return state
|
||||
}
|
||||
|
||||
// ClearState removes the state for a given scenario
|
||||
func (sm *scenarioStateManager) ClearState(scenario string) {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
key := scenarioKey(scenario)
|
||||
delete(sm.states, key)
|
||||
}
|
||||
|
||||
// ClearAllStates removes all scenario states
|
||||
func (sm *scenarioStateManager) ClearAllStates() {
|
||||
sm.mu.Lock()
|
||||
defer sm.mu.Unlock()
|
||||
sm.states = make(map[string]*ScenarioState)
|
||||
}
|
||||
|
||||
// Package-level convenience functions
|
||||
|
||||
// GetScenarioState returns the state for the current scenario
|
||||
func GetScenarioState(scenario string) *ScenarioState {
|
||||
return GetScenarioStateManager().GetState(scenario)
|
||||
}
|
||||
|
||||
// ClearScenarioState removes the state for the current scenario
|
||||
func ClearScenarioState(scenario string) {
|
||||
GetScenarioStateManager().ClearState(scenario)
|
||||
}
|
||||
|
||||
// ClearAllScenarioStates removes all scenario states
|
||||
func ClearAllScenarioStates() {
|
||||
GetScenarioStateManager().ClearAllStates()
|
||||
}
|
||||
Reference in New Issue
Block a user