✨ 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>
229 lines
6.3 KiB
Go
229 lines
6.3 KiB
Go
package testsetup
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
|
|
"dance-lessons-coach/pkg/bdd"
|
|
|
|
"github.com/cucumber/godog"
|
|
)
|
|
|
|
// getWorkingDir returns the current working directory
|
|
func getWorkingDir() string {
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return "unknown"
|
|
}
|
|
return dir
|
|
}
|
|
|
|
// FeatureConfig holds configuration for a feature test
|
|
type FeatureConfig struct {
|
|
FeatureName string
|
|
Format string
|
|
StopOnFailure bool
|
|
}
|
|
|
|
// MultiFeatureConfig holds configuration for multi-feature tests
|
|
type MultiFeatureConfig struct {
|
|
Paths []string
|
|
Format string
|
|
StopOnFailure bool
|
|
}
|
|
|
|
// NewFeatureConfig creates a new feature configuration
|
|
func NewFeatureConfig(featureName, format string, stopOnFailure bool) *FeatureConfig {
|
|
return &FeatureConfig{
|
|
FeatureName: featureName,
|
|
Format: format,
|
|
StopOnFailure: stopOnFailure,
|
|
}
|
|
}
|
|
|
|
// NewMultiFeatureConfig creates a new multi-feature configuration
|
|
func NewMultiFeatureConfig(paths []string, format string, stopOnFailure bool) *MultiFeatureConfig {
|
|
return &MultiFeatureConfig{
|
|
Paths: paths,
|
|
Format: format,
|
|
StopOnFailure: stopOnFailure,
|
|
}
|
|
}
|
|
|
|
// GetFeatureFromEnv gets the feature name from environment variable
|
|
func GetFeatureFromEnv() string {
|
|
return os.Getenv("FEATURE")
|
|
}
|
|
|
|
// GetAllFeaturePaths returns paths for all features by scanning the filesystem
|
|
func GetAllFeaturePaths() []string {
|
|
// Get the project root directory
|
|
projectRoot, err := getProjectRoot()
|
|
if err != nil {
|
|
// Fallback to hardcoded list if we can't determine project root
|
|
return []string{
|
|
"auth",
|
|
"config",
|
|
"greet",
|
|
"health",
|
|
"jwt",
|
|
}
|
|
}
|
|
|
|
// Read the features directory from project root
|
|
featuresPath := filepath.Join(projectRoot, "features")
|
|
entries, err := os.ReadDir(featuresPath)
|
|
if err != nil {
|
|
// Fallback to hardcoded list if filesystem access fails
|
|
return []string{
|
|
"auth",
|
|
"config",
|
|
"greet",
|
|
"health",
|
|
"jwt",
|
|
}
|
|
}
|
|
|
|
var paths []string
|
|
for _, entry := range entries {
|
|
// Only include directories (features) that are not hidden and not test files
|
|
if entry.IsDir() && !strings.HasPrefix(entry.Name(), ".") {
|
|
paths = append(paths, entry.Name())
|
|
}
|
|
}
|
|
|
|
// Sort paths for consistent ordering
|
|
sort.Strings(paths)
|
|
|
|
return paths
|
|
}
|
|
|
|
// getProjectRoot finds the project root directory by looking for go.mod
|
|
func getProjectRoot() (string, error) {
|
|
// Start from current directory and walk up the tree
|
|
dir, err := os.Getwd()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Walk up the directory tree until we find go.mod or reach root
|
|
for {
|
|
// Check if go.mod exists in current directory
|
|
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
|
|
return dir, nil
|
|
}
|
|
|
|
// Move up one directory
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir {
|
|
// Reached root directory
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
|
|
// If we get here, we didn't find go.mod - return original working directory
|
|
return "", fmt.Errorf("could not find project root (go.mod not found)")
|
|
}
|
|
|
|
// CreateTestSuite creates a configured godog test suite
|
|
func CreateTestSuite(t *testing.T, config *FeatureConfig, suiteName string) godog.TestSuite {
|
|
// Set FEATURE environment variable for feature-specific configuration
|
|
os.Setenv("FEATURE", config.FeatureName)
|
|
|
|
// Allow tag override via environment variable
|
|
tags := os.Getenv("GODOG_TAGS")
|
|
if tags == "" {
|
|
// Default tags if not overridden
|
|
tags = "~@flaky && ~@todo && ~@skip"
|
|
}
|
|
|
|
// Allow stop on failure override via environment variable
|
|
stopOnFailure := config.StopOnFailure
|
|
if envStop := os.Getenv("GODOG_STOP_ON_FAILURE"); envStop != "" {
|
|
// Support various boolean formats
|
|
stopOnFailure, _ = strconv.ParseBool(envStop)
|
|
}
|
|
|
|
// Allow randomization seed override via environment variable
|
|
randomize := int64(-1) // Default: randomize test order
|
|
if envSeed := os.Getenv("GODOG_RANDOM_SEED"); envSeed != "" {
|
|
if parsedSeed, err := strconv.ParseInt(envSeed, 10, 64); err == nil {
|
|
randomize = parsedSeed
|
|
}
|
|
}
|
|
|
|
// Determine the correct path for feature files
|
|
// When running from within a feature directory, use "." to find feature files in current dir
|
|
// When running from outside, use the feature name as a relative path
|
|
featurePath := "."
|
|
if workingDir := getWorkingDir(); !strings.HasSuffix(workingDir, "/"+config.FeatureName) && !strings.HasSuffix(workingDir, "\\"+config.FeatureName) {
|
|
// Not running from within the feature directory, use feature name
|
|
featurePath = config.FeatureName
|
|
}
|
|
|
|
return godog.TestSuite{
|
|
Name: suiteName,
|
|
TestSuiteInitializer: bdd.InitializeTestSuite,
|
|
ScenarioInitializer: bdd.InitializeScenario,
|
|
Options: &godog.Options{
|
|
Format: config.Format,
|
|
Paths: []string{featurePath},
|
|
TestingT: t,
|
|
Strict: true,
|
|
Randomize: randomize,
|
|
StopOnFailure: stopOnFailure,
|
|
Tags: tags,
|
|
},
|
|
}
|
|
}
|
|
|
|
// CreateMultiFeatureTestSuite creates a configured godog test suite for multiple features
|
|
func CreateMultiFeatureTestSuite(t *testing.T, config *MultiFeatureConfig, suiteName string) godog.TestSuite {
|
|
// Set FEATURE environment variable for feature-specific configuration
|
|
// For multi-feature tests, we don't set a specific feature
|
|
os.Setenv("FEATURE", "")
|
|
|
|
// Allow tag override via environment variable
|
|
tags := os.Getenv("GODOG_TAGS")
|
|
if tags == "" {
|
|
// Default tags if not overridden
|
|
tags = "~@flaky && ~@todo && ~@skip"
|
|
}
|
|
|
|
// Allow stop on failure override via environment variable
|
|
stopOnFailure := config.StopOnFailure
|
|
if envStop := os.Getenv("GODOG_STOP_ON_FAILURE"); envStop != "" {
|
|
// Support various boolean formats
|
|
stopOnFailure, _ = strconv.ParseBool(envStop)
|
|
}
|
|
|
|
// Allow randomization seed override via environment variable
|
|
randomize := int64(-1) // Default: randomize test order
|
|
if envSeed := os.Getenv("GODOG_RANDOM_SEED"); envSeed != "" {
|
|
if parsedSeed, err := strconv.ParseInt(envSeed, 10, 64); err == nil {
|
|
randomize = parsedSeed
|
|
}
|
|
}
|
|
|
|
return godog.TestSuite{
|
|
Name: suiteName,
|
|
TestSuiteInitializer: bdd.InitializeTestSuite,
|
|
ScenarioInitializer: bdd.InitializeScenario,
|
|
Options: &godog.Options{
|
|
Format: config.Format,
|
|
Paths: config.Paths,
|
|
TestingT: t,
|
|
Strict: true,
|
|
Randomize: randomize,
|
|
StopOnFailure: stopOnFailure,
|
|
Tags: tags,
|
|
},
|
|
}
|
|
}
|