Compare commits
7 Commits
520da07bfe
...
4df20585b8
| Author | SHA1 | Date | |
|---|---|---|---|
| 4df20585b8 | |||
| aa4823eb11 | |||
| 756fc5abfd | |||
| 1f92302eff | |||
| 4292f79c6a | |||
| e9fd453a88 | |||
| a75f87777b |
@@ -26,6 +26,66 @@ Used to categorize tests by system component:
|
||||
- `@database` - Database interaction tests
|
||||
- `@security` - Security-related tests
|
||||
|
||||
### Exclusion Tags
|
||||
Used to exclude tests from execution:
|
||||
- `@flaky` - Tests that are unstable or intermittently fail
|
||||
- `@todo` - Tests with pending step implementations
|
||||
- `@skip` - Tests that should be skipped entirely
|
||||
|
||||
### Work In Progress Tag
|
||||
Used to override exclusions for active development:
|
||||
- `@wip` - Work In Progress - overrides exclusion tags to allow focused development
|
||||
|
||||
**Usage:** Add `@wip` to scenarios you're actively working on, even if they have other exclusion tags like `@todo` or `@skip`. The `@wip` tag takes precedence and allows the scenario to run.
|
||||
|
||||
**Example:**
|
||||
```gherkin
|
||||
@todo @wip
|
||||
Scenario: JWT authentication with multiple secrets
|
||||
Given the server is running with multiple JWT secrets
|
||||
When I authenticate with valid credentials
|
||||
Then I should receive a valid JWT token
|
||||
```
|
||||
|
||||
### Command-Line Tag Override
|
||||
You can override the default tag filtering by setting the `GODOG_TAGS` environment variable when running tests.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Run only @wip scenarios
|
||||
GODOG_TAGS="@wip" go test ./features/jwt/...
|
||||
|
||||
# Run smoke tests only
|
||||
GODOG_TAGS="@smoke" go test ./features/...
|
||||
|
||||
# Run specific combination
|
||||
GODOG_TAGS="@jwt && ~@todo" go test ./features/...
|
||||
|
||||
# Combine with other environment variables
|
||||
DLC_DATABASE_HOST=localhost GODOG_TAGS="@wip" go test ./features/jwt/...
|
||||
```
|
||||
|
||||
### Stop On Failure Control
|
||||
You can control whether tests stop on first failure using the `GODOG_STOP_ON_FAILURE` environment variable.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
# Stop on first failure (strict mode)
|
||||
GODOG_STOP_ON_FAILURE="true" go test ./features/jwt/...
|
||||
|
||||
# Continue after failures (lenient mode)
|
||||
GODOG_STOP_ON_FAILURE="false" go test ./features/jwt/...
|
||||
|
||||
# Combine with tag filtering
|
||||
GODOG_TAGS="@wip" GODOG_STOP_ON_FAILURE="true" go test ./features/jwt/...
|
||||
```
|
||||
|
||||
**Default Behavior:**
|
||||
- If `GODOG_TAGS` is not set, the test uses the default tag filter: `~@flaky && ~@todo && ~@skip`
|
||||
- If `GODOG_STOP_ON_FAILURE` is not set, each feature uses its default:
|
||||
- `jwt`, `greet`, `auth`, `health`: `true` (stop on failure)
|
||||
- `config`, `all features`: `false` (continue after failures)
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Running Smoke Tests
|
||||
@@ -150,6 +210,10 @@ Feature: Health Endpoint
|
||||
| `@config` | Configuration | `@config` on config scenarios |
|
||||
| `@api` | API endpoints | `@api` on endpoint tests |
|
||||
| `@v2` | V2 API | `@v2` on version 2 tests |
|
||||
| `@flaky` | Exclude flaky tests | `@flaky` on unstable scenarios |
|
||||
| `@todo` | Exclude pending tests | `@todo` on unimplemented scenarios |
|
||||
| `@skip` | Exclude tests entirely | `@skip` on disabled scenarios |
|
||||
| `@wip` | Work in progress | `@wip` on actively developed scenarios |
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dance-lessons-coach/pkg/bdd"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"dance-lessons-coach/pkg/bdd/testsetup"
|
||||
)
|
||||
|
||||
func TestAuthBDD(t *testing.T) {
|
||||
// Set FEATURE environment variable for feature-specific configuration
|
||||
os.Setenv("FEATURE", "auth")
|
||||
|
||||
suite := godog.TestSuite{
|
||||
Name: "dance-lessons-coach BDD Tests - Auth Feature",
|
||||
TestSuiteInitializer: bdd.InitializeTestSuite,
|
||||
ScenarioInitializer: bdd.InitializeScenario,
|
||||
Options: &godog.Options{
|
||||
Format: "progress",
|
||||
Paths: []string{"."},
|
||||
TestingT: t,
|
||||
Strict: true,
|
||||
Randomize: -1,
|
||||
StopOnFailure: true,
|
||||
},
|
||||
}
|
||||
config := testsetup.NewFeatureConfig("auth", "progress", false)
|
||||
suite := testsetup.CreateTestSuite(t, config, "dance-lessons-coach BDD Tests - Auth Feature")
|
||||
|
||||
if suite.Run() != 0 {
|
||||
t.Fatal("non-zero status returned, failed to run auth BDD tests")
|
||||
|
||||
@@ -1,50 +1,30 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dance-lessons-coach/pkg/bdd"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"dance-lessons-coach/pkg/bdd/testsetup"
|
||||
)
|
||||
|
||||
func TestBDD(t *testing.T) {
|
||||
// Get feature name from environment variable or default to all features
|
||||
feature := os.Getenv("FEATURE")
|
||||
feature := testsetup.GetFeatureFromEnv()
|
||||
|
||||
var paths []string
|
||||
var suiteName string
|
||||
var paths []string
|
||||
|
||||
if feature == "" {
|
||||
// Run all features
|
||||
suiteName = "dance-lessons-coach BDD Tests - All Features"
|
||||
paths = []string{
|
||||
"auth",
|
||||
"config",
|
||||
"greet",
|
||||
"health",
|
||||
"jwt",
|
||||
}
|
||||
paths = testsetup.GetAllFeaturePaths()
|
||||
} else {
|
||||
// Run specific feature
|
||||
suiteName = "dance-lessons-coach BDD Tests - " + feature + " Feature"
|
||||
paths = []string{feature}
|
||||
}
|
||||
|
||||
suite := godog.TestSuite{
|
||||
Name: suiteName,
|
||||
TestSuiteInitializer: bdd.InitializeTestSuite,
|
||||
ScenarioInitializer: bdd.InitializeScenario,
|
||||
Options: &godog.Options{
|
||||
Format: "progress",
|
||||
Paths: paths,
|
||||
TestingT: t,
|
||||
Strict: true,
|
||||
Randomize: -1,
|
||||
// StopOnFailure: true,
|
||||
},
|
||||
}
|
||||
config := testsetup.NewMultiFeatureConfig(paths, "progress", false)
|
||||
suite := testsetup.CreateMultiFeatureTestSuite(t, config, suiteName)
|
||||
|
||||
if suite.Run() != 0 {
|
||||
t.Fatal("non-zero status returned, failed to run BDD tests")
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
Feature: Config Hot Reloading
|
||||
The system should support selective hot reloading of configuration changes
|
||||
|
||||
@flaky
|
||||
Scenario: Hot reloading logging level changes
|
||||
Given the server is running with config file monitoring enabled
|
||||
When I update the logging level to "debug" in the config file
|
||||
Then the logging level should be updated without restart
|
||||
And debug logs should appear in the output
|
||||
|
||||
@flaky
|
||||
Scenario: Hot reloading feature flags
|
||||
Given the server is running with config file monitoring enabled
|
||||
And the v2 API is disabled
|
||||
@@ -15,6 +17,7 @@ Feature: Config Hot Reloading
|
||||
Then the v2 API should become available without restart
|
||||
And v2 API requests should succeed
|
||||
|
||||
@flaky
|
||||
Scenario: Hot reloading telemetry sampling settings
|
||||
Given the server is running with config file monitoring enabled
|
||||
And telemetry is enabled
|
||||
@@ -23,6 +26,7 @@ Feature: Config Hot Reloading
|
||||
Then the telemetry sampling should be updated without restart
|
||||
And the new sampling settings should be applied
|
||||
|
||||
@flaky
|
||||
Scenario: Hot reloading JWT TTL
|
||||
Given the server is running with config file monitoring enabled
|
||||
And JWT TTL is set to 1 hour
|
||||
@@ -30,6 +34,7 @@ Feature: Config Hot Reloading
|
||||
Then the JWT TTL should be updated without restart
|
||||
And new JWT tokens should have the updated expiration
|
||||
|
||||
@flaky
|
||||
Scenario: Attempting to hot reload non-reloadable settings should be ignored
|
||||
Given the server is running with config file monitoring enabled
|
||||
When I update the server port to 9090 in the config file
|
||||
@@ -37,6 +42,7 @@ Feature: Config Hot Reloading
|
||||
And the server should continue running on the original port
|
||||
And a warning should be logged about ignored configuration change
|
||||
|
||||
@flaky
|
||||
Scenario: Invalid configuration changes should be handled gracefully
|
||||
Given the server is running with config file monitoring enabled
|
||||
When I update the logging level to "invalid_level" in the config file
|
||||
@@ -44,12 +50,14 @@ Feature: Config Hot Reloading
|
||||
And an error should be logged about invalid configuration
|
||||
And the server should continue running normally
|
||||
|
||||
@flaky
|
||||
Scenario: Config file monitoring should handle file deletion gracefully
|
||||
Given the server is running with config file monitoring enabled
|
||||
When I delete the config file
|
||||
Then the server should continue running with last known good configuration
|
||||
And a warning should be logged about missing config file
|
||||
|
||||
@flaky
|
||||
Scenario: Config file monitoring should handle file recreation
|
||||
Given the server is running with config file monitoring enabled
|
||||
And I have deleted the config file
|
||||
@@ -57,6 +65,7 @@ Feature: Config Hot Reloading
|
||||
Then the server should reload the configuration
|
||||
And the new configuration should be applied
|
||||
|
||||
@flaky
|
||||
Scenario: Multiple rapid configuration changes should be handled
|
||||
Given the server is running with config file monitoring enabled
|
||||
When I rapidly update the logging level multiple times
|
||||
@@ -64,6 +73,7 @@ Feature: Config Hot Reloading
|
||||
And the final configuration should be applied
|
||||
And no configuration changes should be lost
|
||||
|
||||
@flaky
|
||||
Scenario: Configuration changes should be audited
|
||||
Given the server is running with config file monitoring enabled
|
||||
And audit logging is enabled
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dance-lessons-coach/pkg/bdd"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"dance-lessons-coach/pkg/bdd/testsetup"
|
||||
)
|
||||
|
||||
func TestConfigBDD(t *testing.T) {
|
||||
// Set FEATURE environment variable for feature-specific configuration
|
||||
os.Setenv("FEATURE", "config")
|
||||
|
||||
suite := godog.TestSuite{
|
||||
Name: "dance-lessons-coach BDD Tests - Config Feature",
|
||||
TestSuiteInitializer: bdd.InitializeTestSuite,
|
||||
ScenarioInitializer: bdd.InitializeScenario,
|
||||
Options: &godog.Options{
|
||||
Format: "progress",
|
||||
Paths: []string{"."},
|
||||
TestingT: t,
|
||||
Strict: true,
|
||||
Randomize: -1,
|
||||
StopOnFailure: false,
|
||||
},
|
||||
}
|
||||
config := testsetup.NewFeatureConfig("config", "progress", false)
|
||||
suite := testsetup.CreateTestSuite(t, config, "dance-lessons-coach BDD Tests - Config Feature")
|
||||
|
||||
if suite.Run() != 0 {
|
||||
t.Fatal("non-zero status returned, failed to run config BDD tests")
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
package greet
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dance-lessons-coach/pkg/bdd"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"dance-lessons-coach/pkg/bdd/testsetup"
|
||||
)
|
||||
|
||||
func TestGreetBDD(t *testing.T) {
|
||||
// Set FEATURE environment variable for feature-specific configuration
|
||||
os.Setenv("FEATURE", "greet")
|
||||
|
||||
suite := godog.TestSuite{
|
||||
Name: "dance-lessons-coach BDD Tests - Greet Feature",
|
||||
TestSuiteInitializer: bdd.InitializeTestSuite,
|
||||
ScenarioInitializer: bdd.InitializeScenario,
|
||||
Options: &godog.Options{
|
||||
Format: "progress",
|
||||
Paths: []string{"."},
|
||||
TestingT: t,
|
||||
Strict: true,
|
||||
Randomize: -1,
|
||||
StopOnFailure: true,
|
||||
},
|
||||
}
|
||||
config := testsetup.NewFeatureConfig("greet", "progress", false)
|
||||
suite := testsetup.CreateTestSuite(t, config, "dance-lessons-coach BDD Tests - Greet Feature")
|
||||
|
||||
if suite.Run() != 0 {
|
||||
t.Fatal("non-zero status returned, failed to run greet BDD tests")
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dance-lessons-coach/pkg/bdd"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"dance-lessons-coach/pkg/bdd/testsetup"
|
||||
)
|
||||
|
||||
func TestHealthBDD(t *testing.T) {
|
||||
// Set FEATURE environment variable for feature-specific configuration
|
||||
os.Setenv("FEATURE", "health")
|
||||
|
||||
suite := godog.TestSuite{
|
||||
Name: "dance-lessons-coach BDD Tests - Health Feature",
|
||||
TestSuiteInitializer: bdd.InitializeTestSuite,
|
||||
ScenarioInitializer: bdd.InitializeScenario,
|
||||
Options: &godog.Options{
|
||||
Format: "progress",
|
||||
Paths: []string{"."},
|
||||
TestingT: t,
|
||||
Strict: true,
|
||||
Randomize: -1,
|
||||
StopOnFailure: true,
|
||||
},
|
||||
}
|
||||
config := testsetup.NewFeatureConfig("health", "progress", false)
|
||||
suite := testsetup.CreateTestSuite(t, config, "dance-lessons-coach BDD Tests - Health Feature")
|
||||
|
||||
if suite.Run() != 0 {
|
||||
t.Fatal("non-zero status returned, failed to run health BDD tests")
|
||||
|
||||
@@ -10,6 +10,7 @@ Feature: JWT Secret Retention Policy
|
||||
And the retention factor is 2.0
|
||||
And the maximum retention is 72 hours
|
||||
|
||||
@todo
|
||||
Scenario: Automatic cleanup of expired secrets
|
||||
Given a primary JWT secret exists
|
||||
And I add a secondary JWT secret with 1 hour expiration
|
||||
@@ -18,6 +19,7 @@ Feature: JWT Secret Retention Policy
|
||||
And the primary secret should remain active
|
||||
And I should see cleanup event in logs
|
||||
|
||||
@todo
|
||||
Scenario: Secret retention based on TTL factor
|
||||
Given the JWT TTL is set to 2 hours
|
||||
And the retention factor is 3.0
|
||||
@@ -25,6 +27,7 @@ Feature: JWT Secret Retention Policy
|
||||
Then the secret should expire after 6 hours
|
||||
And the retention period should be 6 hours
|
||||
|
||||
@todo
|
||||
Scenario: Maximum retention period enforcement
|
||||
Given the JWT TTL is set to 72 hours
|
||||
And the retention factor is 3.0
|
||||
@@ -33,6 +36,7 @@ Feature: JWT Secret Retention Policy
|
||||
Then the retention period should be capped at 72 hours
|
||||
And not exceed the maximum retention limit
|
||||
|
||||
@todo
|
||||
Scenario: Cleanup preserves primary secret
|
||||
Given a primary JWT secret exists
|
||||
And the primary secret is older than retention period
|
||||
@@ -40,6 +44,7 @@ Feature: JWT Secret Retention Policy
|
||||
Then the primary secret should not be removed
|
||||
And the primary secret should remain active
|
||||
|
||||
@todo
|
||||
Scenario: Multiple secrets with different ages
|
||||
Given I have 3 JWT secrets of different ages
|
||||
And secret A is 1 hour old (within retention)
|
||||
@@ -50,12 +55,14 @@ Feature: JWT Secret Retention Policy
|
||||
And secret B should be removed
|
||||
And secret C should be retained as primary
|
||||
|
||||
@todo
|
||||
Scenario: Cleanup frequency configuration
|
||||
Given the cleanup interval is set to 30 minutes
|
||||
When I add an expired JWT secret
|
||||
Then it should be removed within 30 minutes
|
||||
And I should see cleanup events every 30 minutes
|
||||
|
||||
@todo
|
||||
Scenario: Token validation with expired secret
|
||||
Given a user "retentionuser" exists with password "testpass123"
|
||||
And I authenticate with username "retentionuser" and password "testpass123"
|
||||
@@ -65,6 +72,7 @@ Feature: JWT Secret Retention Policy
|
||||
Then the token validation should fail
|
||||
And I should receive "invalid_token" error
|
||||
|
||||
@todo
|
||||
Scenario: Graceful rotation during retention period
|
||||
Given a user "gracefuluser" exists with password "testpass123"
|
||||
And I authenticate with username "gracefuluser" and password "testpass123"
|
||||
@@ -81,6 +89,7 @@ Feature: JWT Secret Retention Policy
|
||||
Then I should receive configuration validation error
|
||||
And the error should mention "retention_factor must be ≥ 1.0"
|
||||
|
||||
@todo
|
||||
Scenario: Metrics for secret retention
|
||||
Given I have enabled Prometheus metrics
|
||||
When the cleanup job removes expired secrets
|
||||
@@ -88,12 +97,14 @@ Feature: JWT Secret Retention Policy
|
||||
And I should see "jwt_secrets_active_count" metric decrease
|
||||
And I should see "jwt_secret_retention_duration_seconds" histogram update
|
||||
|
||||
@todo
|
||||
Scenario: Log masking for security
|
||||
Given I add a new JWT secret "super-secret-key-123456"
|
||||
When the cleanup job runs
|
||||
Then the logs should show masked secret "supe****123456"
|
||||
And not expose the full secret in logs
|
||||
|
||||
@todo
|
||||
Scenario: Cleanup with high volume of secrets
|
||||
Given I have 1000 JWT secrets
|
||||
And 300 of them are expired
|
||||
@@ -102,12 +113,14 @@ Feature: JWT Secret Retention Policy
|
||||
And remove all 300 expired secrets
|
||||
And not impact server performance
|
||||
|
||||
@todo
|
||||
Scenario: Disabled cleanup via configuration
|
||||
Given I set cleanup interval to 8760 hours
|
||||
When I add expired JWT secrets
|
||||
Then they should not be automatically removed
|
||||
And manual cleanup should still be possible
|
||||
|
||||
@todo
|
||||
Scenario: Retention period calculation edge cases
|
||||
Given the JWT TTL is 1 hour
|
||||
And the retention factor is 1.0
|
||||
@@ -115,12 +128,14 @@ Feature: JWT Secret Retention Policy
|
||||
Then the retention period should be 1 hour
|
||||
And the secret should expire after 1 hour
|
||||
|
||||
@todo
|
||||
Scenario: Secret validation with retention policy
|
||||
Given I try to add an invalid JWT secret
|
||||
When the secret is less than 16 characters
|
||||
Then I should receive validation error
|
||||
And the error should mention "must be at least 16 characters"
|
||||
|
||||
@todo
|
||||
Scenario: Cleanup job error handling
|
||||
Given the cleanup job encounters an error
|
||||
When it tries to remove a secret
|
||||
@@ -128,6 +143,7 @@ Feature: JWT Secret Retention Policy
|
||||
And continue with remaining secrets
|
||||
And not crash the cleanup process
|
||||
|
||||
@todo
|
||||
Scenario: Configuration reload without restart
|
||||
Given the server is running with default retention settings
|
||||
When I update the retention factor via configuration
|
||||
@@ -135,6 +151,7 @@ Feature: JWT Secret Retention Policy
|
||||
And existing secrets should be reevaluated
|
||||
And cleanup should use new retention periods
|
||||
|
||||
@todo
|
||||
Scenario: Audit trail for secret operations
|
||||
Given I enable audit logging
|
||||
When I add a new JWT secret
|
||||
@@ -142,6 +159,7 @@ Feature: JWT Secret Retention Policy
|
||||
And when the secret is removed by cleanup
|
||||
Then I should see audit log entry with event type "secret_removed"
|
||||
|
||||
@todo
|
||||
Scenario: Retention policy with token refresh
|
||||
Given a user "refreshuser" exists with password "testpass123"
|
||||
And I authenticate and receive token A
|
||||
@@ -150,13 +168,15 @@ Feature: JWT Secret Retention Policy
|
||||
And token A should still be valid until retention expires
|
||||
And both tokens should work concurrently
|
||||
|
||||
@todo
|
||||
Scenario: Emergency secret rotation
|
||||
Given a security incident requires immediate rotation
|
||||
When I rotate to a new primary secret
|
||||
Then old tokens should be invalidated immediately
|
||||
And new tokens should use the emergency secret
|
||||
And cleanup should remove compromised secrets
|
||||
|
||||
|
||||
@todo
|
||||
Scenario: Monitoring and alerting
|
||||
Given I have monitoring configured
|
||||
When the cleanup job fails repeatedly
|
||||
|
||||
@@ -1,31 +1,14 @@
|
||||
package jwt
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"dance-lessons-coach/pkg/bdd"
|
||||
|
||||
"github.com/cucumber/godog"
|
||||
"dance-lessons-coach/pkg/bdd/testsetup"
|
||||
)
|
||||
|
||||
func TestJWTBDD(t *testing.T) {
|
||||
// Set FEATURE environment variable for feature-specific configuration
|
||||
os.Setenv("FEATURE", "jwt")
|
||||
|
||||
suite := godog.TestSuite{
|
||||
Name: "dance-lessons-coach BDD Tests - JWT Feature",
|
||||
TestSuiteInitializer: bdd.InitializeTestSuite,
|
||||
ScenarioInitializer: bdd.InitializeScenario,
|
||||
Options: &godog.Options{
|
||||
Format: "pretty",
|
||||
Paths: []string{"."},
|
||||
TestingT: t,
|
||||
Strict: true,
|
||||
Randomize: -1,
|
||||
StopOnFailure: true,
|
||||
},
|
||||
}
|
||||
config := testsetup.NewFeatureConfig("jwt", "pretty", false)
|
||||
suite := testsetup.CreateTestSuite(t, config, "dance-lessons-coach BDD Tests - JWT Feature")
|
||||
|
||||
if suite.Run() != 0 {
|
||||
t.Fatal("non-zero status returned, failed to run jwt BDD tests")
|
||||
|
||||
212
pkg/bdd/testsetup/testsetup.go
Normal file
212
pkg/bdd/testsetup/testsetup.go
Normal file
@@ -0,0 +1,212 @@
|
||||
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)
|
||||
}
|
||||
|
||||
// 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: -1,
|
||||
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)
|
||||
}
|
||||
|
||||
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: -1,
|
||||
StopOnFailure: stopOnFailure,
|
||||
Tags: tags,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -122,17 +122,17 @@ run_tests_with_tags() {
|
||||
echo "🏗️ CI environment detected, using service configuration"
|
||||
fi
|
||||
|
||||
# Run tests with proper coverage measurement
|
||||
# Run tests with proper coverage measurement and tag exclusion
|
||||
set +e
|
||||
|
||||
if [ -n "$tags" ]; then
|
||||
# Use godog directly for tag filtering
|
||||
echo "🚀 Running: godog $tags features/"
|
||||
test_output=$(godog $tags features/ 2>&1)
|
||||
# Use godog directly for tag filtering with exclusion
|
||||
echo "🚀 Running: godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip features/"
|
||||
test_output=$(godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip features/ 2>&1)
|
||||
else
|
||||
# Use go test for full test suite
|
||||
echo "🚀 Running: go test ./features/..."
|
||||
test_output=$(go test ./features/... -v -cover -coverpkg=./... -coverprofile=coverage.out 2>&1)
|
||||
# Use go test for full test suite with tag exclusion
|
||||
echo "🚀 Running: go test ./features/... -tags=~@flaky,~@todo,~@skip"
|
||||
test_output=$(go test ./features/... -tags=~@flaky,~@todo,~@skip -v -cover -coverpkg=./... -coverprofile=coverage.out 2>&1)
|
||||
fi
|
||||
|
||||
test_exit_code=$?
|
||||
|
||||
@@ -43,9 +43,9 @@ run_feature_test() {
|
||||
docker exec dance-lessons-coach-postgres createdb -U postgres "${DLC_DATABASE_NAME}"
|
||||
fi
|
||||
|
||||
# Run the feature tests
|
||||
# Run the feature tests with tag exclusion
|
||||
cd "features/${feature_name}"
|
||||
FEATURE=${feature_name} DLC_DATABASE_NAME="${DLC_DATABASE_NAME}" go test -v . 2>&1 | grep -E "(PASS|FAIL|RUN)" || true
|
||||
FEATURE=${feature_name} DLC_DATABASE_NAME="${DLC_DATABASE_NAME}" go test -v . -tags="~@flaky && ~@todo && ~@skip" 2>&1 | grep -E "(PASS|FAIL|RUN)" || true
|
||||
|
||||
# Cleanup
|
||||
cd ../..
|
||||
|
||||
@@ -108,9 +108,9 @@ run_feature_tests() {
|
||||
export DLC_DATABASE_SSL_MODE="disable"
|
||||
export DLC_CONFIG_FILE="${CONFIG}"
|
||||
|
||||
# Run tests with proper coverage measurement
|
||||
# Run tests with proper coverage measurement and tag exclusion
|
||||
set +e
|
||||
test_output=$(go test ./features/${FEATURE}/... -v -cover -coverpkg=./... -coverprofile=coverage-${FEATURE}.out 2>&1)
|
||||
test_output=$(go test ./features/${FEATURE}/... -tags=~@flaky,~@todo,~@skip -v -cover -coverpkg=./... -coverprofile=coverage-${FEATURE}.out 2>&1)
|
||||
test_exit_code=$?
|
||||
set -e
|
||||
|
||||
|
||||
Reference in New Issue
Block a user