✨ 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>
9.2 KiB
9.2 KiB
BDD State Tracer
Overview
The BDD State Tracer is a debugging tool that logs scenario execution, database operations, and state modifications to a file in $TMPDIR for analysis of test execution order and state pollution issues.
Purpose
Why Tracing Was Added
During multi-iteration BDD test runs with ./scripts/validate-test-suite.sh, intermittent failures occurred that were difficult to diagnose:
- Tests passed when run individually
- Tests failed when run together in the validation script
- Patterns suggested database state pollution between scenarios across different feature packages
The tracer was created to answer key questions:
- Execution Order: Which scenarios run in which order?
- State Modifications: What database writes/cleanups occur and when?
- Overlap Detection: Are scenarios running in parallel (causing race conditions)?
- Isolation Verification: Is schema isolation working as expected?
Key Findings from Tracing
- Sequential Execution: Each feature package runs in a separate process (separate PIDs), but scenarios within each feature run sequentially
- Shared Database: All processes share the same PostgreSQL database connection
- Schema Isolation Status: When
BDD_SCHEMA_ISOLATION=false(default in validate script), all scenarios share thepublicschema - Cleanup Operations: Database cleanup (
CleanupDatabase) runs after each scenario, deleting all test data from all tables - In-Memory State: JWT secrets are stored in-memory only, not in database - schema isolation doesn't prevent JWT secret pollution
Example Trace Output
2026-04-11T10:10:53.032156 | auth | User registration | SCENARIO_START |
2026-04-11T10:10:53.146438 | auth | User registration | SCENARIO_END | PASSED
2026-04-11T10:10:53.152398 | auth | User registration | JWT_RESET | ok
2026-04-11T10:10:53.162357 | auth | Failed authentication | SCENARIO_START |
2026-04-11T10:10:53.268273 | auth | Failed authentication | SCENARIO_END | PASSED
Usage
Enable Tracing
Set the environment variable BDD_TRACE_STATE=1 before running tests:
# Single run with tracing
BDD_TRACE_STATE=1 go test ./features/auth -v
# Validation script with tracing
BDD_TRACE_STATE=1 ./scripts/validate-test-suite.sh 1
# Multiple runs with tracing
BDD_TRACE_STATE=1 ./scripts/validate-test-suite.sh 5
Trace File Location
Trace files are written to $TMPDIR (typically /var/folders/.../T/ on macOS or /tmp on Linux):
# Find trace files
ls -la $TMPDIR/bdd-state-trace-*.log
# View a trace file
cat $TMPDIR/bdd-state-trace-20260411-101053-12345.log
Trace File Format
TIMESTAMP | FEATURE | SCENARIO | ACTION | DETAILS
2026-04-11T10:10:53.032156 | auth | User registration | SCENARIO_START |
2026-04-11T10:10:53.146438 | auth | User registration | SCENARIO_END | PASSED
2026-04-11T10:10:53.152398 | auth | User registration | JWT_RESET | ok
2026-04-11T10:10:53.162357 | auth | User registration | DB_CLEANUP | all_tables
Columns:
TIMESTAMP: ISO 8601 format with microsecondsFEATURE: Feature name fromFEATUREenvironment variableSCENARIO: Scenario name (includes URI for disambiguation)ACTION: Type of action (see below)DETAILS: Additional context
Action Types:
SCENARIO_START- Scenario execution beginsSCENARIO_END- Scenario execution completes (PASSED or FAILED)DB_CLEANUP- Database cleanup operationDB_SELECT- Database read operationJWT_RESET- JWT secrets reset to initial stateDB_INSERT/UPDATE/DELETE- Database write operations (future)SCHEMA_*- Schema isolation operations (future)TX_*- Transaction boundary operations (future)
Implementation
Architecture
The state tracer uses a simple file-based approach:
- Per-Process Tracing: Each
go testprocess creates its own trace file with unique filename based on timestamp and PID - Immediate Flush: Each trace line is flushed immediately to disk using
Sync()to prevent data loss - No Dependencies: Uses only standard library (
os,fmt,time,path/filepath) - Singleton Pattern: Package-level functions for easy usage across the codebase
Files
pkg/bdd/testserver/state_tracer.go- Core tracing functionspkg/bdd/suite.go- Integration with godog Before/After scenario hooks
Key Functions
// Package-level functions (called from anywhere)
TraceStateScenarioStart(feature, scenario string)
TraceStateScenarioEnd(feature, scenario string, err error)
TraceStateDBCleanup(feature, scenario, table string)
TraceStateJWTSecretOperation(feature, scenario, operation, details string)
TraceStateSchemaIsolation(feature, scenario, operation, details string)
TraceStateTransaction(feature, scenario, action, details string)
TraceStateDBRead(feature, scenario, table, details string)
Limitations
Current Limitations
- Per-Process Files: Each
go testprocess creates its own file, making correlation across processes manual - No Database Write Tracing: Currently only traces cleanup, not individual INSERT/UPDATE/DELETE operations
- No API Call Tracing: Doesn't trace HTTP requests made during scenarios
- No Timing Analysis: Doesn't measure duration between operations automatically
- No Schema Name in Trace: When schema isolation is enabled, doesn't show which schema is active
- File Rotation: No automatic cleanup of old trace files
Known Issues
- PID-based filenames: If multiple runs happen in the same second, filenames could collide
- Large file sizes: High-volume tracing could create large files (mitigated by per-run files)
- No header/footer: Trace files start immediately with data, no metadata about the run
Future Enhancements
Priority 1: Process Correlation
- Add a unique run ID that can be passed across all processes
- Include process start/end markers to show process lifecycle
- Add parent PID tracking to show process hierarchy
Priority 2: Database Operation Tracing
- Add tracing for all database writes (INSERT, UPDATE, DELETE)
- Include query text and affected rows
- Trace transaction boundaries with IDs
- Add schema name to all database operations when isolation is enabled
Priority 3: API Call Tracing
- Trace all HTTP requests made during scenarios
- Include request method, path, status code, and duration
- Mark requests that modify state (POST, PUT, DELETE vs GET)
Priority 4: Analysis Tools
- Create a
bdd-trace-analyzertool to:- Merge trace files from all processes in correct order
- Detect overlapping scenarios (parallel execution)
- Identify database state pollution patterns
- Generate visualization of scenario execution timeline
- Flag potential race conditions
Priority 5: Improved Output
- Add trace file header with metadata (run ID, start time, config, etc.)
- Color-coded output for different action types
- JSON output option for programmatic analysis
- Trace level filtering (DEBUG, INFO, WARN, ERROR)
Priority 6: Performance Optimization
- Batch writes instead of per-line flush (with configurable flush interval)
- Compress old trace files
- Automatic cleanup of old files
Analysis Use Cases
Detecting State Pollution
Look for patterns like:
PID 1234 | auth | Scenario A | DB_CLEANUP | all_tables
PID 5678 | greet | Scenario B | SCENARIO_START |
# ^ Scenario B starts AFTER auth cleanup - potential issue
Detecting Parallel Execution
Check if timestamps overlap:
PID 1234 | 10:10:53.032 | auth | Scenario A | SCENARIO_START
PID 5678 | 10:10:53.035 | greet | Scenario B | SCENARIO_START
# ^ Both started within 3ms - likely parallel
Verifying Schema Isolation
Check that each scenario gets its own schema:
PID 1234 | auth | Scenario A | SCHEMA_CREATE | test_a1b2c3d4
PID 1234 | auth | Scenario B | SCHEMA_CREATE | test_e5f6g7h8
# ^ Different schemas for different scenarios - good
Troubleshooting
Tracing Not Working
- Verify
BDD_TRACE_STATE=1is set:echo $BDD_TRACE_STATE - Check if trace files are being created:
ls -la $TMPDIR/bdd-state-trace-*.log - Verify the
testserverpackage is being used (tracing is integrated there)
No Trace Files Found
- Tracing only works when
BDD_TRACE_STATE=1is set before the test process starts - Each
go testprocess creates its own file - if tests pass quickly, files may be short - Files are created in
$TMPDIRwhich defaults to/tmpon Linux and a temp folder on macOS
Trace Files Too Large
- Tracing every operation can generate large files
- Consider filtering to specific scenarios:
# Run only failing scenarios with tracing BDD_TRACE_STATE=1 go test ./features/auth -v -run "TestAuthBDD/Password_reset"
Related Files
pkg/bdd/suite.go- Godog test suite initialization with tracing hookspkg/bdd/testserver/server.go- Test server with tracing integrationscripts/validate-test-suite.sh- Test validation script