package providers import ( "runtime" "sync" "testing" ) func TestNewProviderFactory(t *testing.T) { factory := NewProviderFactory() if factory == nil { t.Fatal("expected non-nil factory") } if factory.registry == nil { t.Fatal("expected non-nil registry in factory") } } func TestProviderFactory_CreateProvider(t *testing.T) { factory := NewProviderFactory() tests := []struct { name string issuerURL string wantType ProviderType wantError bool errorSubstr string }{ { name: "Google provider detection", issuerURL: "https://accounts.google.com/.well-known/openid_configuration", wantType: ProviderTypeGoogle, wantError: false, }, { name: "Azure provider detection - login.microsoftonline.com", issuerURL: "https://login.microsoftonline.com/tenant-id/v2.0", wantType: ProviderTypeAzure, wantError: false, }, { name: "Azure provider detection - sts.windows.net", issuerURL: "https://sts.windows.net/tenant-id/", wantType: ProviderTypeAzure, wantError: false, }, { name: "Generic provider detection", issuerURL: "https://auth.example.com/realms/test", wantType: ProviderTypeGeneric, wantError: false, }, { name: "Empty issuer URL", issuerURL: "", wantError: true, errorSubstr: "issuer URL cannot be empty", }, { name: "Invalid URL format", issuerURL: "not-a-valid-url", wantType: ProviderTypeGeneric, wantError: false, // url.Parse accepts this as a valid URL }, { name: "URL with invalid scheme", issuerURL: "ftp://example.com/auth", wantType: ProviderTypeGeneric, wantError: false, // Should create generic provider for non-standard schemes }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { provider, err := factory.CreateProvider(tt.issuerURL) if tt.wantError { if err == nil { t.Errorf("expected error but got none") return } if tt.errorSubstr != "" && err.Error() != tt.errorSubstr { t.Errorf("expected error to contain %q, got %q", tt.errorSubstr, err.Error()) } return } if err != nil { t.Errorf("unexpected error: %v", err) return } if provider == nil { t.Error("expected non-nil provider") return } if provider.GetType() != tt.wantType { t.Errorf("expected provider type %d, got %d", tt.wantType, provider.GetType()) } }) } } func TestProviderFactory_CreateProviderByType(t *testing.T) { factory := NewProviderFactory() tests := []struct { name string providerType ProviderType wantError bool errorSubstr string }{ { name: "Generic provider", providerType: ProviderTypeGeneric, wantError: false, }, { name: "Google provider", providerType: ProviderTypeGoogle, wantError: false, }, { name: "Azure provider", providerType: ProviderTypeAzure, wantError: false, }, { name: "Invalid provider type", providerType: ProviderType(999), wantError: true, errorSubstr: "unsupported provider type: 999", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { provider, err := factory.CreateProviderByType(tt.providerType) if tt.wantError { if err == nil { t.Errorf("expected error but got none") return } if tt.errorSubstr != "" && err.Error() != tt.errorSubstr { t.Errorf("expected error to contain %q, got %q", tt.errorSubstr, err.Error()) } return } if err != nil { t.Errorf("unexpected error: %v", err) return } if provider == nil { t.Error("expected non-nil provider") return } if provider.GetType() != tt.providerType { t.Errorf("expected provider type %d, got %d", tt.providerType, provider.GetType()) } }) } } func TestProviderFactory_GetSupportedProviders(t *testing.T) { factory := NewProviderFactory() supported := factory.GetSupportedProviders() expectedProviders := map[ProviderType][]string{ ProviderTypeGeneric: {"*"}, ProviderTypeGoogle: {"accounts.google.com"}, ProviderTypeAzure: {"login.microsoftonline.com", "sts.windows.net"}, } if len(supported) != len(expectedProviders) { t.Errorf("expected %d supported providers, got %d", len(expectedProviders), len(supported)) } for expectedType, expectedPatterns := range expectedProviders { patterns, exists := supported[expectedType] if !exists { t.Errorf("expected provider type %d to be supported", expectedType) continue } if len(patterns) != len(expectedPatterns) { t.Errorf("expected %d patterns for provider type %d, got %d", len(expectedPatterns), expectedType, len(patterns)) continue } for i, expectedPattern := range expectedPatterns { if patterns[i] != expectedPattern { t.Errorf("expected pattern %q for provider type %d, got %q", expectedPattern, expectedType, patterns[i]) } } } } func TestProviderFactory_DetectProviderType(t *testing.T) { factory := NewProviderFactory() tests := []struct { name string issuerURL string wantType ProviderType wantError bool }{ { name: "Google detection", issuerURL: "https://accounts.google.com/.well-known/openid_configuration", wantType: ProviderTypeGoogle, wantError: false, }, { name: "Azure detection", issuerURL: "https://login.microsoftonline.com/tenant/v2.0", wantType: ProviderTypeAzure, wantError: false, }, { name: "Generic detection", issuerURL: "https://keycloak.example.com/auth/realms/master", wantType: ProviderTypeGeneric, wantError: false, }, { name: "Invalid URL", issuerURL: "", wantError: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { providerType, err := factory.DetectProviderType(tt.issuerURL) if tt.wantError { if err == nil { t.Error("expected error but got none") } return } if err != nil { t.Errorf("unexpected error: %v", err) return } if providerType != tt.wantType { t.Errorf("expected provider type %d, got %d", tt.wantType, providerType) } }) } } func TestProviderFactory_IsProviderSupported(t *testing.T) { factory := NewProviderFactory() tests := []struct { name string issuerURL string wantSupport bool }{ { name: "Google URL", issuerURL: "https://accounts.google.com/o/oauth2/auth", wantSupport: true, }, { name: "Azure URL - login.microsoftonline.com", issuerURL: "https://login.microsoftonline.com/tenant/v2.0", wantSupport: true, }, { name: "Azure URL - sts.windows.net", issuerURL: "https://sts.windows.net/tenant/", wantSupport: true, }, { name: "Generic URL", issuerURL: "https://auth.example.com/realms/master", wantSupport: true, }, { name: "Empty URL", issuerURL: "", wantSupport: false, }, { name: "Invalid URL", issuerURL: "not-a-valid-url", wantSupport: true, // Generic provider supports all URLs }, { name: "Valid but generic URL", issuerURL: "https://keycloak.example.com/auth", wantSupport: true, // Should be supported as generic }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { supported := factory.IsProviderSupported(tt.issuerURL) if supported != tt.wantSupport { t.Errorf("expected support %v for URL %q, got %v", tt.wantSupport, tt.issuerURL, supported) } }) } } func TestProviderFactory_ConcurrentAccess(t *testing.T) { factory := NewProviderFactory() // Track initial goroutine count for memory safety initialGoroutines := runtime.NumGoroutine() const numGoroutines = 100 const numOperationsPerGoroutine = 10 var wg sync.WaitGroup wg.Add(numGoroutines) urls := []string{ "https://accounts.google.com/.well-known/openid_configuration", "https://login.microsoftonline.com/tenant/v2.0", "https://auth.example.com/realms/master", } // Test concurrent provider creation for i := 0; i < numGoroutines; i++ { go func(workerID int) { defer wg.Done() for j := 0; j < numOperationsPerGoroutine; j++ { url := urls[j%len(urls)] // Test CreateProvider provider, err := factory.CreateProvider(url) if err != nil { t.Errorf("worker %d: unexpected error creating provider: %v", workerID, err) return } if provider == nil { t.Errorf("worker %d: expected non-nil provider", workerID) return } // Test IsProviderSupported supported := factory.IsProviderSupported(url) if !supported { t.Errorf("worker %d: expected URL %s to be supported", workerID, url) return } // Test DetectProviderType _, err = factory.DetectProviderType(url) if err != nil { t.Errorf("worker %d: unexpected error detecting provider type: %v", workerID, err) return } } }(i) } wg.Wait() // Check for potential goroutine leaks - allow some tolerance for test framework overhead finalGoroutines := runtime.NumGoroutine() if finalGoroutines > initialGoroutines+5 { t.Errorf("potential goroutine leak: started with %d goroutines, ended with %d", initialGoroutines, finalGoroutines) } } func TestProviderFactory_MemorySafety(t *testing.T) { // Test that creating many providers doesn't cause memory leaks const numCreations = 1000 initialGoroutines := runtime.NumGoroutine() for i := 0; i < numCreations; i++ { factory := NewProviderFactory() _, err := factory.CreateProvider("https://accounts.google.com/.well-known/openid_configuration") if err != nil { t.Fatalf("unexpected error creating provider: %v", err) } } // Force garbage collection to cleanup any lingering resources runtime.GC() runtime.GC() // Call twice to ensure cleanup finalGoroutines := runtime.NumGoroutine() if finalGoroutines > initialGoroutines+5 { t.Errorf("potential goroutine leak: started with %d goroutines, ended with %d", initialGoroutines, finalGoroutines) } } func TestProviderFactory_EdgeCases(t *testing.T) { factory := NewProviderFactory() t.Run("nil factory registry handling", func(t *testing.T) { // This test ensures we handle edge cases properly defer func() { if r := recover(); r != nil { // Expected behavior - nil registry should cause panic or be handled t.Logf("Recovered from panic as expected: %v", r) } }() brokenFactory := &ProviderFactory{registry: nil} _, err := brokenFactory.CreateProvider("https://accounts.google.com") if err == nil { t.Error("expected error with nil registry") } }) t.Run("malformed URLs", func(t *testing.T) { malformedURLs := []string{ "://missing-scheme.com", "https://", "https:///missing-host", "https://example.com:port-not-number", } for _, url := range malformedURLs { _, err := factory.CreateProvider(url) // Some malformed URLs might still be accepted by url.Parse, // but we ensure the system doesn't crash if err == nil { // Still check that we get a valid provider provider, err := factory.CreateProvider(url) if err == nil && provider == nil { t.Errorf("got nil provider without error for URL: %s", url) } } } }) t.Run("very long URLs", func(t *testing.T) { longURL := "https://accounts.google.com/" + string(make([]byte, 10000)) for i := range longURL[len("https://accounts.google.com/"):] { longURL = longURL[:len("https://accounts.google.com/")+i] + "a" + longURL[len("https://accounts.google.com/")+i+1:] } provider, err := factory.CreateProvider(longURL) if err != nil { t.Logf("long URL rejected as expected: %v", err) } else if provider == nil { t.Error("got nil provider without error for very long URL") } }) } // Benchmark tests for performance validation func BenchmarkProviderFactory_CreateProvider(b *testing.B) { factory := NewProviderFactory() urls := []string{ "https://accounts.google.com/.well-known/openid_configuration", "https://login.microsoftonline.com/tenant/v2.0", "https://auth.example.com/realms/master", } b.ResetTimer() for i := 0; i < b.N; i++ { url := urls[i%len(urls)] _, err := factory.CreateProvider(url) if err != nil { b.Fatalf("unexpected error: %v", err) } } } func BenchmarkProviderFactory_DetectProviderType(b *testing.B) { factory := NewProviderFactory() urls := []string{ "https://accounts.google.com/.well-known/openid_configuration", "https://login.microsoftonline.com/tenant/v2.0", "https://auth.example.com/realms/master", } b.ResetTimer() for i := 0; i < b.N; i++ { url := urls[i%len(urls)] _, err := factory.DetectProviderType(url) if err != nil { b.Fatalf("unexpected error: %v", err) } } } func BenchmarkProviderFactory_IsProviderSupported(b *testing.B) { factory := NewProviderFactory() urls := []string{ "https://accounts.google.com/.well-known/openid_configuration", "https://login.microsoftonline.com/tenant/v2.0", "https://auth.example.com/realms/master", } b.ResetTimer() for i := 0; i < b.N; i++ { url := urls[i%len(urls)] factory.IsProviderSupported(url) } }