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%
209 lines
7.1 KiB
Go
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)
|
|
}
|