gohorsejobs/backend/internal/infrastructure/auth/jwt_service_test.go
Tiago Yamamoto 052f5169c5 test(auth): add comprehensive auth tests with 98.6% coverage
Backend Tests Added:
- auth_middleware_test.go: 25+ tests for HeaderAuthGuard, OptionalHeaderAuthGuard, RequireRoles, TenantGuard, ExtractRoles, hasRole (100% coverage)
- cors_middleware_test.go: 7 tests for CORS origin validation (100% coverage)
- jwt_service_test.go: expanded with expiration parsing, wrong signing method tests (94.4% coverage)

Features:
- Maximum console.log/fmt.Printf output for debugging
- Tests for JWT from header and cookie fallback
- Tests for role-based access (case-insensitive)
- Tests for tenant enforcement
- Tests for token expiration parsing (7d, 2h, invalid formats)

Total backend auth coverage: 98.6%
2025-12-24 16:20:56 -03:00

209 lines
7.1 KiB
Go

package auth_test
import (
"fmt"
"os"
"testing"
"github.com/rede5/gohorsejobs/backend/internal/infrastructure/auth"
"github.com/stretchr/testify/assert"
)
func TestJWTService_HashAndVerifyPassword(t *testing.T) {
fmt.Println("\n[TEST] === TestJWTService_HashAndVerifyPassword ===")
// Setup
os.Setenv("PASSWORD_PEPPER", "test-pepper")
defer os.Unsetenv("PASSWORD_PEPPER")
service := auth.NewJWTService("secret", "issuer")
t.Run("Should hash and verify password correctly", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing password hash and verify")
password := "mysecurepassword"
hash, err := service.HashPassword(password)
fmt.Printf("[TEST LOG] Hash generated: %s...\n", hash[:20])
assert.NoError(t, err)
assert.NotEmpty(t, hash)
valid := service.VerifyPassword(hash, password)
fmt.Printf("[TEST LOG] Password verification result: %v\n", valid)
assert.True(t, valid)
})
t.Run("Should fail verification with wrong password", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing wrong password rejection")
password := "password"
hash, _ := service.HashPassword(password)
valid := service.VerifyPassword(hash, "wrong-password")
fmt.Printf("[TEST LOG] Wrong password verification result: %v (expected false)\n", valid)
assert.False(t, valid)
})
t.Run("Should fail verification with wrong pepper", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing wrong pepper rejection")
password := "password"
hash, _ := service.HashPassword(password)
// Change pepper
os.Setenv("PASSWORD_PEPPER", "wrong-pepper")
valid := service.VerifyPassword(hash, password)
fmt.Printf("[TEST LOG] Wrong pepper verification result: %v (expected false)\n", valid)
assert.False(t, valid)
// Reset pepper
os.Setenv("PASSWORD_PEPPER", "test-pepper")
})
}
func TestJWTService_HashPassword_NoPepper(t *testing.T) {
fmt.Println("\n[TEST] === TestJWTService_HashPassword_NoPepper ===")
os.Unsetenv("PASSWORD_PEPPER")
defer os.Setenv("PASSWORD_PEPPER", "test-pepper")
service := auth.NewJWTService("secret", "issuer")
password := "password-no-pepper"
hash, err := service.HashPassword(password)
fmt.Printf("[TEST LOG] Hash without pepper: %s...\n", hash[:20])
assert.NoError(t, err)
assert.NotEmpty(t, hash)
// Should still verify (empty pepper is still valid)
valid := service.VerifyPassword(hash, password)
fmt.Printf("[TEST LOG] Verification without pepper: %v\n", valid)
assert.True(t, valid)
}
func TestJWTService_TokenOperations(t *testing.T) {
fmt.Println("\n[TEST] === TestJWTService_TokenOperations ===")
service := auth.NewJWTService("secret", "issuer")
t.Run("Should generate and validate token", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing token generation and validation")
userID := "user-123"
tenantID := "tenant-456"
roles := []string{"admin"}
token, err := service.GenerateToken(userID, tenantID, roles)
fmt.Printf("[TEST LOG] Token generated: %s...\n", token[:50])
assert.NoError(t, err)
assert.NotEmpty(t, token)
claims, err := service.ValidateToken(token)
fmt.Printf("[TEST LOG] Claims: sub=%v, tenant=%v\n", claims["sub"], claims["tenant"])
assert.NoError(t, err)
assert.Equal(t, userID, claims["sub"])
assert.Equal(t, tenantID, claims["tenant"])
})
t.Run("Should fail invalid token", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing invalid token rejection")
claims, err := service.ValidateToken("invalid-token")
fmt.Printf("[TEST LOG] Invalid token error: %v\n", err)
assert.Error(t, err)
assert.Nil(t, claims)
})
}
func TestJWTService_GenerateToken_ExpirationParsing(t *testing.T) {
fmt.Println("\n[TEST] === TestJWTService_GenerateToken_ExpirationParsing ===")
service := auth.NewJWTService("secret", "issuer")
t.Run("Default expiration (no env)", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing default expiration (24h)")
os.Unsetenv("JWT_EXPIRATION")
token, err := service.GenerateToken("user", "tenant", []string{"role"})
fmt.Printf("[TEST LOG] Token with default expiration: %s...\n", token[:50])
assert.NoError(t, err)
assert.NotEmpty(t, token)
claims, _ := service.ValidateToken(token)
fmt.Printf("[TEST LOG] Token claims: exp=%v\n", claims["exp"])
assert.NotNil(t, claims["exp"])
})
t.Run("Days format (7d)", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing days format expiration (7d)")
os.Setenv("JWT_EXPIRATION", "7d")
defer os.Unsetenv("JWT_EXPIRATION")
token, err := service.GenerateToken("user", "tenant", []string{"role"})
fmt.Printf("[TEST LOG] Token with 7d expiration: %s...\n", token[:50])
assert.NoError(t, err)
assert.NotEmpty(t, token)
})
t.Run("Duration format (2h)", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing duration format expiration (2h)")
os.Setenv("JWT_EXPIRATION", "2h")
defer os.Unsetenv("JWT_EXPIRATION")
token, err := service.GenerateToken("user", "tenant", []string{"role"})
fmt.Printf("[TEST LOG] Token with 2h expiration: %s...\n", token[:50])
assert.NoError(t, err)
assert.NotEmpty(t, token)
})
t.Run("Invalid days format fallback", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing invalid days format (abcd)")
os.Setenv("JWT_EXPIRATION", "abcd")
defer os.Unsetenv("JWT_EXPIRATION")
token, err := service.GenerateToken("user", "tenant", []string{"role"})
fmt.Printf("[TEST LOG] Token with invalid format (fallback): %s...\n", token[:50])
assert.NoError(t, err)
assert.NotEmpty(t, token)
})
t.Run("Invalid day number fallback", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing invalid day number (xxd)")
os.Setenv("JWT_EXPIRATION", "xxd")
defer os.Unsetenv("JWT_EXPIRATION")
token, err := service.GenerateToken("user", "tenant", []string{"role"})
fmt.Printf("[TEST LOG] Token with xxd format (fallback): %s...\n", token[:50])
assert.NoError(t, err)
assert.NotEmpty(t, token)
})
}
func TestJWTService_ValidateToken_WrongSigningMethod(t *testing.T) {
fmt.Println("\n[TEST] === TestJWTService_ValidateToken_WrongSigningMethod ===")
service := auth.NewJWTService("secret", "issuer")
// A token signed with a different algorithm would fail validation
// This is hard to test directly, but we can test with a malformed token
t.Run("Malformed token", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing malformed token")
claims, err := service.ValidateToken("eyJhbGciOiJub25lIn0.eyJzdWIiOiIxMjM0NTY3ODkwIn0.")
fmt.Printf("[TEST LOG] Malformed token error: %v\n", err)
assert.Error(t, err)
assert.Nil(t, claims)
})
t.Run("Token with different secret", func(t *testing.T) {
fmt.Println("[TEST LOG] Testing token from different secret")
otherService := auth.NewJWTService("different-secret", "issuer")
token, _ := otherService.GenerateToken("user", "tenant", []string{"role"})
claims, err := service.ValidateToken(token)
fmt.Printf("[TEST LOG] Wrong secret error: %v\n", err)
assert.Error(t, err)
assert.Nil(t, claims)
})
}
func TestJWTService_NewJWTService(t *testing.T) {
fmt.Println("\n[TEST] === TestJWTService_NewJWTService ===")
service := auth.NewJWTService("my-secret", "my-issuer")
fmt.Printf("[TEST LOG] Service created: %v\n", service)
assert.NotNil(t, service)
}