🧪 test: add JWT secret rotation BDD scenarios and step implementations #12

Merged
arcodange merged 72 commits from feature/jwt-secret-rotation into main 2026-04-11 17:56:47 +02:00
13 changed files with 44 additions and 31 deletions
Showing only changes of commit 1f92302eff - Show all commits

View File

@@ -65,7 +65,7 @@ GODOG_TAGS="@jwt && ~@todo" go test ./features/...
DLC_DATABASE_HOST=localhost GODOG_TAGS="@wip" go test ./features/jwt/... DLC_DATABASE_HOST=localhost GODOG_TAGS="@wip" go test ./features/jwt/...
``` ```
**Default Behavior:** If `GODOG_TAGS` is not set, the test uses the default tag filter: `~@flaky && ~@todo && ~@skip && @wip` **Default Behavior:** If `GODOG_TAGS` is not set, the test uses the default tag filter: `~@flaky && ~@todo && ~@skip`
## Usage Examples ## Usage Examples

View File

@@ -17,7 +17,7 @@ func TestAuthBDD(t *testing.T) {
tags := os.Getenv("GODOG_TAGS") tags := os.Getenv("GODOG_TAGS")
if tags == "" { if tags == "" {
// Default tags if not overridden // Default tags if not overridden
tags = "~@flaky && ~@todo && ~@skip && @wip" tags = "~@flaky && ~@todo && ~@skip"
} }
suite := godog.TestSuite{ suite := godog.TestSuite{
@@ -30,7 +30,7 @@ func TestAuthBDD(t *testing.T) {
TestingT: t, TestingT: t,
Strict: true, Strict: true,
Randomize: -1, Randomize: -1,
StopOnFailure: true, StopOnFailure: false,
Tags: tags, Tags: tags,
}, },
} }

View File

@@ -32,6 +32,13 @@ func TestBDD(t *testing.T) {
paths = []string{feature} paths = []string{feature}
} }
// Allow tag override via environment variable
tags := os.Getenv("GODOG_TAGS")
if tags == "" {
// Default tags if not overridden
tags = "~@flaky && ~@todo && ~@skip"
}
suite := godog.TestSuite{ suite := godog.TestSuite{
Name: suiteName, Name: suiteName,
TestSuiteInitializer: bdd.InitializeTestSuite, TestSuiteInitializer: bdd.InitializeTestSuite,
@@ -42,7 +49,8 @@ func TestBDD(t *testing.T) {
TestingT: t, TestingT: t,
Strict: true, Strict: true,
Randomize: -1, Randomize: -1,
// StopOnFailure: true, StopOnFailure: false,
Tags: tags,
}, },
} }

View File

@@ -2,12 +2,14 @@
Feature: Config Hot Reloading Feature: Config Hot Reloading
The system should support selective hot reloading of configuration changes The system should support selective hot reloading of configuration changes
@flaky
Scenario: Hot reloading logging level changes Scenario: Hot reloading logging level changes
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
When I update the logging level to "debug" in the config file When I update the logging level to "debug" in the config file
Then the logging level should be updated without restart Then the logging level should be updated without restart
And debug logs should appear in the output And debug logs should appear in the output
@flaky
Scenario: Hot reloading feature flags Scenario: Hot reloading feature flags
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
And the v2 API is disabled And the v2 API is disabled
@@ -15,6 +17,7 @@ Feature: Config Hot Reloading
Then the v2 API should become available without restart Then the v2 API should become available without restart
And v2 API requests should succeed And v2 API requests should succeed
@flaky
Scenario: Hot reloading telemetry sampling settings Scenario: Hot reloading telemetry sampling settings
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
And telemetry is enabled And telemetry is enabled
@@ -23,6 +26,7 @@ Feature: Config Hot Reloading
Then the telemetry sampling should be updated without restart Then the telemetry sampling should be updated without restart
And the new sampling settings should be applied And the new sampling settings should be applied
@flaky
Scenario: Hot reloading JWT TTL Scenario: Hot reloading JWT TTL
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
And JWT TTL is set to 1 hour 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 Then the JWT TTL should be updated without restart
And new JWT tokens should have the updated expiration And new JWT tokens should have the updated expiration
@flaky
Scenario: Attempting to hot reload non-reloadable settings should be ignored Scenario: Attempting to hot reload non-reloadable settings should be ignored
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
When I update the server port to 9090 in the config file 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 the server should continue running on the original port
And a warning should be logged about ignored configuration change And a warning should be logged about ignored configuration change
@flaky
Scenario: Invalid configuration changes should be handled gracefully Scenario: Invalid configuration changes should be handled gracefully
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
When I update the logging level to "invalid_level" in the config file 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 an error should be logged about invalid configuration
And the server should continue running normally And the server should continue running normally
@flaky
Scenario: Config file monitoring should handle file deletion gracefully Scenario: Config file monitoring should handle file deletion gracefully
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
When I delete the config file When I delete the config file
Then the server should continue running with last known good configuration Then the server should continue running with last known good configuration
And a warning should be logged about missing config file And a warning should be logged about missing config file
@flaky
Scenario: Config file monitoring should handle file recreation Scenario: Config file monitoring should handle file recreation
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
And I have deleted the config file And I have deleted the config file
@@ -57,6 +65,7 @@ Feature: Config Hot Reloading
Then the server should reload the configuration Then the server should reload the configuration
And the new configuration should be applied And the new configuration should be applied
@flaky
Scenario: Multiple rapid configuration changes should be handled Scenario: Multiple rapid configuration changes should be handled
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
When I rapidly update the logging level multiple times 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 the final configuration should be applied
And no configuration changes should be lost And no configuration changes should be lost
@flaky
Scenario: Configuration changes should be audited Scenario: Configuration changes should be audited
Given the server is running with config file monitoring enabled Given the server is running with config file monitoring enabled
And audit logging is enabled And audit logging is enabled

View File

@@ -17,7 +17,7 @@ func TestConfigBDD(t *testing.T) {
tags := os.Getenv("GODOG_TAGS") tags := os.Getenv("GODOG_TAGS")
if tags == "" { if tags == "" {
// Default tags if not overridden // Default tags if not overridden
tags = "~@flaky && ~@todo && ~@skip && @wip" tags = "~@flaky && ~@todo && ~@skip"
} }
suite := godog.TestSuite{ suite := godog.TestSuite{

View File

@@ -17,7 +17,7 @@ func TestGreetBDD(t *testing.T) {
tags := os.Getenv("GODOG_TAGS") tags := os.Getenv("GODOG_TAGS")
if tags == "" { if tags == "" {
// Default tags if not overridden // Default tags if not overridden
tags = "~@flaky && ~@todo && ~@skip && @wip" tags = "~@flaky && ~@todo && ~@skip"
} }
suite := godog.TestSuite{ suite := godog.TestSuite{
@@ -30,7 +30,7 @@ func TestGreetBDD(t *testing.T) {
TestingT: t, TestingT: t,
Strict: true, Strict: true,
Randomize: -1, Randomize: -1,
StopOnFailure: true, StopOnFailure: false,
Tags: tags, Tags: tags,
}, },
} }

View File

@@ -17,7 +17,7 @@ func TestHealthBDD(t *testing.T) {
tags := os.Getenv("GODOG_TAGS") tags := os.Getenv("GODOG_TAGS")
if tags == "" { if tags == "" {
// Default tags if not overridden // Default tags if not overridden
tags = "~@flaky && ~@todo && ~@skip && @wip" tags = "~@flaky && ~@todo && ~@skip"
} }
suite := godog.TestSuite{ suite := godog.TestSuite{
@@ -30,7 +30,7 @@ func TestHealthBDD(t *testing.T) {
TestingT: t, TestingT: t,
Strict: true, Strict: true,
Randomize: -1, Randomize: -1,
StopOnFailure: true, StopOnFailure: false,
Tags: tags, Tags: tags,
}, },
} }

View File

@@ -4,7 +4,6 @@ Feature: JWT Secret Rotation
I want to rotate JWT secrets without disrupting users I want to rotate JWT secrets without disrupting users
So that we can maintain security while ensuring continuous service So that we can maintain security while ensuring continuous service
@wip
Scenario: Authentication with multiple valid JWT secrets Scenario: Authentication with multiple valid JWT secrets
Given the server is running with multiple JWT secrets Given the server is running with multiple JWT secrets
And a user "multiuser" exists with password "testpass123" And a user "multiuser" exists with password "testpass123"
@@ -12,7 +11,6 @@ Feature: JWT Secret Rotation
Then the authentication should be successful Then the authentication should be successful
And I should receive a valid JWT token signed with the primary secret And I should receive a valid JWT token signed with the primary secret
@todo
Scenario: Token validation with multiple valid secrets Scenario: Token validation with multiple valid secrets
Given the server is running with multiple JWT secrets Given the server is running with multiple JWT secrets
And a user "tokenuser" exists with password "testpass123" And a user "tokenuser" exists with password "testpass123"
@@ -23,7 +21,6 @@ Feature: JWT Secret Rotation
Then the token should be valid Then the token should be valid
And it should contain the correct user ID And it should contain the correct user ID
@todo
Scenario: Secret rotation - adding new secret while keeping old one valid Scenario: Secret rotation - adding new secret while keeping old one valid
Given the server is running with primary JWT secret Given the server is running with primary JWT secret
And a user "rotateuser" exists with password "testpass123" And a user "rotateuser" exists with password "testpass123"
@@ -37,14 +34,12 @@ Feature: JWT Secret Rotation
When I validate the old JWT token signed with primary secret When I validate the old JWT token signed with primary secret
Then the token should still be valid Then the token should still be valid
@todo
Scenario: Token rejection after secret expiration Scenario: Token rejection after secret expiration
Given the server is running with primary and expired secondary JWT secrets Given the server is running with primary and expired secondary JWT secrets
When I use a JWT token signed with the expired secondary secret for authentication When I use a JWT token signed with the expired secondary secret for authentication
Then the authentication should fail Then the authentication should fail
And the response should contain error "invalid_token" And the response should contain error "invalid_token"
@todo
Scenario: Graceful secret rotation with user continuity Scenario: Graceful secret rotation with user continuity
Given the server is running with primary JWT secret Given the server is running with primary JWT secret
And a user "gracefuluser" exists with password "testpass123" And a user "gracefuluser" exists with password "testpass123"

View File

@@ -17,7 +17,7 @@ func TestJWTBDD(t *testing.T) {
tags := os.Getenv("GODOG_TAGS") tags := os.Getenv("GODOG_TAGS")
if tags == "" { if tags == "" {
// Default tags if not overridden // Default tags if not overridden
tags = "~@flaky && ~@todo && ~@skip && @wip" tags = "~@flaky && ~@todo && ~@skip"
} }
suite := godog.TestSuite{ suite := godog.TestSuite{
@@ -30,7 +30,7 @@ func TestJWTBDD(t *testing.T) {
TestingT: t, TestingT: t,
Strict: true, Strict: true,
Randomize: -1, Randomize: -1,
StopOnFailure: true, StopOnFailure: false,
Tags: tags, Tags: tags,
}, },
} }

View File

@@ -126,13 +126,13 @@ run_tests_with_tags() {
set +e set +e
if [ -n "$tags" ]; then if [ -n "$tags" ]; then
# Use godog directly for tag filtering with exclusion and WIP inclusion # Use godog directly for tag filtering with exclusion
echo "🚀 Running: godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip --tags=@wip features/" echo "🚀 Running: godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip features/"
test_output=$(godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip --tags=@wip features/ 2>&1) test_output=$(godog $tags --tags=~@flaky --tags=~@todo --tags=~@skip features/ 2>&1)
else else
# Use go test for full test suite with tag exclusion and WIP inclusion # Use go test for full test suite with tag exclusion
echo "🚀 Running: go test ./features/... -tags=~@flaky,~@todo,~@skip,@wip" echo "🚀 Running: go test ./features/... -tags=~@flaky,~@todo,~@skip"
test_output=$(go test ./features/... -tags=~@flaky,~@todo,~@skip,@wip -v -cover -coverpkg=./... -coverprofile=coverage.out 2>&1) test_output=$(go test ./features/... -tags=~@flaky,~@todo,~@skip -v -cover -coverpkg=./... -coverprofile=coverage.out 2>&1)
fi fi
test_exit_code=$? test_exit_code=$?

View File

@@ -43,9 +43,9 @@ run_feature_test() {
docker exec dance-lessons-coach-postgres createdb -U postgres "${DLC_DATABASE_NAME}" docker exec dance-lessons-coach-postgres createdb -U postgres "${DLC_DATABASE_NAME}"
fi fi
# Run the feature tests with tag exclusion and WIP inclusion # Run the feature tests with tag exclusion
cd "features/${feature_name}" cd "features/${feature_name}"
FEATURE=${feature_name} DLC_DATABASE_NAME="${DLC_DATABASE_NAME}" go test -v . -tags="~@flaky && ~@todo && ~@skip && @wip" 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 # Cleanup
cd ../.. cd ../..

View File

@@ -110,7 +110,7 @@ run_feature_tests() {
# Run tests with proper coverage measurement and tag exclusion # Run tests with proper coverage measurement and tag exclusion
set +e set +e
test_output=$(go test ./features/${FEATURE}/... -tags=~@flaky,~@todo,~@skip,@wip -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=$? test_exit_code=$?
set -e set -e