Files
dance-lessons-coach/pkg/bdd/steps/steps.go
Gabriel Radureau 424eeab7d9 🧪 test: add failing BDD tests for user authentication system
Added comprehensive BDD feature file and step definitions for user authentication
following ADR-0018. All tests are failing as expected per TDD practice.

- Created features/user_authentication.feature with 7 scenarios
- Added 17 step definitions for authentication flows
- Tests cover: user auth, admin auth, registration, password reset
- All tests fail with descriptive error messages

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-04-06 22:56:12 +02:00

221 lines
9.4 KiB
Go

package steps
import (
"dance-lessons-coach/pkg/bdd/testserver"
"fmt"
"strings"
"github.com/cucumber/godog"
)
// StepContext holds the test client and implements all step definitions
type StepContext struct {
client *testserver.Client
}
// NewStepContext creates a new step context
func NewStepContext(client *testserver.Client) *StepContext {
return &StepContext{client: client}
}
// InitializeAllSteps registers all step definitions for the BDD tests
func InitializeAllSteps(ctx *godog.ScenarioContext, client *testserver.Client) {
sc := NewStepContext(client)
ctx.Step(`^I request a greeting for "([^"]*)"$`, sc.iRequestAGreetingFor)
ctx.Step(`^I request the default greeting$`, sc.iRequestTheDefaultGreeting)
ctx.Step(`^I request the health endpoint$`, sc.iRequestTheHealthEndpoint)
ctx.Step(`^the response should be "{\\"([^"]*)":\\"([^"]*)"}"$`, sc.theResponseShouldBe)
ctx.Step(`^the server is running$`, sc.theServerIsRunning)
ctx.Step(`^the server is running with v2 enabled$`, sc.theServerIsRunningWithV2Enabled)
ctx.Step(`^I send a POST request to v2 greet with name "([^"]*)"$`, sc.iSendPOSTRequestToV2GreetWithName)
ctx.Step(`^I send a POST request to v2 greet with invalid JSON "([^"]*)"$`, sc.iSendPOSTRequestToV2GreetWithInvalidJSON)
ctx.Step(`^the response should contain error "([^"]*)"$`, sc.theResponseShouldContainError)
// User Authentication Steps
ctx.Step(`^a user "([^"]*)" exists with password "([^"]*)"$`, sc.aUserExistsWithPassword)
ctx.Step(`^I authenticate with username "([^"]*)" and password "([^"]*)"$`, sc.iAuthenticateWithUsernameAndPassword)
ctx.Step(`^the authentication should be successful$`, sc.theAuthenticationShouldBeSuccessful)
ctx.Step(`^I should receive a valid JWT token$`, sc.iShouldReceiveAValidJWTToken)
ctx.Step(`^the authentication should fail$`, sc.theAuthenticationShouldFail)
ctx.Step(`^I authenticate as admin with master password "([^"]*)"$`, sc.iAuthenticateAsAdminWithMasterPassword)
ctx.Step(`^the token should contain admin claims$`, sc.theTokenShouldContainAdminClaims)
ctx.Step(`^I register a new user "([^"]*)" with password "([^"]*)"$`, sc.iRegisterANewUserWithPassword)
ctx.Step(`^the registration should be successful$`, sc.theRegistrationShouldBeSuccessful)
ctx.Step(`^I should be able to authenticate with the new credentials$`, sc.iShouldBeAbleToAuthenticateWithTheNewCredentials)
ctx.Step(`^I am authenticated as admin$`, sc.iAmAuthenticatedAsAdmin)
ctx.Step(`^I request password reset for user "([^"]*)"$`, sc.iRequestPasswordResetForUser)
ctx.Step(`^the password reset should be allowed$`, sc.thePasswordResetShouldBeAllowed)
ctx.Step(`^the user should be flagged for password reset$`, sc.theUserShouldBeFlaggedForPasswordReset)
ctx.Step(`^I complete password reset for "([^"]*)" with new password "([^"]*)"$`, sc.iCompletePasswordResetForWithNewPassword)
ctx.Step(`^I should be able to authenticate with the new password$`, sc.iShouldBeAbleToAuthenticateWithTheNewPassword)
ctx.Step(`^a user "([^"]*)" exists and is flagged for password reset$`, sc.aUserExistsAndIsFlaggedForPasswordReset)
ctx.Step(`^the password reset should be successful$`, sc.thePasswordResetShouldBeSuccessful)
}
func (sc *StepContext) iRequestAGreetingFor(name string) error {
return sc.client.Request("GET", fmt.Sprintf("/api/v1/greet/%s", name), nil)
}
func (sc *StepContext) iRequestTheDefaultGreeting() error {
return sc.client.Request("GET", "/api/v1/greet/", nil)
}
func (sc *StepContext) iRequestTheHealthEndpoint() error {
return sc.client.Request("GET", "/api/health", nil)
}
func (sc *StepContext) theResponseShouldBe(arg1, arg2 string) error {
// The regex captures the full JSON from the feature file, including quotes
// We need to extract just the key and value without the surrounding quotes and backslashes
// Remove the surrounding quotes and backslashes
cleanArg1 := strings.Trim(arg1, `"\`)
cleanArg2 := strings.Trim(arg2, `"\`)
// Build the expected JSON string
expected := fmt.Sprintf(`{"%s":"%s"}`, cleanArg1, cleanArg2)
return sc.client.ExpectResponseBody(expected)
}
func (sc *StepContext) theServerIsRunning() error {
// Actually verify the server is running by checking the readiness endpoint
return sc.client.Request("GET", "/api/ready", nil)
}
func (sc *StepContext) theServerIsRunningWithV2Enabled() error {
// Verify the server is running and v2 is enabled by checking v2 endpoint exists
// First check server is running
if err := sc.client.Request("GET", "/api/ready", nil); err != nil {
return err
}
// Check if v2 endpoint is available (should return 405 Method Not Allowed for GET, which means endpoint exists)
// If v2 is disabled, this will return 404
resp, err := sc.client.CustomRequest("GET", "/api/v2/greet", nil)
if err != nil {
return err
}
defer resp.Body.Close()
// If we get 405, v2 is enabled (endpoint exists but doesn't allow GET)
// If we get 404, v2 is disabled
if resp.StatusCode == 404 {
return fmt.Errorf("v2 endpoint not available - v2 feature flag not enabled")
}
return nil
}
func (sc *StepContext) iSendPOSTRequestToV2GreetWithName(name string) error {
// Create JSON request body
requestBody := map[string]string{"name": name}
return sc.client.Request("POST", "/api/v2/greet", requestBody)
}
func (sc *StepContext) iSendPOSTRequestToV2GreetWithInvalidJSON(invalidJSON string) error {
// Send raw invalid JSON
return sc.client.Request("POST", "/api/v2/greet", invalidJSON)
}
func (sc *StepContext) theResponseShouldContainError(expectedError string) error {
// Check if the response contains the expected error
body := string(sc.client.GetLastBody())
if !strings.Contains(body, expectedError) {
return fmt.Errorf("expected response to contain error %q, got %q", expectedError, body)
}
return nil
}
// User Authentication Steps
func (sc *StepContext) aUserExistsWithPassword(username, password string) error {
// This will need to be implemented when user management is available
return fmt.Errorf("user management not yet implemented")
}
func (sc *StepContext) iAuthenticateWithUsernameAndPassword(username, password string) error {
// This will need to be implemented when authentication endpoints are available
return fmt.Errorf("authentication not yet implemented")
}
func (sc *StepContext) theAuthenticationShouldBeSuccessful() error {
// This will need to be implemented when authentication is available
return fmt.Errorf("authentication not yet implemented")
}
func (sc *StepContext) iShouldReceiveAValidJWTToken() error {
// This will need to be implemented when JWT generation is available
return fmt.Errorf("JWT generation not yet implemented")
}
func (sc *StepContext) theAuthenticationShouldFail() error {
// This will need to be implemented when authentication is available
return fmt.Errorf("authentication not yet implemented")
}
func (sc *StepContext) iAuthenticateAsAdminWithMasterPassword(password string) error {
// This will need to be implemented when admin authentication is available
return fmt.Errorf("admin authentication not yet implemented")
}
func (sc *StepContext) theTokenShouldContainAdminClaims() error {
// This will need to be implemented when JWT claims are available
return fmt.Errorf("JWT claims not yet implemented")
}
func (sc *StepContext) iRegisterANewUserWithPassword(username, password string) error {
// This will need to be implemented when user registration is available
return fmt.Errorf("user registration not yet implemented")
}
func (sc *StepContext) theRegistrationShouldBeSuccessful() error {
// This will need to be implemented when user registration is available
return fmt.Errorf("user registration not yet implemented")
}
func (sc *StepContext) iShouldBeAbleToAuthenticateWithTheNewCredentials() error {
// This will need to be implemented when authentication is available
return fmt.Errorf("authentication not yet implemented")
}
func (sc *StepContext) iAmAuthenticatedAsAdmin() error {
// This will need to be implemented when admin authentication is available
return fmt.Errorf("admin authentication not yet implemented")
}
func (sc *StepContext) iRequestPasswordResetForUser(username string) error {
// This will need to be implemented when password reset is available
return fmt.Errorf("password reset not yet implemented")
}
func (sc *StepContext) thePasswordResetShouldBeAllowed() error {
// This will need to be implemented when password reset is available
return fmt.Errorf("password reset not yet implemented")
}
func (sc *StepContext) theUserShouldBeFlaggedForPasswordReset() error {
// This will need to be implemented when password reset is available
return fmt.Errorf("password reset not yet implemented")
}
func (sc *StepContext) iCompletePasswordResetForWithNewPassword(username, password string) error {
// This will need to be implemented when password reset is available
return fmt.Errorf("password reset not yet implemented")
}
func (sc *StepContext) aUserExistsAndIsFlaggedForPasswordReset(username string) error {
// This will need to be implemented when password reset is available
return fmt.Errorf("password reset not yet implemented")
}
func (sc *StepContext) thePasswordResetShouldBeSuccessful() error {
// This will need to be implemented when password reset is available
return fmt.Errorf("password reset not yet implemented")
}
func (sc *StepContext) iShouldBeAbleToAuthenticateWithTheNewPassword() error {
// This will need to be implemented when authentication is available
return fmt.Errorf("authentication not yet implemented")
}