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%
This commit is contained in:
parent
7720f2e35e
commit
052f5169c5
5 changed files with 944 additions and 2 deletions
|
|
@ -46,6 +46,7 @@ require (
|
|||
github.com/go-openapi/swag/typeutils v0.25.4 // indirect
|
||||
github.com/go-openapi/swag/yamlutils v0.25.4 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.1.0 // indirect
|
||||
github.com/swaggo/files/v2 v2.0.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/mod v0.30.0 // indirect
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
|
|
|
|||
601
backend/internal/api/middleware/auth_middleware_test.go
Normal file
601
backend/internal/api/middleware/auth_middleware_test.go
Normal file
|
|
@ -0,0 +1,601 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockAuthService implements ports.AuthService for testing
|
||||
type MockAuthService struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockAuthService) ValidateToken(token string) (map[string]interface{}, error) {
|
||||
fmt.Printf("[TEST LOG] ValidateToken called with token: '%s...'\n", token[:min(20, len(token))])
|
||||
args := m.Called(token)
|
||||
if args.Get(0) == nil {
|
||||
return nil, args.Error(1)
|
||||
}
|
||||
return args.Get(0).(map[string]interface{}), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAuthService) GenerateToken(userID, tenantID string, roles []string) (string, error) {
|
||||
fmt.Printf("[TEST LOG] GenerateToken called: userID=%s, tenantID=%s, roles=%v\n", userID, tenantID, roles)
|
||||
args := m.Called(userID, tenantID, roles)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAuthService) HashPassword(password string) (string, error) {
|
||||
fmt.Printf("[TEST LOG] HashPassword called\n")
|
||||
args := m.Called(password)
|
||||
return args.String(0), args.Error(1)
|
||||
}
|
||||
|
||||
func (m *MockAuthService) VerifyPassword(hash, password string) bool {
|
||||
fmt.Printf("[TEST LOG] VerifyPassword called\n")
|
||||
args := m.Called(hash, password)
|
||||
return args.Bool(0)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TestHeaderAuthGuard - Tests for the main auth middleware
|
||||
// ============================================================================
|
||||
|
||||
func TestHeaderAuthGuard_ValidTokenFromHeader(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHeaderAuthGuard_ValidTokenFromHeader ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"sub": "user-123",
|
||||
"tenant": "tenant-456",
|
||||
"roles": []interface{}{"admin", "user"},
|
||||
}
|
||||
mockAuth.On("ValidateToken", "valid-jwt-token").Return(claims, nil)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - checking context values")
|
||||
userID := r.Context().Value(ContextUserID)
|
||||
tenantID := r.Context().Value(ContextTenantID)
|
||||
roles := r.Context().Value(ContextRoles)
|
||||
|
||||
fmt.Printf("[TEST LOG] Context: userID=%v, tenantID=%v, roles=%v\n", userID, tenantID, roles)
|
||||
|
||||
assert.Equal(t, "user-123", userID)
|
||||
assert.Equal(t, "tenant-456", tenantID)
|
||||
assert.NotNil(t, roles)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer valid-jwt-token")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
mockAuth.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestHeaderAuthGuard_ValidTokenFromCookie(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHeaderAuthGuard_ValidTokenFromCookie ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"sub": "user-cookie-123",
|
||||
"tenant": "tenant-cookie-456",
|
||||
"roles": []string{"candidate"},
|
||||
}
|
||||
mockAuth.On("ValidateToken", "cookie-jwt-token").Return(claims, nil)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached via cookie auth")
|
||||
userID := r.Context().Value(ContextUserID)
|
||||
fmt.Printf("[TEST LOG] Context userID: %v\n", userID)
|
||||
assert.Equal(t, "user-cookie-123", userID)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.AddCookie(&http.Cookie{Name: "jwt", Value: "cookie-jwt-token"})
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
mockAuth.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestHeaderAuthGuard_MissingToken(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHeaderAuthGuard_MissingToken ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called when token is missing")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Missing Authorization Header or Cookie")
|
||||
}
|
||||
|
||||
func TestHeaderAuthGuard_InvalidTokenFormat(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHeaderAuthGuard_InvalidTokenFormat ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called with invalid token format")
|
||||
})
|
||||
|
||||
// Test with "Basic" instead of "Bearer"
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Basic some-token")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
}
|
||||
|
||||
func TestHeaderAuthGuard_InvalidToken(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHeaderAuthGuard_InvalidToken ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
mockAuth.On("ValidateToken", "invalid-token").Return(nil, fmt.Errorf("token expired"))
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called with invalid token")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/protected", nil)
|
||||
req.Header.Set("Authorization", "Bearer invalid-token")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.HeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Invalid Token")
|
||||
mockAuth.AssertExpectations(t)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TestOptionalHeaderAuthGuard - Tests for optional auth middleware
|
||||
// ============================================================================
|
||||
|
||||
func TestOptionalHeaderAuthGuard_NoToken(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_NoToken ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handlerCalled := false
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached without token - context should be empty")
|
||||
handlerCalled = true
|
||||
|
||||
// Context values should be nil/empty
|
||||
userID := r.Context().Value(ContextUserID)
|
||||
fmt.Printf("[TEST LOG] Context userID (should be nil): %v\n", userID)
|
||||
assert.Nil(t, userID)
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/public", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Handler was called: %v (expected: true)\n", handlerCalled)
|
||||
assert.True(t, handlerCalled)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestOptionalHeaderAuthGuard_ValidToken(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_ValidToken ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"sub": "optional-user",
|
||||
"tenant": "optional-tenant",
|
||||
"roles": []string{"viewer"},
|
||||
}
|
||||
mockAuth.On("ValidateToken", "optional-token").Return(claims, nil)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached with optional token")
|
||||
userID := r.Context().Value(ContextUserID)
|
||||
fmt.Printf("[TEST LOG] Context userID: %v\n", userID)
|
||||
assert.Equal(t, "optional-user", userID)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/public", nil)
|
||||
req.Header.Set("Authorization", "Bearer optional-token")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
mockAuth.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestOptionalHeaderAuthGuard_InvalidToken(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_InvalidToken ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
mockAuth.On("ValidateToken", "bad-optional-token").Return(nil, fmt.Errorf("invalid"))
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called with invalid optional token")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/public", nil)
|
||||
req.Header.Set("Authorization", "Bearer bad-optional-token")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusUnauthorized)
|
||||
assert.Equal(t, http.StatusUnauthorized, rr.Code)
|
||||
mockAuth.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestOptionalHeaderAuthGuard_TokenFromCookie(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestOptionalHeaderAuthGuard_TokenFromCookie ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
claims := map[string]interface{}{
|
||||
"sub": "cookie-user",
|
||||
"tenant": "cookie-tenant",
|
||||
"roles": []string{"user"},
|
||||
}
|
||||
mockAuth.On("ValidateToken", "cookie-optional-token").Return(claims, nil)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
userID := r.Context().Value(ContextUserID)
|
||||
assert.Equal(t, "cookie-user", userID)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/public", nil)
|
||||
req.AddCookie(&http.Cookie{Name: "jwt", Value: "cookie-optional-token"})
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.OptionalHeaderAuthGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
mockAuth.AssertExpectations(t)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TestRequireRoles - Tests for role-based access control
|
||||
// ============================================================================
|
||||
|
||||
func TestRequireRoles_UserHasRequiredRole(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestRequireRoles_UserHasRequiredRole ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - user has required role")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
// Create request with roles in context
|
||||
req := httptest.NewRequest("GET", "/admin", nil)
|
||||
ctx := context.WithValue(req.Context(), ContextRoles, []string{"admin", "user"})
|
||||
req = req.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.RequireRoles("admin")(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestRequireRoles_UserLacksRequiredRole(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestRequireRoles_UserLacksRequiredRole ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called when user lacks role")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/admin", nil)
|
||||
ctx := context.WithValue(req.Context(), ContextRoles, []string{"user", "viewer"})
|
||||
req = req.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.RequireRoles("admin", "superadmin")(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Forbidden")
|
||||
}
|
||||
|
||||
func TestRequireRoles_CaseInsensitive(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestRequireRoles_CaseInsensitive ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - case insensitive match worked")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/admin", nil)
|
||||
ctx := context.WithValue(req.Context(), ContextRoles, []string{"ADMIN", "USER"})
|
||||
req = req.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.RequireRoles("admin")(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestRequireRoles_NoRolesInContext(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestRequireRoles_NoRolesInContext ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called when no roles in context")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/admin", nil)
|
||||
// No roles in context
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.RequireRoles("admin")(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Roles not found")
|
||||
}
|
||||
|
||||
func TestRequireRoles_MultipleAllowedRoles(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestRequireRoles_MultipleAllowedRoles ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - matched one of multiple allowed roles")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/manage", nil)
|
||||
ctx := context.WithValue(req.Context(), ContextRoles, []string{"moderator"})
|
||||
req = req.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
// Allow admin, moderator, or superadmin
|
||||
mw.RequireRoles("admin", "moderator", "superadmin")(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TestTenantGuard - Tests for tenant enforcement
|
||||
// ============================================================================
|
||||
|
||||
func TestTenantGuard_ValidTenant(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestTenantGuard_ValidTenant ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - tenant is valid")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenant-resource", nil)
|
||||
ctx := context.WithValue(req.Context(), ContextTenantID, "tenant-123")
|
||||
req = req.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.TenantGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusOK)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestTenantGuard_MissingTenant(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestTenantGuard_MissingTenant ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called when tenant is missing")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenant-resource", nil)
|
||||
// No tenant in context
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.TenantGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
assert.Contains(t, rr.Body.String(), "Tenant Context Missing")
|
||||
}
|
||||
|
||||
func TestTenantGuard_EmptyTenant(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestTenantGuard_EmptyTenant ===")
|
||||
|
||||
mockAuth := new(MockAuthService)
|
||||
mw := NewMiddleware(mockAuth)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("Handler should not be called when tenant is empty")
|
||||
})
|
||||
|
||||
req := httptest.NewRequest("GET", "/tenant-resource", nil)
|
||||
ctx := context.WithValue(req.Context(), ContextTenantID, "")
|
||||
req = req.WithContext(ctx)
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
mw.TenantGuard(handler).ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: %d)\n", rr.Code, http.StatusForbidden)
|
||||
assert.Equal(t, http.StatusForbidden, rr.Code)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TestExtractRoles - Tests for the helper function
|
||||
// ============================================================================
|
||||
|
||||
func TestExtractRoles_FromStringSlice(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestExtractRoles_FromStringSlice ===")
|
||||
|
||||
input := []string{"admin", "user", "viewer"}
|
||||
result := ExtractRoles(input)
|
||||
|
||||
fmt.Printf("[TEST LOG] Input: %v, Result: %v\n", input, result)
|
||||
assert.Equal(t, []string{"admin", "user", "viewer"}, result)
|
||||
}
|
||||
|
||||
func TestExtractRoles_FromInterfaceSlice(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestExtractRoles_FromInterfaceSlice ===")
|
||||
|
||||
input := []interface{}{"admin", "moderator"}
|
||||
result := ExtractRoles(input)
|
||||
|
||||
fmt.Printf("[TEST LOG] Input: %v, Result: %v\n", input, result)
|
||||
assert.Equal(t, []string{"admin", "moderator"}, result)
|
||||
}
|
||||
|
||||
func TestExtractRoles_FromNil(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestExtractRoles_FromNil ===")
|
||||
|
||||
result := ExtractRoles(nil)
|
||||
|
||||
fmt.Printf("[TEST LOG] Input: nil, Result: %v\n", result)
|
||||
assert.Equal(t, []string{}, result)
|
||||
}
|
||||
|
||||
func TestExtractRoles_FromUnknownType(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestExtractRoles_FromUnknownType ===")
|
||||
|
||||
input := "not-a-slice"
|
||||
result := ExtractRoles(input)
|
||||
|
||||
fmt.Printf("[TEST LOG] Input: %v (type: string), Result: %v\n", input, result)
|
||||
assert.Equal(t, []string{}, result)
|
||||
}
|
||||
|
||||
func TestExtractRoles_FromMixedInterfaceSlice(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestExtractRoles_FromMixedInterfaceSlice ===")
|
||||
|
||||
input := []interface{}{"admin", 123, "user", nil}
|
||||
result := ExtractRoles(input)
|
||||
|
||||
fmt.Printf("[TEST LOG] Input: %v, Result: %v\n", input, result)
|
||||
// Should only extract strings
|
||||
assert.Equal(t, []string{"admin", "user"}, result)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TestHasRole - Tests for the role matching helper
|
||||
// ============================================================================
|
||||
|
||||
func TestHasRole_SingleMatch(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHasRole_SingleMatch ===")
|
||||
|
||||
userRoles := []string{"admin", "user"}
|
||||
allowedRoles := []string{"admin"}
|
||||
|
||||
result := hasRole(userRoles, allowedRoles)
|
||||
|
||||
fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
|
||||
assert.True(t, result)
|
||||
}
|
||||
|
||||
func TestHasRole_NoMatch(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHasRole_NoMatch ===")
|
||||
|
||||
userRoles := []string{"user", "viewer"}
|
||||
allowedRoles := []string{"admin", "superadmin"}
|
||||
|
||||
result := hasRole(userRoles, allowedRoles)
|
||||
|
||||
fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
|
||||
assert.False(t, result)
|
||||
}
|
||||
|
||||
func TestHasRole_CaseInsensitive(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHasRole_CaseInsensitive ===")
|
||||
|
||||
userRoles := []string{"ADMIN", "USER"}
|
||||
allowedRoles := []string{"admin"}
|
||||
|
||||
result := hasRole(userRoles, allowedRoles)
|
||||
|
||||
fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
|
||||
assert.True(t, result)
|
||||
}
|
||||
|
||||
func TestHasRole_EmptyUserRoles(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHasRole_EmptyUserRoles ===")
|
||||
|
||||
userRoles := []string{}
|
||||
allowedRoles := []string{"admin"}
|
||||
|
||||
result := hasRole(userRoles, allowedRoles)
|
||||
|
||||
fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
|
||||
assert.False(t, result)
|
||||
}
|
||||
|
||||
func TestHasRole_EmptyAllowedRoles(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestHasRole_EmptyAllowedRoles ===")
|
||||
|
||||
userRoles := []string{"admin"}
|
||||
allowedRoles := []string{}
|
||||
|
||||
result := hasRole(userRoles, allowedRoles)
|
||||
|
||||
fmt.Printf("[TEST LOG] User roles: %v, Allowed: %v, Result: %v\n", userRoles, allowedRoles, result)
|
||||
assert.False(t, result)
|
||||
}
|
||||
205
backend/internal/api/middleware/cors_middleware_test.go
Normal file
205
backend/internal/api/middleware/cors_middleware_test.go
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// ============================================================================
|
||||
// TestCORSMiddleware - Tests for CORS middleware
|
||||
// ============================================================================
|
||||
|
||||
func TestCORSMiddleware_AllowedOrigin(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestCORSMiddleware_AllowedOrigin ===")
|
||||
|
||||
// Set allowed origins
|
||||
os.Setenv("CORS_ORIGINS", "http://localhost:3000,http://example.com")
|
||||
defer os.Unsetenv("CORS_ORIGINS")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - CORS headers should be set")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
middleware := CORSMiddleware(handler)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/test", nil)
|
||||
req.Header.Set("Origin", "http://localhost:3000")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middleware.ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Origin: '%s'\n", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Credentials: '%s'\n", rr.Header().Get("Access-Control-Allow-Credentials"))
|
||||
|
||||
assert.Equal(t, "http://localhost:3000", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
assert.Equal(t, "true", rr.Header().Get("Access-Control-Allow-Credentials"))
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestCORSMiddleware_DeniedOrigin(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestCORSMiddleware_DeniedOrigin ===")
|
||||
|
||||
os.Setenv("CORS_ORIGINS", "http://localhost:3000")
|
||||
defer os.Unsetenv("CORS_ORIGINS")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - CORS origin should be empty")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
middleware := CORSMiddleware(handler)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/test", nil)
|
||||
req.Header.Set("Origin", "http://evil.com")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middleware.ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Origin: '%s' (expected empty)\n", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
|
||||
// Origin not in allowed list - should not set Access-Control-Allow-Origin
|
||||
assert.Equal(t, "", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
// But credentials header should still be set
|
||||
assert.Equal(t, "true", rr.Header().Get("Access-Control-Allow-Credentials"))
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestCORSMiddleware_WildcardOrigin(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestCORSMiddleware_WildcardOrigin ===")
|
||||
|
||||
os.Setenv("CORS_ORIGINS", "*")
|
||||
defer os.Unsetenv("CORS_ORIGINS")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - wildcard CORS")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
middleware := CORSMiddleware(handler)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/test", nil)
|
||||
req.Header.Set("Origin", "http://any-origin.com")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middleware.ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Origin: '%s' (expected *)\n", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
|
||||
assert.Equal(t, "*", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestCORSMiddleware_PreflightOptions(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestCORSMiddleware_PreflightOptions ===")
|
||||
|
||||
os.Setenv("CORS_ORIGINS", "http://localhost:3000")
|
||||
defer os.Unsetenv("CORS_ORIGINS")
|
||||
|
||||
handlerCalled := false
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handlerCalled = true
|
||||
t.Error("Handler should not be called for OPTIONS preflight")
|
||||
})
|
||||
|
||||
middleware := CORSMiddleware(handler)
|
||||
|
||||
req := httptest.NewRequest("OPTIONS", "/api/test", nil)
|
||||
req.Header.Set("Origin", "http://localhost:3000")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middleware.ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Response status: %d (expected: 200 for preflight)\n", rr.Code)
|
||||
fmt.Printf("[TEST LOG] Handler was called: %v (expected: false)\n", handlerCalled)
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Methods: '%s'\n", rr.Header().Get("Access-Control-Allow-Methods"))
|
||||
|
||||
assert.False(t, handlerCalled)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
assert.Contains(t, rr.Header().Get("Access-Control-Allow-Methods"), "POST")
|
||||
assert.Contains(t, rr.Header().Get("Access-Control-Allow-Methods"), "GET")
|
||||
assert.Contains(t, rr.Header().Get("Access-Control-Allow-Methods"), "PUT")
|
||||
assert.Contains(t, rr.Header().Get("Access-Control-Allow-Methods"), "DELETE")
|
||||
assert.Contains(t, rr.Header().Get("Access-Control-Allow-Headers"), "Authorization")
|
||||
}
|
||||
|
||||
func TestCORSMiddleware_DefaultOrigin(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestCORSMiddleware_DefaultOrigin ===")
|
||||
|
||||
// Clear CORS_ORIGINS to test default
|
||||
os.Unsetenv("CORS_ORIGINS")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - default origin")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
middleware := CORSMiddleware(handler)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/test", nil)
|
||||
req.Header.Set("Origin", "http://localhost:3000")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middleware.ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Origin: '%s'\n", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
|
||||
// Default should allow localhost:3000
|
||||
assert.Equal(t, "http://localhost:3000", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
||||
func TestCORSMiddleware_MultipleOrigins(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestCORSMiddleware_MultipleOrigins ===")
|
||||
|
||||
os.Setenv("CORS_ORIGINS", "http://app.example.com, http://admin.example.com")
|
||||
defer os.Unsetenv("CORS_ORIGINS")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
middleware := CORSMiddleware(handler)
|
||||
|
||||
// Test second origin in list
|
||||
req := httptest.NewRequest("GET", "/api/test", nil)
|
||||
req.Header.Set("Origin", "http://admin.example.com")
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middleware.ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Origin: '%s'\n", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
|
||||
assert.Equal(t, "http://admin.example.com", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
}
|
||||
|
||||
func TestCORSMiddleware_NoOriginHeader(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestCORSMiddleware_NoOriginHeader ===")
|
||||
|
||||
os.Setenv("CORS_ORIGINS", "http://localhost:3000")
|
||||
defer os.Unsetenv("CORS_ORIGINS")
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("[TEST LOG] Handler reached - no origin header in request")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
})
|
||||
|
||||
middleware := CORSMiddleware(handler)
|
||||
|
||||
req := httptest.NewRequest("GET", "/api/test", nil)
|
||||
// No Origin header set
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
middleware.ServeHTTP(rr, req)
|
||||
|
||||
fmt.Printf("[TEST LOG] Access-Control-Allow-Origin: '%s' (expected empty)\n", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
|
||||
// No origin means no matching, so header should be empty
|
||||
assert.Equal(t, "", rr.Header().Get("Access-Control-Allow-Origin"))
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package auth_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
|
|
@ -9,6 +10,8 @@ import (
|
|||
)
|
||||
|
||||
func TestJWTService_HashAndVerifyPassword(t *testing.T) {
|
||||
fmt.Println("\n[TEST] === TestJWTService_HashAndVerifyPassword ===")
|
||||
|
||||
// Setup
|
||||
os.Setenv("PASSWORD_PEPPER", "test-pepper")
|
||||
defer os.Unsetenv("PASSWORD_PEPPER")
|
||||
|
|
@ -16,30 +19,37 @@ func TestJWTService_HashAndVerifyPassword(t *testing.T) {
|
|||
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
|
||||
|
|
@ -47,29 +57,153 @@ func TestJWTService_HashAndVerifyPassword(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
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"])
|
||||
// JSON numbers are float64, so careful with types if we check deep structure,
|
||||
// but roles might come back as []interface{}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue