package auth import ( "context" "strings" "testing" "time" "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestNewMockAuthService(t *testing.T) { t.Run("Creates new mock auth service", func(t *testing.T) { service, err := NewMockAuthService("test-service") require.NoError(t, err) assert.NotNil(t, service) assert.Equal(t, "test-service", service.issuer) assert.Len(t, service.secretKey, 32) }) } func TestMockAuthService_GenerateToken(t *testing.T) { service, err := NewMockAuthService("test-service") require.NoError(t, err) t.Run("Generates valid JWT token", func(t *testing.T) { userID := "user123" username := "testuser" email := "test@example.com" roles := []string{"player", "admin"} tokenString, err := service.GenerateToken(userID, username, email, roles) require.NoError(t, err) assert.NotEmpty(t, tokenString) // Verify the token has the expected structure parts := strings.Split(tokenString, ".") assert.Len(t, parts, 3) // header.payload.signature }) t.Run("Generated token contains correct claims", func(t *testing.T) { userID := "user123" username := "testuser" email := "test@example.com" roles := []string{"player"} tokenString, err := service.GenerateToken(userID, username, email, roles) require.NoError(t, err) // Parse and verify claims claims, err := service.ValidateToken(context.Background(), tokenString) require.NoError(t, err) assert.Equal(t, userID, claims.UserID) assert.Equal(t, username, claims.Username) assert.Equal(t, email, claims.Email) assert.Equal(t, roles, claims.Roles) assert.Equal(t, "test-service", claims.Issuer) assert.Equal(t, userID, claims.Subject) assert.Contains(t, claims.Audience, "knowfoolery") }) t.Run("Generated token expires in 24 hours", func(t *testing.T) { tokenString, err := service.GenerateToken("user1", "test", "test@example.com", []string{"player"}) require.NoError(t, err) claims, err := service.ValidateToken(context.Background(), tokenString) require.NoError(t, err) now := time.Now() expected := now.Add(24 * time.Hour) // Allow for some time drift (within 1 minute) assert.WithinDuration(t, expected, claims.ExpiresAt.Time, time.Minute) }) t.Run("Generates different tokens for different users", func(t *testing.T) { token1, err1 := service.GenerateToken("user1", "test1", "test1@example.com", []string{"player"}) token2, err2 := service.GenerateToken("user2", "test2", "test2@example.com", []string{"admin"}) require.NoError(t, err1) require.NoError(t, err2) assert.NotEqual(t, token1, token2) }) } func TestMockAuthService_ValidateToken(t *testing.T) { service, err := NewMockAuthService("test-service") require.NoError(t, err) t.Run("Validates correct token successfully", func(t *testing.T) { userID := "user123" username := "testuser" email := "test@example.com" roles := []string{"player", "admin"} tokenString, err := service.GenerateToken(userID, username, email, roles) require.NoError(t, err) claims, err := service.ValidateToken(context.Background(), tokenString) require.NoError(t, err) assert.Equal(t, userID, claims.UserID) assert.Equal(t, username, claims.Username) assert.Equal(t, email, claims.Email) assert.Equal(t, roles, claims.Roles) }) t.Run("Rejects malformed token", func(t *testing.T) { claims, err := service.ValidateToken(context.Background(), "invalid.token.format") require.Error(t, err) assert.Nil(t, claims) assert.Contains(t, err.Error(), "failed to parse token") }) t.Run("Rejects token with wrong signature", func(t *testing.T) { // Create token with different service (different secret) otherService, err := NewMockAuthService("other-service") require.NoError(t, err) tokenString, err := otherService.GenerateToken("user1", "test", "test@example.com", []string{"player"}) require.NoError(t, err) // Try to validate with original service claims, err := service.ValidateToken(context.Background(), tokenString) require.Error(t, err) assert.Nil(t, claims) assert.Contains(t, err.Error(), "failed to parse token") }) t.Run("Rejects expired token", func(t *testing.T) { // Create a token that's already expired now := time.Now() pastTime := now.Add(-25 * time.Hour) // Expired 1 hour ago claims := Claims{ UserID: "user123", Username: "testuser", Email: "test@example.com", Roles: []string{"player"}, RegisteredClaims: jwt.RegisteredClaims{ Issuer: service.issuer, Subject: "user123", Audience: []string{"knowfoolery"}, ExpiresAt: jwt.NewNumericDate(pastTime), NotBefore: jwt.NewNumericDate(pastTime.Add(-24 * time.Hour)), IssuedAt: jwt.NewNumericDate(pastTime.Add(-24 * time.Hour)), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(service.secretKey) require.NoError(t, err) // Try to validate expired token validClaims, err := service.ValidateToken(context.Background(), tokenString) require.Error(t, err) assert.Nil(t, validClaims) assert.Contains(t, err.Error(), "failed to parse token") }) t.Run("Rejects token with wrong signing method", func(t *testing.T) { // We can't easily create a token with wrong signing method without proper keys, // so just create a malformed token to test the validation malformedToken := "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.INVALID" validClaims, err := service.ValidateToken(context.Background(), malformedToken) require.Error(t, err) assert.Nil(t, validClaims) }) } func TestMockAuthService_CreateMockUsers(t *testing.T) { service, err := NewMockAuthService("test-service") require.NoError(t, err) t.Run("Creates mock user tokens", func(t *testing.T) { tokens := service.CreateMockUsers() assert.Len(t, tokens, 3) assert.Contains(t, tokens, "admin") assert.Contains(t, tokens, "player") assert.Contains(t, tokens, "player2") // Verify each token is valid for userType, tokenString := range tokens { claims, err := service.ValidateToken(context.Background(), tokenString) require.NoError(t, err, "Token for %s should be valid", userType) assert.NotEmpty(t, claims.UserID) assert.NotEmpty(t, claims.Username) assert.NotEmpty(t, claims.Email) assert.NotEmpty(t, claims.Roles) } }) t.Run("Admin token has admin role", func(t *testing.T) { tokens := service.CreateMockUsers() adminToken := tokens["admin"] claims, err := service.ValidateToken(context.Background(), adminToken) require.NoError(t, err) assert.Contains(t, claims.Roles, "admin") assert.Contains(t, claims.Roles, "player") assert.Equal(t, "admin", claims.Username) }) t.Run("Player tokens have player role", func(t *testing.T) { tokens := service.CreateMockUsers() for _, playerKey := range []string{"player", "player2"} { playerToken := tokens[playerKey] claims, err := service.ValidateToken(context.Background(), playerToken) require.NoError(t, err) assert.Contains(t, claims.Roles, "player") assert.NotContains(t, claims.Roles, "admin") } }) t.Run("Creates different tokens each time", func(t *testing.T) { tokens1 := service.CreateMockUsers() time.Sleep(time.Second + time.Millisecond) // Ensure different issued-at time tokens2 := service.CreateMockUsers() for userType := range tokens1 { assert.NotEqual(t, tokens1[userType], tokens2[userType], "Tokens for %s should be different between calls", userType) } }) } func TestMockAuthService_GetSecretHex(t *testing.T) { service, err := NewMockAuthService("test-service") require.NoError(t, err) t.Run("Returns hex encoded secret key", func(t *testing.T) { hexSecret := service.GetSecretHex() assert.NotEmpty(t, hexSecret) assert.Len(t, hexSecret, 64) // 32 bytes * 2 hex chars per byte // Verify it's valid hex for _, char := range hexSecret { assert.True(t, (char >= '0' && char <= '9') || (char >= 'a' && char <= 'f') || (char >= 'A' && char <= 'F'), "Character %c should be valid hex", char) } }) t.Run("Returns consistent secret key", func(t *testing.T) { hex1 := service.GetSecretHex() hex2 := service.GetSecretHex() assert.Equal(t, hex1, hex2, "Secret key should be consistent") }) t.Run("Different services have different secrets", func(t *testing.T) { service2, err := NewMockAuthService("other-service") require.NoError(t, err) hex1 := service.GetSecretHex() hex2 := service2.GetSecretHex() assert.NotEqual(t, hex1, hex2, "Different services should have different secrets") }) }