Spaces:
Build error
Build error
| package security_test | |
| import ( | |
| "context" | |
| "testing" | |
| "time" | |
| "github.com/AmaniQuery/amaniquery/internal/security" | |
| ) | |
| // TestJWTAuthenticator_GenerateAndValidate tests the full JWT lifecycle | |
| func TestJWTAuthenticator_GenerateAndValidate(t *testing.T) { | |
| cfg := security.JWTConfig{ | |
| Secret: []byte("test-secret-key-at-least-32-chars!!"), | |
| Issuer: "test-issuer", | |
| Audience: "test-audience", | |
| TokenDuration: time.Hour, | |
| } | |
| auth := security.NewJWTAuthenticator(cfg) | |
| // Generate token | |
| token, err := auth.GenerateToken("user-123", "test@example.com", []string{"user", "admin"}, "tenant-1") | |
| if err != nil { | |
| t.Fatalf("GenerateToken failed: %v", err) | |
| } | |
| if token == "" { | |
| t.Error("Expected non-empty token") | |
| } | |
| // Validate token | |
| claims, err := auth.ValidateToken(token) | |
| if err != nil { | |
| t.Fatalf("ValidateToken failed: %v", err) | |
| } | |
| if claims.UserID != "user-123" { | |
| t.Errorf("Expected UserID 'user-123', got '%s'", claims.UserID) | |
| } | |
| if claims.Email != "test@example.com" { | |
| t.Errorf("Expected Email 'test@example.com', got '%s'", claims.Email) | |
| } | |
| if len(claims.Roles) != 2 { | |
| t.Errorf("Expected 2 roles, got %d", len(claims.Roles)) | |
| } | |
| if claims.TenantID != "tenant-1" { | |
| t.Errorf("Expected TenantID 'tenant-1', got '%s'", claims.TenantID) | |
| } | |
| } | |
| // TestJWTAuthenticator_InvalidToken tests handling of invalid tokens | |
| func TestJWTAuthenticator_InvalidToken(t *testing.T) { | |
| cfg := security.JWTConfig{ | |
| Secret: []byte("test-secret-key-at-least-32-chars!!"), | |
| Issuer: "test-issuer", | |
| TokenDuration: time.Hour, | |
| } | |
| auth := security.NewJWTAuthenticator(cfg) | |
| testCases := []struct { | |
| name string | |
| token string | |
| }{ | |
| {"empty token", ""}, | |
| {"garbage token", "not-a-valid-token"}, | |
| {"tampered token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"}, | |
| } | |
| for _, tc := range testCases { | |
| t.Run(tc.name, func(t *testing.T) { | |
| _, err := auth.ValidateToken(tc.token) | |
| if err == nil { | |
| t.Error("Expected error for invalid token") | |
| } | |
| }) | |
| } | |
| } | |
| // TestJWTAuthenticator_ExpiredToken tests handling of expired tokens | |
| func TestJWTAuthenticator_ExpiredToken(t *testing.T) { | |
| cfg := security.JWTConfig{ | |
| Secret: []byte("test-secret-key-at-least-32-chars!!"), | |
| Issuer: "test-issuer", | |
| TokenDuration: -time.Hour, // Already expired | |
| } | |
| auth := security.NewJWTAuthenticator(cfg) | |
| token, err := auth.GenerateToken("user-123", "test@example.com", []string{"user"}, "") | |
| if err != nil { | |
| t.Fatalf("GenerateToken failed: %v", err) | |
| } | |
| // Validation should fail due to expiration | |
| _, err = auth.ValidateToken(token) | |
| if err == nil { | |
| t.Error("Expected error for expired token") | |
| } | |
| } | |
| // TestJWTAuthenticator_DifferentSecrets tests that different secrets fail validation | |
| func TestJWTAuthenticator_DifferentSecrets(t *testing.T) { | |
| cfg1 := security.JWTConfig{ | |
| Secret: []byte("first-secret-key-at-least-32-chars!"), | |
| Issuer: "test-issuer", | |
| TokenDuration: time.Hour, | |
| } | |
| cfg2 := security.JWTConfig{ | |
| Secret: []byte("second-secret-key-at-least-32-chars"), | |
| Issuer: "test-issuer", | |
| TokenDuration: time.Hour, | |
| } | |
| auth1 := security.NewJWTAuthenticator(cfg1) | |
| auth2 := security.NewJWTAuthenticator(cfg2) | |
| token, err := auth1.GenerateToken("user-123", "test@example.com", []string{"user"}, "") | |
| if err != nil { | |
| t.Fatalf("GenerateToken failed: %v", err) | |
| } | |
| // Should fail with different secret | |
| _, err = auth2.ValidateToken(token) | |
| if err == nil { | |
| t.Error("Expected error when validating with different secret") | |
| } | |
| } | |
| // TestJWTAuthenticator_IssuerValidation tests issuer validation | |
| func TestJWTAuthenticator_IssuerValidation(t *testing.T) { | |
| cfg1 := security.JWTConfig{ | |
| Secret: []byte("test-secret-key-at-least-32-chars!!"), | |
| Issuer: "issuer-1", | |
| TokenDuration: time.Hour, | |
| } | |
| cfg2 := security.JWTConfig{ | |
| Secret: []byte("test-secret-key-at-least-32-chars!!"), | |
| Issuer: "issuer-2", | |
| TokenDuration: time.Hour, | |
| } | |
| auth1 := security.NewJWTAuthenticator(cfg1) | |
| auth2 := security.NewJWTAuthenticator(cfg2) | |
| token, err := auth1.GenerateToken("user-123", "test@example.com", []string{"user"}, "") | |
| if err != nil { | |
| t.Fatalf("GenerateToken failed: %v", err) | |
| } | |
| // Should fail with different issuer | |
| _, err = auth2.ValidateToken(token) | |
| if err == nil { | |
| t.Error("Expected error for mismatched issuer") | |
| } | |
| } | |
| // TestContextWithClaims tests claims context operations | |
| func TestContextWithClaims(t *testing.T) { | |
| claims := &security.Claims{ | |
| UserID: "user-123", | |
| Email: "test@example.com", | |
| Roles: []string{"admin"}, | |
| } | |
| ctx := security.ContextWithClaims(context.Background(), claims) | |
| extracted, ok := security.ClaimsFromContext(ctx) | |
| if !ok { | |
| t.Fatal("Expected to extract claims from context") | |
| } | |
| if extracted.UserID != claims.UserID { | |
| t.Errorf("Expected UserID %s, got %s", claims.UserID, extracted.UserID) | |
| } | |
| } | |
| // TestClaimsFromContext_NoClaims tests extracting from context without claims | |
| func TestClaimsFromContext_NoClaims(t *testing.T) { | |
| ctx := context.Background() | |
| _, ok := security.ClaimsFromContext(ctx) | |
| if ok { | |
| t.Error("Expected no claims in empty context") | |
| } | |
| } | |
| // TestRequireRole tests role requirement checking | |
| func TestRequireRole(t *testing.T) { | |
| claims := &security.Claims{ | |
| UserID: "user-123", | |
| Roles: []string{"user", "editor"}, | |
| } | |
| ctx := security.ContextWithClaims(context.Background(), claims) | |
| // Should pass for existing role | |
| err := security.RequireRole(ctx, "user") | |
| if err != nil { | |
| t.Errorf("Expected no error for existing role: %v", err) | |
| } | |
| // Should fail for missing role | |
| err = security.RequireRole(ctx, "superadmin") | |
| if err == nil { | |
| t.Error("Expected error for missing role") | |
| } | |
| } | |
| // TestRequireRole_AdminBypass tests admin role bypass | |
| func TestRequireRole_AdminBypass(t *testing.T) { | |
| claims := &security.Claims{ | |
| UserID: "user-123", | |
| Roles: []string{"admin"}, | |
| } | |
| ctx := security.ContextWithClaims(context.Background(), claims) | |
| // Admin should have access to any role | |
| err := security.RequireRole(ctx, "any-role") | |
| if err != nil { | |
| t.Errorf("Expected admin to bypass role check: %v", err) | |
| } | |
| } | |
| // TestRequireAnyRole tests multiple role checking | |
| func TestRequireAnyRole(t *testing.T) { | |
| claims := &security.Claims{ | |
| UserID: "user-123", | |
| Roles: []string{"viewer"}, | |
| } | |
| ctx := security.ContextWithClaims(context.Background(), claims) | |
| // Should pass if user has any of the roles | |
| err := security.RequireAnyRole(ctx, "editor", "viewer", "admin") | |
| if err != nil { | |
| t.Errorf("Expected no error when user has one of the roles: %v", err) | |
| } | |
| // Should fail if user has none of the roles | |
| err = security.RequireAnyRole(ctx, "editor", "admin") | |
| if err == nil { | |
| t.Error("Expected error when user has none of the roles") | |
| } | |
| } | |
| // BenchmarkJWT_Generate benchmarks token generation | |
| func BenchmarkJWT_Generate(b *testing.B) { | |
| cfg := security.JWTConfig{ | |
| Secret: []byte("test-secret-key-at-least-32-chars!!"), | |
| Issuer: "test-issuer", | |
| TokenDuration: time.Hour, | |
| } | |
| auth := security.NewJWTAuthenticator(cfg) | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| auth.GenerateToken("user-123", "test@example.com", []string{"admin"}, "tenant-1") | |
| } | |
| } | |
| // BenchmarkJWT_Validate benchmarks token validation | |
| func BenchmarkJWT_Validate(b *testing.B) { | |
| cfg := security.JWTConfig{ | |
| Secret: []byte("test-secret-key-at-least-32-chars!!"), | |
| Issuer: "test-issuer", | |
| TokenDuration: time.Hour, | |
| } | |
| auth := security.NewJWTAuthenticator(cfg) | |
| token, _ := auth.GenerateToken("user-123", "test@example.com", []string{"admin"}, "") | |
| b.ResetTimer() | |
| for i := 0; i < b.N; i++ { | |
| auth.ValidateToken(token) | |
| } | |
| } | |