mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
737 lines
20 KiB
Go
737 lines
20 KiB
Go
package providers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// mockLegacySettings implements LegacySettings for testing
|
|
type mockLegacySettings struct {
|
|
issuerURL string
|
|
authURL string
|
|
scopes []string
|
|
pkceEnabled bool
|
|
clientID string
|
|
refreshGracePeriod time.Duration
|
|
overrideScopes bool
|
|
}
|
|
|
|
func (m *mockLegacySettings) GetIssuerURL() string {
|
|
return m.issuerURL
|
|
}
|
|
|
|
func (m *mockLegacySettings) GetAuthURL() string {
|
|
return m.authURL
|
|
}
|
|
|
|
func (m *mockLegacySettings) GetScopes() []string {
|
|
return m.scopes
|
|
}
|
|
|
|
func (m *mockLegacySettings) IsPKCEEnabled() bool {
|
|
return m.pkceEnabled
|
|
}
|
|
|
|
func (m *mockLegacySettings) GetClientID() string {
|
|
return m.clientID
|
|
}
|
|
|
|
func (m *mockLegacySettings) GetRefreshGracePeriod() time.Duration {
|
|
return m.refreshGracePeriod
|
|
}
|
|
|
|
func (m *mockLegacySettings) IsOverrideScopes() bool {
|
|
return m.overrideScopes
|
|
}
|
|
|
|
func TestNewAdapter(t *testing.T) {
|
|
provider := NewGoogleProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "test-client",
|
|
issuerURL: "https://accounts.google.com",
|
|
authURL: "https://accounts.google.com/o/oauth2/auth",
|
|
scopes: []string{"openid", "email"},
|
|
pkceEnabled: true,
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
|
|
if adapter == nil {
|
|
t.Fatal("expected non-nil adapter")
|
|
}
|
|
|
|
if adapter.provider != provider {
|
|
t.Error("expected provider to be set correctly")
|
|
}
|
|
|
|
if adapter.legacySettings != settings {
|
|
t.Error("expected legacy settings to be set correctly")
|
|
}
|
|
|
|
if adapter.tokenVerifier != verifier {
|
|
t.Error("expected token verifier to be set correctly")
|
|
}
|
|
|
|
if adapter.tokenCache != cache {
|
|
t.Error("expected token cache to be set correctly")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_GetType(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
provider OIDCProvider
|
|
expectedType ProviderType
|
|
}{
|
|
{
|
|
name: "Google provider",
|
|
provider: NewGoogleProvider(),
|
|
expectedType: ProviderTypeGoogle,
|
|
},
|
|
{
|
|
name: "Azure provider",
|
|
provider: NewAzureProvider(),
|
|
expectedType: ProviderTypeAzure,
|
|
},
|
|
{
|
|
name: "Generic provider",
|
|
provider: NewGenericProvider(),
|
|
expectedType: ProviderTypeGeneric,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
settings := &mockLegacySettings{clientID: "test-client"}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
|
|
adapter := NewAdapter(tt.provider, settings, verifier, cache)
|
|
providerType := adapter.GetType()
|
|
|
|
if providerType != tt.expectedType {
|
|
t.Errorf("expected provider type %d, got %d", tt.expectedType, providerType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdapter_ValidateTokens(t *testing.T) {
|
|
provider := NewGoogleProvider()
|
|
settings := &mockLegacySettings{
|
|
refreshGracePeriod: time.Minute * 5,
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{
|
|
data: map[string]map[string]interface{}{
|
|
"valid-token": {
|
|
"exp": float64(time.Now().Add(time.Hour).Unix()),
|
|
},
|
|
},
|
|
}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
|
|
tests := []struct {
|
|
name string
|
|
session *mockSession
|
|
expectedResult *ValidationResult
|
|
expectError bool
|
|
}{
|
|
{
|
|
name: "valid authenticated session",
|
|
session: &mockSession{
|
|
authenticated: true,
|
|
idToken: "valid-token",
|
|
accessToken: "access-token",
|
|
refreshToken: "refresh-token",
|
|
},
|
|
expectedResult: &ValidationResult{
|
|
Authenticated: true,
|
|
},
|
|
},
|
|
{
|
|
name: "unauthenticated with refresh token",
|
|
session: &mockSession{
|
|
authenticated: false,
|
|
refreshToken: "refresh-token",
|
|
},
|
|
expectedResult: &ValidationResult{
|
|
NeedsRefresh: true,
|
|
},
|
|
},
|
|
{
|
|
name: "unauthenticated without refresh token",
|
|
session: &mockSession{
|
|
authenticated: false,
|
|
},
|
|
expectedResult: &ValidationResult{},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
result, err := adapter.ValidateTokens(tt.session)
|
|
|
|
if tt.expectError {
|
|
if err == nil {
|
|
t.Error("expected error but got none")
|
|
}
|
|
return
|
|
}
|
|
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
return
|
|
}
|
|
|
|
if result == nil {
|
|
t.Fatal("expected non-nil result")
|
|
}
|
|
|
|
if result.Authenticated != tt.expectedResult.Authenticated {
|
|
t.Errorf("expected Authenticated %t, got %t", tt.expectedResult.Authenticated, result.Authenticated)
|
|
}
|
|
|
|
if result.NeedsRefresh != tt.expectedResult.NeedsRefresh {
|
|
t.Errorf("expected NeedsRefresh %t, got %t", tt.expectedResult.NeedsRefresh, result.NeedsRefresh)
|
|
}
|
|
|
|
if result.IsExpired != tt.expectedResult.IsExpired {
|
|
t.Errorf("expected IsExpired %t, got %t", tt.expectedResult.IsExpired, result.IsExpired)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdapter_BuildAuthURL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
provider OIDCProvider
|
|
settings *mockLegacySettings
|
|
redirectURL string
|
|
state string
|
|
nonce string
|
|
codeChallenge string
|
|
expectedSubstrs []string
|
|
unexpectedSubstrs []string
|
|
}{
|
|
{
|
|
name: "Google provider without override scopes",
|
|
provider: NewGoogleProvider(),
|
|
settings: &mockLegacySettings{
|
|
clientID: "google-client-id",
|
|
issuerURL: "https://accounts.google.com",
|
|
authURL: "https://accounts.google.com/o/oauth2/auth",
|
|
scopes: []string{"openid", "email", "profile"},
|
|
pkceEnabled: true,
|
|
overrideScopes: false,
|
|
},
|
|
redirectURL: "https://example.com/callback",
|
|
state: "random-state",
|
|
nonce: "random-nonce",
|
|
codeChallenge: "code-challenge",
|
|
expectedSubstrs: []string{
|
|
"client_id=google-client-id",
|
|
"response_type=code",
|
|
"redirect_uri=https%3A%2F%2Fexample.com%2Fcallback",
|
|
"state=random-state",
|
|
"nonce=random-nonce",
|
|
"code_challenge=code-challenge",
|
|
"code_challenge_method=S256",
|
|
"access_type=offline",
|
|
"prompt=consent",
|
|
"scope=openid+email+profile",
|
|
},
|
|
},
|
|
{
|
|
name: "Azure provider with override scopes",
|
|
provider: NewAzureProvider(),
|
|
settings: &mockLegacySettings{
|
|
clientID: "azure-client-id",
|
|
issuerURL: "https://login.microsoftonline.com/tenant",
|
|
authURL: "https://login.microsoftonline.com/tenant/oauth2/v2.0/authorize",
|
|
scopes: []string{"openid", "offline_access"},
|
|
pkceEnabled: false,
|
|
overrideScopes: true,
|
|
},
|
|
redirectURL: "https://example.com/azure-callback",
|
|
state: "azure-state",
|
|
nonce: "azure-nonce",
|
|
codeChallenge: "",
|
|
expectedSubstrs: []string{
|
|
"client_id=azure-client-id",
|
|
"response_type=code",
|
|
"redirect_uri=https%3A%2F%2Fexample.com%2Fazure-callback",
|
|
"state=azure-state",
|
|
"nonce=azure-nonce",
|
|
"response_mode=query",
|
|
"scope=openid+offline_access",
|
|
},
|
|
unexpectedSubstrs: []string{
|
|
"code_challenge",
|
|
"access_type",
|
|
"prompt",
|
|
},
|
|
},
|
|
{
|
|
name: "Generic provider with relative auth URL",
|
|
provider: NewGenericProvider(),
|
|
settings: &mockLegacySettings{
|
|
clientID: "generic-client-id",
|
|
issuerURL: "https://keycloak.example.com/auth/realms/master",
|
|
authURL: "/auth/realms/master/protocol/openid-connect/auth",
|
|
scopes: []string{"openid", "email"},
|
|
pkceEnabled: false,
|
|
overrideScopes: false,
|
|
},
|
|
redirectURL: "https://example.com/generic-callback",
|
|
state: "generic-state",
|
|
nonce: "generic-nonce",
|
|
codeChallenge: "",
|
|
expectedSubstrs: []string{
|
|
"keycloak.example.com",
|
|
"client_id=generic-client-id",
|
|
"response_type=code",
|
|
"scope=openid+email+offline_access", // Generic provider adds offline_access
|
|
},
|
|
},
|
|
{
|
|
name: "PKCE disabled",
|
|
provider: NewGoogleProvider(),
|
|
settings: &mockLegacySettings{
|
|
clientID: "google-client-id-no-pkce",
|
|
issuerURL: "https://accounts.google.com",
|
|
authURL: "https://accounts.google.com/o/oauth2/auth",
|
|
scopes: []string{"openid", "email"},
|
|
pkceEnabled: false,
|
|
overrideScopes: false,
|
|
},
|
|
redirectURL: "https://example.com/callback",
|
|
state: "state-no-pkce",
|
|
nonce: "nonce-no-pkce",
|
|
codeChallenge: "should-be-ignored",
|
|
expectedSubstrs: []string{
|
|
"client_id=google-client-id-no-pkce",
|
|
"state=state-no-pkce",
|
|
"nonce=nonce-no-pkce",
|
|
},
|
|
unexpectedSubstrs: []string{
|
|
"code_challenge",
|
|
"code_challenge_method",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
adapter := NewAdapter(tt.provider, tt.settings, verifier, cache)
|
|
|
|
authURL := adapter.BuildAuthURL(tt.redirectURL, tt.state, tt.nonce, tt.codeChallenge)
|
|
|
|
if authURL == "" {
|
|
t.Fatal("expected non-empty auth URL")
|
|
}
|
|
|
|
for _, expectedSubstr := range tt.expectedSubstrs {
|
|
if !strings.Contains(authURL, expectedSubstr) {
|
|
t.Errorf("expected auth URL to contain %q, got %q", expectedSubstr, authURL)
|
|
}
|
|
}
|
|
|
|
for _, unexpectedSubstr := range tt.unexpectedSubstrs {
|
|
if strings.Contains(authURL, unexpectedSubstr) {
|
|
t.Errorf("expected auth URL to NOT contain %q, got %q", unexpectedSubstr, authURL)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdapter_BuildAuthURL_ErrorCases(t *testing.T) {
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
|
|
t.Run("invalid issuer URL", func(t *testing.T) {
|
|
provider := NewGenericProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "test-client",
|
|
issuerURL: "://invalid-url",
|
|
authURL: "/relative/path",
|
|
scopes: []string{"openid"},
|
|
overrideScopes: false,
|
|
}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
authURL := adapter.BuildAuthURL("https://example.com/callback", "state", "nonce", "")
|
|
|
|
if authURL != "" {
|
|
t.Errorf("expected empty auth URL for invalid issuer URL, got %q", authURL)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid auth URL", func(t *testing.T) {
|
|
provider := NewGenericProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "test-client",
|
|
issuerURL: "https://example.com",
|
|
authURL: "://invalid-auth-url",
|
|
scopes: []string{"openid"},
|
|
overrideScopes: false,
|
|
}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
authURL := adapter.BuildAuthURL("https://example.com/callback", "state", "nonce", "")
|
|
|
|
if authURL != "" {
|
|
t.Errorf("expected empty auth URL for invalid auth URL, got %q", authURL)
|
|
}
|
|
})
|
|
|
|
t.Run("invalid absolute auth URL", func(t *testing.T) {
|
|
provider := NewGenericProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "test-client",
|
|
issuerURL: "https://example.com",
|
|
authURL: "://invalid-absolute-url",
|
|
scopes: []string{"openid"},
|
|
overrideScopes: false,
|
|
}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
authURL := adapter.BuildAuthURL("https://example.com/callback", "state", "nonce", "")
|
|
|
|
if authURL != "" {
|
|
t.Errorf("expected empty auth URL for invalid absolute auth URL, got %q", authURL)
|
|
}
|
|
})
|
|
|
|
t.Run("provider BuildAuthParams error", func(t *testing.T) {
|
|
// Create a mock provider that returns an error
|
|
mockProvider := &mockProviderWithError{}
|
|
settings := &mockLegacySettings{
|
|
clientID: "test-client",
|
|
issuerURL: "https://example.com",
|
|
authURL: "https://example.com/auth",
|
|
scopes: []string{"openid"},
|
|
overrideScopes: false,
|
|
}
|
|
|
|
adapter := NewAdapter(mockProvider, settings, verifier, cache)
|
|
authURL := adapter.BuildAuthURL("https://example.com/callback", "state", "nonce", "")
|
|
|
|
if authURL != "" {
|
|
t.Errorf("expected empty auth URL when provider returns error, got %q", authURL)
|
|
}
|
|
})
|
|
}
|
|
|
|
// mockProviderWithError is a test helper that returns errors from BuildAuthParams
|
|
type mockProviderWithError struct {
|
|
*BaseProvider
|
|
}
|
|
|
|
func (m *mockProviderWithError) GetType() ProviderType {
|
|
return ProviderTypeGeneric
|
|
}
|
|
|
|
func (m *mockProviderWithError) BuildAuthParams(baseParams url.Values, scopes []string) (*AuthParams, error) {
|
|
return nil, fmt.Errorf("mock error from BuildAuthParams")
|
|
}
|
|
|
|
func TestAdapter_ConcurrentAccess(t *testing.T) {
|
|
provider := NewGoogleProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "test-client",
|
|
issuerURL: "https://accounts.google.com",
|
|
authURL: "https://accounts.google.com/o/oauth2/auth",
|
|
scopes: []string{"openid", "email"},
|
|
pkceEnabled: true,
|
|
refreshGracePeriod: time.Minute,
|
|
overrideScopes: false,
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{
|
|
data: map[string]map[string]interface{}{
|
|
"valid-token": {
|
|
"exp": float64(time.Now().Add(time.Hour).Unix()),
|
|
},
|
|
},
|
|
}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
|
|
// Track initial goroutine count for memory safety
|
|
initialGoroutines := runtime.NumGoroutine()
|
|
|
|
const numGoroutines = 50
|
|
const numOperationsPerGoroutine = 10
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(numGoroutines)
|
|
|
|
// Test concurrent access to adapter methods
|
|
for i := 0; i < numGoroutines; i++ {
|
|
go func(workerID int) {
|
|
defer wg.Done()
|
|
|
|
for j := 0; j < numOperationsPerGoroutine; j++ {
|
|
// Test GetType
|
|
providerType := adapter.GetType()
|
|
if providerType != ProviderTypeGoogle {
|
|
t.Errorf("worker %d: expected Google provider type", workerID)
|
|
return
|
|
}
|
|
|
|
// Test BuildAuthURL
|
|
authURL := adapter.BuildAuthURL(
|
|
fmt.Sprintf("https://example.com/callback-%d", workerID),
|
|
fmt.Sprintf("state-%d-%d", workerID, j),
|
|
fmt.Sprintf("nonce-%d-%d", workerID, j),
|
|
fmt.Sprintf("challenge-%d-%d", workerID, j),
|
|
)
|
|
if authURL == "" {
|
|
t.Errorf("worker %d: expected non-empty auth URL", workerID)
|
|
return
|
|
}
|
|
|
|
// Test ValidateTokens
|
|
session := &mockSession{
|
|
authenticated: true,
|
|
idToken: "valid-token",
|
|
accessToken: fmt.Sprintf("access-token-%d", workerID),
|
|
refreshToken: fmt.Sprintf("refresh-token-%d", workerID),
|
|
}
|
|
|
|
result, err := adapter.ValidateTokens(session)
|
|
if err != nil {
|
|
t.Errorf("worker %d: unexpected error in ValidateTokens: %v", workerID, err)
|
|
return
|
|
}
|
|
if !result.Authenticated {
|
|
t.Errorf("worker %d: expected authenticated result", workerID)
|
|
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 TestAdapter_MemorySafety(t *testing.T) {
|
|
const numIterations = 1000
|
|
|
|
initialGoroutines := runtime.NumGoroutine()
|
|
|
|
for i := 0; i < numIterations; i++ {
|
|
provider := NewGenericProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: fmt.Sprintf("client-%d", i),
|
|
issuerURL: fmt.Sprintf("https://example%d.com", i),
|
|
authURL: fmt.Sprintf("https://example%d.com/auth", i),
|
|
scopes: []string{"openid", "email"},
|
|
pkceEnabled: i%2 == 0, // Alternate PKCE setting
|
|
refreshGracePeriod: time.Minute,
|
|
overrideScopes: i%3 == 0, // Alternate override setting
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{
|
|
data: map[string]map[string]interface{}{
|
|
fmt.Sprintf("token-%d", i): {
|
|
"exp": float64(time.Now().Add(time.Hour).Unix()),
|
|
},
|
|
},
|
|
}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
|
|
// Exercise all adapter methods
|
|
_ = adapter.GetType()
|
|
_ = adapter.BuildAuthURL("https://example.com/callback", "state", "nonce", "challenge")
|
|
|
|
session := &mockSession{
|
|
authenticated: true,
|
|
idToken: fmt.Sprintf("token-%d", i),
|
|
accessToken: fmt.Sprintf("access-%d", i),
|
|
refreshToken: fmt.Sprintf("refresh-%d", i),
|
|
}
|
|
_, _ = adapter.ValidateTokens(session)
|
|
}
|
|
|
|
// Force garbage collection
|
|
runtime.GC()
|
|
runtime.GC()
|
|
|
|
finalGoroutines := runtime.NumGoroutine()
|
|
if finalGoroutines > initialGoroutines+5 {
|
|
t.Errorf("potential goroutine leak: started with %d goroutines, ended with %d", initialGoroutines, finalGoroutines)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_EdgeCases(t *testing.T) {
|
|
t.Run("empty parameters", func(t *testing.T) {
|
|
provider := NewGenericProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "",
|
|
issuerURL: "",
|
|
authURL: "",
|
|
scopes: []string{},
|
|
pkceEnabled: false,
|
|
overrideScopes: false,
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
authURL := adapter.BuildAuthURL("", "", "", "")
|
|
|
|
// Should not crash, but may return empty or invalid URL
|
|
if authURL != "" {
|
|
t.Logf("Got auth URL with empty parameters: %s", authURL)
|
|
}
|
|
})
|
|
|
|
t.Run("very long parameters", func(t *testing.T) {
|
|
provider := NewGenericProvider()
|
|
longString := strings.Repeat("a", 5000)
|
|
settings := &mockLegacySettings{
|
|
clientID: longString,
|
|
issuerURL: "https://example.com/" + longString,
|
|
authURL: "https://example.com/" + longString + "/auth",
|
|
scopes: []string{"openid", longString},
|
|
pkceEnabled: true,
|
|
overrideScopes: false,
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
authURL := adapter.BuildAuthURL(
|
|
"https://example.com/callback",
|
|
longString,
|
|
longString,
|
|
longString,
|
|
)
|
|
|
|
// Should not crash
|
|
if authURL == "" {
|
|
t.Log("Long parameters resulted in empty auth URL")
|
|
}
|
|
})
|
|
|
|
t.Run("special characters in parameters", func(t *testing.T) {
|
|
provider := NewGenericProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "[email protected]",
|
|
issuerURL: "https://example.com/auth?param=value&other=test",
|
|
authURL: "https://example.com/auth/endpoint?default=param",
|
|
scopes: []string{"openid", "email+special", "profile/test"},
|
|
pkceEnabled: true,
|
|
overrideScopes: false,
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
authURL := adapter.BuildAuthURL(
|
|
"https://example.com/callback?return=url",
|
|
"state+with/special=chars&more",
|
|
"nonce_with_underscores",
|
|
"challenge-with-dashes",
|
|
)
|
|
|
|
if authURL == "" {
|
|
t.Error("expected non-empty auth URL with special characters")
|
|
}
|
|
|
|
// Verify URL is properly encoded
|
|
if !strings.Contains(authURL, "%") {
|
|
t.Error("expected auth URL to contain URL encoding")
|
|
}
|
|
})
|
|
}
|
|
|
|
// Benchmark tests for performance validation
|
|
func BenchmarkAdapter_GetType(b *testing.B) {
|
|
provider := NewGoogleProvider()
|
|
settings := &mockLegacySettings{clientID: "test-client"}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
adapter.GetType()
|
|
}
|
|
}
|
|
|
|
func BenchmarkAdapter_BuildAuthURL(b *testing.B) {
|
|
provider := NewGoogleProvider()
|
|
settings := &mockLegacySettings{
|
|
clientID: "test-client",
|
|
issuerURL: "https://accounts.google.com",
|
|
authURL: "https://accounts.google.com/o/oauth2/auth",
|
|
scopes: []string{"openid", "email", "profile"},
|
|
pkceEnabled: true,
|
|
overrideScopes: false,
|
|
}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{}
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
adapter.BuildAuthURL(
|
|
"https://example.com/callback",
|
|
"test-state",
|
|
"test-nonce",
|
|
"test-challenge",
|
|
)
|
|
}
|
|
}
|
|
|
|
func BenchmarkAdapter_ValidateTokens(b *testing.B) {
|
|
provider := NewGoogleProvider()
|
|
settings := &mockLegacySettings{refreshGracePeriod: time.Minute}
|
|
verifier := &mockTokenVerifier{}
|
|
cache := &mockTokenCache{
|
|
data: map[string]map[string]interface{}{
|
|
"test-token": {
|
|
"exp": float64(time.Now().Add(time.Hour).Unix()),
|
|
},
|
|
},
|
|
}
|
|
adapter := NewAdapter(provider, settings, verifier, cache)
|
|
|
|
session := &mockSession{
|
|
authenticated: true,
|
|
idToken: "test-token",
|
|
accessToken: "access-token",
|
|
refreshToken: "refresh-token",
|
|
}
|
|
|
|
b.ResetTimer()
|
|
for i := 0; i < b.N; i++ {
|
|
_, err := adapter.ValidateTokens(session)
|
|
if err != nil {
|
|
b.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
}
|