veza/veza-backend-api/internal/services/jwt_service_test.go
2025-12-12 21:34:34 -05:00

134 lines
4.5 KiB
Go

package services
import (
"testing"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"veza-backend-api/internal/models"
"github.com/stretchr/testify/assert"
)
func TestJWTService(t *testing.T) {
secret := "test-secret-key-for-unit-tests-very-secure"
issuer := "veza-api"
audience := "veza-app"
jwtService, err := NewJWTService(secret, issuer, audience)
assert.NoError(t, err)
// Mock User
// GO-004: Utiliser UUID au lieu de int
userID := uuid.New()
user := &models.User{
ID: userID,
Email: "test@example.com",
Username: "testuser",
Role: "user",
TokenVersion: 5,
}
t.Run("GenerateAccessToken", func(t *testing.T) {
token, err := jwtService.GenerateAccessToken(user)
assert.NoError(t, err)
assert.NotEmpty(t, token)
// Validate immediately
claims, err := jwtService.ValidateToken(token)
assert.NoError(t, err)
assert.Equal(t, user.ID, claims.UserID)
assert.Equal(t, user.Email, claims.Email)
assert.Equal(t, user.Role, claims.Role)
})
t.Run("GenerateRefreshToken", func(t *testing.T) {
token, err := jwtService.GenerateRefreshToken(user)
assert.NoError(t, err)
assert.NotEmpty(t, token)
// Validate
claims, err := jwtService.ValidateToken(token)
assert.NoError(t, err)
assert.Equal(t, user.ID, claims.UserID)
// Refresh token doesn't have email in current implementation
assert.Empty(t, claims.Email)
})
t.Run("VerifyTokenVersion", func(t *testing.T) {
// Generate token with user.TokenVersion = 5
token, _ := jwtService.GenerateAccessToken(user)
claims, _ := jwtService.ValidateToken(token)
// Case 1: Same version -> OK
err := jwtService.VerifyTokenVersion(claims, 5)
assert.NoError(t, err)
// Case 2: DB version is higher -> Error
err = jwtService.VerifyTokenVersion(claims, 6)
assert.Error(t, err)
assert.Contains(t, err.Error(), "token version mismatch")
// Case 3: DB version is lower -> OK (assuming implementation allows older tokens if logic permits, but usually equality is checked.
// Let's check implementation logic: return claims.TokenVersion != currentVersion
err = jwtService.VerifyTokenVersion(claims, 4)
assert.Error(t, err) // Expect error because version must match
})
t.Run("ExpiredToken", func(t *testing.T) {
invalidToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.invalid_signature"
_, err := jwtService.ValidateToken(invalidToken)
assert.Error(t, err)
})
t.Run("Security_StrictValidation", func(t *testing.T) {
// Test Invalid Issuer
invalidIssuerClaims := models.CustomClaims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
Issuer: "evil.com", // Wrong issuer
Audience: jwt.ClaimStrings{audience},
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, invalidIssuerClaims)
tokenString, _ := token.SignedString([]byte(secret))
_, err := jwtService.ValidateToken(tokenString)
assert.Error(t, err)
assert.Contains(t, err.Error(), "token has invalid claims: issuer name 'evil.com' is invalid")
// Test Invalid Audience
invalidAudClaims := models.CustomClaims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
Issuer: issuer,
Audience: jwt.ClaimStrings{"wrong-app"}, // Wrong audience
},
}
token = jwt.NewWithClaims(jwt.SigningMethodHS256, invalidAudClaims)
tokenString, _ = token.SignedString([]byte(secret))
_, err = jwtService.ValidateToken(tokenString)
assert.Error(t, err)
assert.Contains(t, err.Error(), "token has invalid claims: token contains an invalid number of audience claims")
// Test Invalid Algorithm (HS512 instead of HS256)
// Note: We need to use the SAME secret but different alg to verify alg check works even if signature verifies (if library allowed it, but strict parsing should fail alg)
validClaims := models.CustomClaims{
UserID: userID,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
Issuer: issuer,
Audience: jwt.ClaimStrings{audience},
},
}
token = jwt.NewWithClaims(jwt.SigningMethodHS512, validClaims)
tokenString, _ = token.SignedString([]byte(secret))
_, err = jwtService.ValidateToken(tokenString)
assert.Error(t, err)
// The error might be "unexpected signing method" or "signature is invalid" depending on implementation details
// But our code explicitly checks for HS256 and returns "invalid signing algorithm" or "unexpected signing method"
assert.Contains(t, err.Error(), "unexpected signing method")
})
}