mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
509 lines
13 KiB
Go
509 lines
13 KiB
Go
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)
|
|
}
|
|
}
|