🧪 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
4 changed files with 62 additions and 61 deletions
Showing only changes of commit 168efd3e99 - Show all commits

View File

@@ -23,7 +23,7 @@ Feature: JWT Secret Retention Policy
And the retention factor is 3.0 And the retention factor is 3.0
When I add a new JWT secret When I add a new JWT secret
Then the secret should expire after 6 hours Then the secret should expire after 6 hours
And the retention period should be calculated as "2h × 3.0 = 6h" And the retention period should be 6 hours
Scenario: Maximum retention period enforcement Scenario: Maximum retention period enforcement
Given the JWT TTL is set to 72 hours Given the JWT TTL is set to 72 hours

View File

@@ -8,6 +8,7 @@ import (
"dance-lessons-coach/pkg/bdd/testserver" "dance-lessons-coach/pkg/bdd/testserver"
"github.com/cucumber/godog"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
@@ -182,7 +183,7 @@ func (s *AuthSteps) theRegistrationShouldBeSuccessful() error {
func (s *AuthSteps) iShouldBeAbleToAuthenticateWithTheNewCredentials() error { func (s *AuthSteps) iShouldBeAbleToAuthenticateWithTheNewCredentials() error {
// This is the same as regular authentication // This is the same as regular authentication
return nil return godog.ErrPending
} }
func (s *AuthSteps) iAmAuthenticatedAsAdmin() error { func (s *AuthSteps) iAmAuthenticatedAsAdmin() error {
@@ -212,7 +213,7 @@ func (s *AuthSteps) thePasswordResetShouldBeAllowed() error {
func (s *AuthSteps) theUserShouldBeFlaggedForPasswordReset() error { func (s *AuthSteps) theUserShouldBeFlaggedForPasswordReset() error {
// This is verified by the password reset request being successful // This is verified by the password reset request being successful
return nil return godog.ErrPending
} }
func (s *AuthSteps) iCompletePasswordResetForWithNewPassword(username, password string) error { func (s *AuthSteps) iCompletePasswordResetForWithNewPassword(username, password string) error {
@@ -251,7 +252,7 @@ func (s *AuthSteps) thePasswordResetShouldBeSuccessful() error {
func (s *AuthSteps) iShouldBeAbleToAuthenticateWithTheNewPassword() error { func (s *AuthSteps) iShouldBeAbleToAuthenticateWithTheNewPassword() error {
// This is the same as regular authentication // This is the same as regular authentication
return nil return godog.ErrPending
} }
func (s *AuthSteps) thePasswordResetShouldFail() error { func (s *AuthSteps) thePasswordResetShouldFail() error {

View File

@@ -33,19 +33,25 @@ func (s *JWTRetentionSteps) theServerIsRunningWithJWTSecretRetentionConfigured()
func (s *JWTRetentionSteps) theDefaultJWTTTLIsHours(hours int) error { func (s *JWTRetentionSteps) theDefaultJWTTTLIsHours(hours int) error {
// This would verify the default TTL configuration // This would verify the default TTL configuration
// For now, we'll just verify server is running // For now, we'll just verify server is running
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theRetentionFactorIs(factor float64) error { func (s *JWTRetentionSteps) theRetentionFactorIs(factor float64) error {
// This would set the retention factor // This would set the retention factor
// For now, we'll store it for reference // For now, we'll store it for reference
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theMaximumRetentionIsHours(hours int) error { func (s *JWTRetentionSteps) theMaximumRetentionIsHours(hours int) error {
// This would set the maximum retention // This would set the maximum retention
// For now, we'll store it for reference // For now, we'll store it for reference
return nil return godog.ErrPending
}
func (s *JWTRetentionSteps) theRetentionPeriodShouldBeHours(hours int) error {
// This would verify the retention period calculation
// For now, we'll just verify server is running
return godog.ErrPending
} }
// Secret Management Steps // Secret Management Steps
@@ -69,13 +75,13 @@ func (s *JWTRetentionSteps) iAddASecondaryJWTSecretWithHourExpiration(hours int)
func (s *JWTRetentionSteps) iWaitForTheRetentionPeriodToElapse() error { func (s *JWTRetentionSteps) iWaitForTheRetentionPeriodToElapse() error {
// Simulate waiting for retention period // Simulate waiting for retention period
// In real implementation, this would actually wait or mock time // In real implementation, this would actually wait or mock time
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theExpiredSecondarySecretShouldBeAutomaticallyRemoved() error { func (s *JWTRetentionSteps) theExpiredSecondarySecretShouldBeAutomaticallyRemoved() error {
// Verify the secondary secret is no longer valid // Verify the secondary secret is no longer valid
// Try to authenticate with it - should fail // Try to authenticate with it - should fail
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) thePrimarySecretShouldRemainActive() error { func (s *JWTRetentionSteps) thePrimarySecretShouldRemainActive() error {
@@ -87,42 +93,36 @@ func (s *JWTRetentionSteps) thePrimarySecretShouldRemainActive() error {
func (s *JWTRetentionSteps) iShouldSeeCleanupEventInLogs() error { func (s *JWTRetentionSteps) iShouldSeeCleanupEventInLogs() error {
// Check logs for cleanup events // Check logs for cleanup events
// In real implementation, this would verify log output // In real implementation, this would verify log output
return nil return godog.ErrPending
} }
// Retention Calculation Steps // Retention Calculation Steps
func (s *JWTRetentionSteps) theJWTTTLIsSetToHours(hours int) error { func (s *JWTRetentionSteps) theJWTTTLIsSetToHours(hours int) error {
// Set JWT TTL // Set JWT TTL
return nil return godog.ErrPending
}
func (s *JWTRetentionSteps) theRetentionPeriodShouldBeCalculatedAs(formula string) error {
// Verify retention period calculation
// Parse formula and validate
return nil
} }
func (s *JWTRetentionSteps) theRetentionPeriodShouldBeCappedAtHours(hours int) error { func (s *JWTRetentionSteps) theRetentionPeriodShouldBeCappedAtHours(hours int) error {
// Verify maximum retention enforcement // Verify maximum retention enforcement
return nil return godog.ErrPending
} }
// Cleanup Frequency Steps // Cleanup Frequency Steps
func (s *JWTRetentionSteps) theCleanupIntervalIsSetToMinutes(minutes int) error { func (s *JWTRetentionSteps) theCleanupIntervalIsSetToMinutes(minutes int) error {
// Set cleanup interval // Set cleanup interval
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) itShouldBeRemovedWithinMinutes(minutes int) error { func (s *JWTRetentionSteps) itShouldBeRemovedWithinMinutes(minutes int) error {
// Verify timely removal // Verify timely removal
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iShouldSeeCleanupEventsEveryMinutes(minutes int) error { func (s *JWTRetentionSteps) iShouldSeeCleanupEventsEveryMinutes(minutes int) error {
// Verify regular cleanup events // Verify regular cleanup events
return nil return godog.ErrPending
} }
// Token Validation Steps // Token Validation Steps
@@ -152,7 +152,7 @@ func (s *JWTRetentionSteps) iReceiveAValidJWTTokenSignedWithCurrentSecret() erro
func (s *JWTRetentionSteps) iWaitForTheSecretToExpire() error { func (s *JWTRetentionSteps) iWaitForTheSecretToExpire() error {
// Simulate waiting for secret expiration // Simulate waiting for secret expiration
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iTryToValidateTheExpiredToken() error { func (s *JWTRetentionSteps) iTryToValidateTheExpiredToken() error {
@@ -193,34 +193,34 @@ func (s *JWTRetentionSteps) iTryToStartTheServer() error {
func (s *JWTRetentionSteps) iShouldReceiveConfigurationValidationError() error { func (s *JWTRetentionSteps) iShouldReceiveConfigurationValidationError() error {
// Verify validation error // Verify validation error
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theErrorShouldMention(message string) error { func (s *JWTRetentionSteps) theErrorShouldMention(message string) error {
// Verify error message content // Verify error message content
return nil return godog.ErrPending
} }
// Metrics Steps // Metrics Steps
func (s *JWTRetentionSteps) iHaveEnabledPrometheusMetrics() error { func (s *JWTRetentionSteps) iHaveEnabledPrometheusMetrics() error {
// Enable metrics in configuration // Enable metrics in configuration
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iShouldSeeMetricIncrement(metric string) error { func (s *JWTRetentionSteps) iShouldSeeMetricIncrement(metric string) error {
// Verify metric was incremented // Verify metric was incremented
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iShouldSeeMetricDecrease(metric string) error { func (s *JWTRetentionSteps) iShouldSeeMetricDecrease(metric string) error {
// Verify metric was decremented // Verify metric was decremented
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iShouldSeeHistogramUpdate(metric string) error { func (s *JWTRetentionSteps) iShouldSeeHistogramUpdate(metric string) error {
// Verify histogram was updated // Verify histogram was updated
return nil return godog.ErrPending
} }
// Logging Steps // Logging Steps
@@ -251,58 +251,58 @@ func (s *JWTRetentionSteps) theLogsShouldShowMaskedSecret(masked string) error {
func (s *JWTRetentionSteps) theLogsShouldNotExposeTheFullSecret() error { func (s *JWTRetentionSteps) theLogsShouldNotExposeTheFullSecret() error {
// Verify no full secret exposure // Verify no full secret exposure
return nil return godog.ErrPending
} }
// Performance Steps // Performance Steps
func (s *JWTRetentionSteps) iHaveJWTSecrets(count int) error { func (s *JWTRetentionSteps) iHaveJWTSecrets(count int) error {
// Simulate having many secrets // Simulate having many secrets
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) ofThemAreExpired(expiredCount int) error { func (s *JWTRetentionSteps) ofThemAreExpired(expiredCount int) error {
// Simulate expired secrets // Simulate expired secrets
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) itShouldCompleteWithinMilliseconds(ms int) error { func (s *JWTRetentionSteps) itShouldCompleteWithinMilliseconds(ms int) error {
// Verify performance // Verify performance
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andNotImpactServerPerformance() error { func (s *JWTRetentionSteps) andNotImpactServerPerformance() error {
// Verify no performance impact // Verify no performance impact
return nil return godog.ErrPending
} }
// Configuration Management Steps // Configuration Management Steps
func (s *JWTRetentionSteps) iSetCleanupIntervalToHours(hours int) error { func (s *JWTRetentionSteps) iSetCleanupIntervalToHours(hours int) error {
// Set very high cleanup interval (effectively disabled) // Set very high cleanup interval (effectively disabled)
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theyShouldNotBeAutomaticallyRemoved() error { func (s *JWTRetentionSteps) theyShouldNotBeAutomaticallyRemoved() error {
// Verify no automatic cleanup // Verify no automatic cleanup
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andManualCleanupShouldStillBePossible() error { func (s *JWTRetentionSteps) andManualCleanupShouldStillBePossible() error {
// Verify manual cleanup still works // Verify manual cleanup still works
return nil return godog.ErrPending
} }
// Edge Case Steps // Edge Case Steps
func (s *JWTRetentionSteps) theRetentionPeriodShouldBeHour() error { func (s *JWTRetentionSteps) theRetentionPeriodShouldBeHour() error {
// Verify 1-hour retention // Verify 1-hour retention
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theSecretShouldExpireAfterHour() error { func (s *JWTRetentionSteps) theSecretShouldExpireAfterHour() error {
// Verify expiration timing // Verify expiration timing
return nil return godog.ErrPending
} }
// Validation Steps // Validation Steps
@@ -336,61 +336,61 @@ func (s *JWTRetentionSteps) theErrorShouldMentionMinimumCharacters() error {
func (s *JWTRetentionSteps) theCleanupJobEncountersAnError() error { func (s *JWTRetentionSteps) theCleanupJobEncountersAnError() error {
// Simulate cleanup error // Simulate cleanup error
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) itShouldLogTheError() error { func (s *JWTRetentionSteps) itShouldLogTheError() error {
// Verify error logging // Verify error logging
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andContinueWithRemainingSecrets() error { func (s *JWTRetentionSteps) andContinueWithRemainingSecrets() error {
// Verify continuation // Verify continuation
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andNotCrashTheCleanupProcess() error { func (s *JWTRetentionSteps) andNotCrashTheCleanupProcess() error {
// Verify process doesn't crash // Verify process doesn't crash
return nil return godog.ErrPending
} }
// Configuration Reload Steps // Configuration Reload Steps
func (s *JWTRetentionSteps) theServerIsRunningWithDefaultRetentionSettings() error { func (s *JWTRetentionSteps) theServerIsRunningWithDefaultRetentionSettings() error {
// Verify default settings // Verify default settings
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iUpdateTheRetentionFactorViaConfiguration() error { func (s *JWTRetentionSteps) iUpdateTheRetentionFactorViaConfiguration() error {
// Update configuration // Update configuration
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theNewSettingsShouldTakeEffectImmediately() error { func (s *JWTRetentionSteps) theNewSettingsShouldTakeEffectImmediately() error {
// Verify immediate effect // Verify immediate effect
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andExistingSecretsShouldBeReevaluated() error { func (s *JWTRetentionSteps) andExistingSecretsShouldBeReevaluated() error {
// Verify reevaluation // Verify reevaluation
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andCleanupShouldUseNewRetentionPeriods() error { func (s *JWTRetentionSteps) andCleanupShouldUseNewRetentionPeriods() error {
// Verify new periods used // Verify new periods used
return nil return godog.ErrPending
} }
// Audit Trail Steps // Audit Trail Steps
func (s *JWTRetentionSteps) iEnableAuditLogging() error { func (s *JWTRetentionSteps) iEnableAuditLogging() error {
// Enable audit logging // Enable audit logging
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iShouldSeeAuditLogEntryWithEventType(eventType string) error { func (s *JWTRetentionSteps) iShouldSeeAuditLogEntryWithEventType(eventType string) error {
// Verify audit log entry // Verify audit log entry
return nil return godog.ErrPending
} }
// Token Refresh Steps // Token Refresh Steps
@@ -413,17 +413,17 @@ func (s *JWTRetentionSteps) iRefreshMyTokenDuringRetentionPeriod() error {
func (s *JWTRetentionSteps) iShouldReceiveNewTokenB() error { func (s *JWTRetentionSteps) iShouldReceiveNewTokenB() error {
// Verify new token received // Verify new token received
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andTokenAShouldStillBeValidUntilRetentionExpires() error { func (s *JWTRetentionSteps) andTokenAShouldStillBeValidUntilRetentionExpires() error {
// Verify old token still works // Verify old token still works
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andBothTokensShouldWorkConcurrently() error { func (s *JWTRetentionSteps) andBothTokensShouldWorkConcurrently() error {
// Verify concurrent validity // Verify concurrent validity
return nil return godog.ErrPending
} }
// Emergency Rotation Steps // Emergency Rotation Steps
@@ -437,17 +437,17 @@ func (s *JWTRetentionSteps) iRotateToANewPrimarySecret() error {
func (s *JWTRetentionSteps) oldTokensShouldBeInvalidatedImmediately() error { func (s *JWTRetentionSteps) oldTokensShouldBeInvalidatedImmediately() error {
// Verify immediate invalidation // Verify immediate invalidation
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andNewTokensShouldUseTheEmergencySecret() error { func (s *JWTRetentionSteps) andNewTokensShouldUseTheEmergencySecret() error {
// Verify new tokens use emergency secret // Verify new tokens use emergency secret
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andCleanupShouldRemoveCompromisedSecrets() error { func (s *JWTRetentionSteps) andCleanupShouldRemoveCompromisedSecrets() error {
// Verify compromised secrets removed // Verify compromised secrets removed
return nil return godog.ErrPending
} }
// Additional missing steps for JWT retention // Additional missing steps for JWT retention
@@ -639,25 +639,25 @@ func (s *JWTRetentionSteps) whenTheSecretIsRemovedByCleanup() error {
func (s *JWTRetentionSteps) iHaveMonitoringConfigured() error { func (s *JWTRetentionSteps) iHaveMonitoringConfigured() error {
// Configure monitoring // Configure monitoring
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theCleanupJobFailsRepeatedly() error { func (s *JWTRetentionSteps) theCleanupJobFailsRepeatedly() error {
// Simulate repeated failures // Simulate repeated failures
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) iShouldReceiveAlertNotification() error { func (s *JWTRetentionSteps) iShouldReceiveAlertNotification() error {
// Verify alert received // Verify alert received
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) theAlertShouldIncludeErrorDetails() error { func (s *JWTRetentionSteps) theAlertShouldIncludeErrorDetails() error {
// Verify error details included // Verify error details included
return nil return godog.ErrPending
} }
func (s *JWTRetentionSteps) andSuggestRemediationSteps() error { func (s *JWTRetentionSteps) andSuggestRemediationSteps() error {
// Verify remediation suggestions // Verify remediation suggestions
return nil return godog.ErrPending
} }

View File

@@ -106,8 +106,8 @@ func InitializeAllSteps(ctx *godog.ScenarioContext, client *testserver.Client) {
ctx.Step(`^the primary secret should remain active$`, sc.jwtRetentionSteps.thePrimarySecretShouldRemainActive) ctx.Step(`^the primary secret should remain active$`, sc.jwtRetentionSteps.thePrimarySecretShouldRemainActive)
ctx.Step(`^I should see cleanup event in logs$`, sc.jwtRetentionSteps.iShouldSeeCleanupEventInLogs) ctx.Step(`^I should see cleanup event in logs$`, sc.jwtRetentionSteps.iShouldSeeCleanupEventInLogs)
ctx.Step(`^the JWT TTL is set to (\d+) hours$`, sc.jwtRetentionSteps.theJWTTTLIsSetToHours) ctx.Step(`^the JWT TTL is set to (\d+) hours$`, sc.jwtRetentionSteps.theJWTTTLIsSetToHours)
ctx.Step(`^the retention period should be calculated as "([^"]*)"$`, sc.jwtRetentionSteps.theRetentionPeriodShouldBeCalculatedAs)
ctx.Step(`^the retention period should be capped at (\d+) hours$`, sc.jwtRetentionSteps.theRetentionPeriodShouldBeCappedAtHours) ctx.Step(`^the retention period should be capped at (\d+) hours$`, sc.jwtRetentionSteps.theRetentionPeriodShouldBeCappedAtHours)
ctx.Step(`^the retention period should be (\d+) hours$`, sc.jwtRetentionSteps.theRetentionPeriodShouldBeHours)
ctx.Step(`^the cleanup interval is set to (\d+) minutes$`, sc.jwtRetentionSteps.theCleanupIntervalIsSetToMinutes) ctx.Step(`^the cleanup interval is set to (\d+) minutes$`, sc.jwtRetentionSteps.theCleanupIntervalIsSetToMinutes)
ctx.Step(`^it should be removed within (\d+) minutes$`, sc.jwtRetentionSteps.itShouldBeRemovedWithinMinutes) ctx.Step(`^it should be removed within (\d+) minutes$`, sc.jwtRetentionSteps.itShouldBeRemovedWithinMinutes)
ctx.Step(`^I should see cleanup events every (\d+) minutes$`, sc.jwtRetentionSteps.iShouldSeeCleanupEventsEveryMinutes) ctx.Step(`^I should see cleanup events every (\d+) minutes$`, sc.jwtRetentionSteps.iShouldSeeCleanupEventsEveryMinutes)