mirror of
https://github.com/lukaszraczylo/traefikoidc.git
synced 2026-10-14 04:07:46 +00:00
493 lines
12 KiB
Go
493 lines
12 KiB
Go
package traefikoidc
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Test the core OptimizedCache functionality that's not covered
|
|
func TestOptimizedCacheBasic(t *testing.T) {
|
|
cache := NewOptimizedCache()
|
|
if cache == nil {
|
|
t.Fatal("NewOptimizedCache returned nil")
|
|
}
|
|
|
|
// Basic set and get
|
|
cache.Set("test_key", "test_value", 5*time.Minute)
|
|
if val, found := cache.Get("test_key"); !found || val != "test_value" {
|
|
t.Errorf("Failed to get cached value: %v, %v", val, found)
|
|
}
|
|
|
|
// Delete
|
|
cache.Delete("test_key")
|
|
if _, found := cache.Get("test_key"); found {
|
|
t.Error("Value should be deleted")
|
|
}
|
|
|
|
// Test with config
|
|
logger := NewLogger("debug")
|
|
config := OptimizedCacheConfig{
|
|
MaxSize: 50,
|
|
MaxMemoryBytes: 10,
|
|
Logger: logger,
|
|
EnableMemoryLimit: true,
|
|
}
|
|
cache2 := NewOptimizedCacheWithConfig(config)
|
|
if cache2 == nil {
|
|
t.Fatal("NewOptimizedCacheWithConfig returned nil")
|
|
}
|
|
|
|
// Test cleanup
|
|
cache2.Set("expire", "value", 100*time.Millisecond)
|
|
time.Sleep(150 * time.Millisecond)
|
|
cache2.Cleanup()
|
|
if _, found := cache2.Get("expire"); found {
|
|
t.Error("Expired item should be cleaned up")
|
|
}
|
|
|
|
// Test close
|
|
cache2.Close()
|
|
}
|
|
|
|
// Test UnifiedCache basic functionality
|
|
func TestUnifiedCacheBasic(t *testing.T) {
|
|
config := DefaultUnifiedCacheConfig()
|
|
if config.MaxSize <= 0 {
|
|
t.Error("Default config should have positive MaxSize")
|
|
}
|
|
|
|
cache := NewUnifiedCache(config)
|
|
if cache == nil {
|
|
t.Fatal("NewUnifiedCache returned nil")
|
|
}
|
|
|
|
// Basic operations
|
|
cache.Set("key1", "value1", 5*time.Minute)
|
|
if val, found := cache.Get("key1"); !found || val != "value1" {
|
|
t.Errorf("Failed to get cached value: %v", val)
|
|
}
|
|
|
|
cache.Delete("key1")
|
|
if _, found := cache.Get("key1"); found {
|
|
t.Error("Value should be deleted")
|
|
}
|
|
|
|
// Cleanup
|
|
cache.Set("expired", "value", 1*time.Nanosecond)
|
|
time.Sleep(10 * time.Millisecond)
|
|
cache.Cleanup()
|
|
|
|
// GetMetrics
|
|
metrics := cache.GetMetrics()
|
|
if metrics == nil {
|
|
t.Error("GetMetrics should not return nil")
|
|
}
|
|
|
|
cache.Close()
|
|
}
|
|
|
|
// Test memory pool functionality
|
|
func TestMemoryPoolBasic(t *testing.T) {
|
|
manager := NewMemoryPoolManager()
|
|
if manager == nil {
|
|
t.Fatal("NewMemoryPoolManager returned nil")
|
|
}
|
|
|
|
// Test compression buffer
|
|
buf := manager.GetCompressionBuffer()
|
|
if buf == nil {
|
|
t.Fatal("GetCompressionBuffer returned nil")
|
|
}
|
|
buf.WriteString("test")
|
|
manager.PutCompressionBuffer(buf)
|
|
|
|
// Test JWT parsing buffer
|
|
jwtBuf := manager.GetJWTParsingBuffer()
|
|
if jwtBuf == nil {
|
|
t.Fatal("GetJWTParsingBuffer returned nil")
|
|
}
|
|
manager.PutJWTParsingBuffer(jwtBuf)
|
|
|
|
// Test HTTP response buffer
|
|
httpBuf := manager.GetHTTPResponseBuffer()
|
|
if httpBuf == nil {
|
|
t.Fatal("GetHTTPResponseBuffer returned nil")
|
|
}
|
|
manager.PutHTTPResponseBuffer(httpBuf)
|
|
|
|
// Test string builder
|
|
sb := manager.GetStringBuilder()
|
|
if sb == nil {
|
|
t.Fatal("GetStringBuilder returned nil")
|
|
}
|
|
manager.PutStringBuilder(sb)
|
|
}
|
|
|
|
// Test global memory pools
|
|
func TestGlobalPools(t *testing.T) {
|
|
pools := GetGlobalMemoryPools()
|
|
if pools == nil {
|
|
t.Fatal("GetGlobalMemoryPools returned nil")
|
|
}
|
|
|
|
// Should be singleton
|
|
pools2 := GetGlobalMemoryPools()
|
|
if pools != pools2 {
|
|
t.Error("GetGlobalMemoryPools should return singleton")
|
|
}
|
|
|
|
// Cleanup should not panic
|
|
CleanupGlobalMemoryPools()
|
|
}
|
|
|
|
// Test TokenCompressionPool
|
|
func TestTokenCompression(t *testing.T) {
|
|
pool := NewTokenCompressionPool()
|
|
if pool == nil {
|
|
t.Fatal("NewTokenCompressionPool returned nil")
|
|
}
|
|
|
|
compBuf := pool.GetCompressionBuffer()
|
|
if compBuf == nil {
|
|
t.Fatal("GetCompressionBuffer returned nil")
|
|
}
|
|
pool.PutCompressionBuffer(compBuf)
|
|
|
|
decompBuf := pool.GetDecompressionBuffer()
|
|
if decompBuf == nil {
|
|
t.Fatal("GetDecompressionBuffer returned nil")
|
|
}
|
|
pool.PutDecompressionBuffer(decompBuf)
|
|
}
|
|
|
|
// Test error recovery base functionality
|
|
func TestErrorRecoveryBasic(t *testing.T) {
|
|
logger := NewLogger("debug")
|
|
|
|
// Test BaseRecoveryMechanism
|
|
base := NewBaseRecoveryMechanism("test", logger)
|
|
base.RecordRequest()
|
|
base.RecordSuccess()
|
|
base.RecordFailure()
|
|
|
|
metrics := base.GetBaseMetrics()
|
|
if metrics == nil {
|
|
t.Fatal("GetBaseMetrics returned nil")
|
|
}
|
|
|
|
// Test CircuitBreaker
|
|
cbConfig := CircuitBreakerConfig{
|
|
MaxFailures: 3,
|
|
ResetTimeout: 100 * time.Millisecond,
|
|
}
|
|
cb := NewCircuitBreaker(cbConfig, logger)
|
|
|
|
if cb.GetState() != CircuitBreakerClosed {
|
|
t.Error("Initial state should be closed")
|
|
}
|
|
|
|
// Trip the circuit
|
|
for i := 0; i < 3; i++ {
|
|
cb.Execute(func() error {
|
|
return fmt.Errorf("error")
|
|
})
|
|
}
|
|
|
|
if cb.GetState() != CircuitBreakerOpen {
|
|
t.Error("Circuit should be open after max failures")
|
|
}
|
|
|
|
cb.Reset()
|
|
if cb.GetState() != CircuitBreakerClosed {
|
|
t.Error("Circuit should be closed after reset")
|
|
}
|
|
|
|
// Test RetryExecutor
|
|
retryConfig := RetryConfig{
|
|
MaxAttempts: 3,
|
|
InitialDelay: 10 * time.Millisecond,
|
|
}
|
|
re := NewRetryExecutor(retryConfig, logger)
|
|
|
|
if re != nil && !re.IsAvailable() {
|
|
t.Error("RetryExecutor should be available")
|
|
}
|
|
}
|
|
|
|
// Test profiling components
|
|
func TestProfilingBasic(t *testing.T) {
|
|
logger := NewLogger("debug")
|
|
|
|
// Test ProfilingManager
|
|
pm := NewProfilingManager(logger)
|
|
if pm == nil {
|
|
t.Fatal("NewProfilingManager returned nil")
|
|
}
|
|
|
|
// Take snapshot - may fail if profiling is disabled
|
|
snapshot, _ := pm.TakeSnapshot()
|
|
if snapshot != nil {
|
|
t.Log("Snapshot taken successfully")
|
|
}
|
|
|
|
// Test global singletons
|
|
globalPM := GetGlobalProfilingManager()
|
|
if globalPM == nil {
|
|
t.Fatal("GetGlobalProfilingManager returned nil")
|
|
}
|
|
|
|
globalMTO := GetGlobalTestOrchestrator()
|
|
if globalMTO == nil {
|
|
t.Fatal("GetGlobalTestOrchestrator returned nil")
|
|
}
|
|
}
|
|
|
|
// Test cache strategies
|
|
func TestCacheStrategies(t *testing.T) {
|
|
// Test LRU strategy
|
|
lru := NewLRUStrategy(10)
|
|
if lru.Name() != "LRU" {
|
|
t.Errorf("Expected LRU name, got %s", lru.Name())
|
|
}
|
|
|
|
// Test doubly linked list
|
|
list := NewDoublyLinkedList()
|
|
node1 := list.PushBack("key1")
|
|
if node1 == nil || node1.Key != "key1" {
|
|
t.Error("PushBack failed")
|
|
}
|
|
|
|
list.PushBack("key2")
|
|
list.PushBack("key3")
|
|
|
|
list.MoveToBack(node1)
|
|
|
|
popped := list.PopFront()
|
|
if popped == nil || popped.Key != "key2" {
|
|
t.Errorf("Expected key2, got %v", popped)
|
|
}
|
|
}
|
|
|
|
// Test CacheAdapter
|
|
func TestCacheAdapterBasic(t *testing.T) {
|
|
config := DefaultUnifiedCacheConfig()
|
|
unified := NewUnifiedCache(config)
|
|
adapter := NewCacheAdapter(unified)
|
|
|
|
adapter.Set("key", "value", 5*time.Minute)
|
|
if val, found := adapter.Get("key"); !found || val != "value" {
|
|
t.Errorf("CacheAdapter get failed: %v", val)
|
|
}
|
|
|
|
adapter.Delete("key")
|
|
adapter.Cleanup()
|
|
adapter.Close()
|
|
}
|
|
|
|
// Test JWT validation edge cases
|
|
func TestJWTValidationCases(t *testing.T) {
|
|
validator := NewTokenValidator(NewLogger("debug"))
|
|
|
|
// Test valid JWT
|
|
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"RS256","typ":"JWT"}`))
|
|
claims := map[string]interface{}{
|
|
"sub": "user123",
|
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
|
"iat": time.Now().Unix(),
|
|
}
|
|
claimsJSON, _ := json.Marshal(claims)
|
|
payload := base64.RawURLEncoding.EncodeToString(claimsJSON)
|
|
signature := base64.RawURLEncoding.EncodeToString([]byte("fake-signature"))
|
|
|
|
token := header + "." + payload + "." + signature
|
|
|
|
result := validator.ValidateToken(token, true)
|
|
if !result.Valid {
|
|
t.Errorf("Expected valid token, got error: %v", result.Error)
|
|
}
|
|
|
|
// Test expired JWT
|
|
expiredClaims := map[string]interface{}{
|
|
"sub": "user123",
|
|
"exp": time.Now().Add(-1 * time.Hour).Unix(),
|
|
}
|
|
expiredJSON, _ := json.Marshal(expiredClaims)
|
|
expiredPayload := base64.RawURLEncoding.EncodeToString(expiredJSON)
|
|
expiredToken := header + "." + expiredPayload + "." + signature
|
|
|
|
expiredResult := validator.ValidateToken(expiredToken, true)
|
|
if expiredResult.Valid {
|
|
t.Error("Expired token should be invalid")
|
|
}
|
|
|
|
// Test opaque token
|
|
opaqueToken := "abcdefghijklmnopqrstuvwxyz1234567890"
|
|
opaqueResult := validator.ValidateToken(opaqueToken, false)
|
|
if !opaqueResult.Valid {
|
|
t.Errorf("Valid opaque token rejected: %v", opaqueResult.Error)
|
|
}
|
|
|
|
// Test ExtractClaims
|
|
extractedClaims, err := validator.ExtractClaims(token)
|
|
if err != nil || extractedClaims["sub"] != "user123" {
|
|
t.Errorf("ExtractClaims failed: %v", err)
|
|
}
|
|
|
|
// Test CompareTokens
|
|
if !validator.CompareTokens("token123", "token123") {
|
|
t.Error("Identical tokens should match")
|
|
}
|
|
if validator.CompareTokens("token123", "token456") {
|
|
t.Error("Different tokens should not match")
|
|
}
|
|
|
|
// Test ValidateTokenSize
|
|
if err := validator.ValidateTokenSize("small", 100); err != nil {
|
|
t.Errorf("Small token should be valid: %v", err)
|
|
}
|
|
if err := validator.ValidateTokenSize(strings.Repeat("a", 200), 100); err == nil {
|
|
t.Error("Large token should be invalid")
|
|
}
|
|
}
|
|
|
|
// Test replay cache
|
|
func TestReplayCacheBasic(t *testing.T) {
|
|
// Test getReplayCacheStats
|
|
size, maxSize := getReplayCacheStats()
|
|
if size < 0 || maxSize <= 0 {
|
|
t.Errorf("Invalid replay cache stats: size=%d, maxSize=%d", size, maxSize)
|
|
}
|
|
|
|
// Test cleanupReplayCache (should not panic)
|
|
cleanupReplayCache()
|
|
}
|
|
|
|
// Test audience verification
|
|
func TestAudienceVerify(t *testing.T) {
|
|
// Exact match
|
|
if err := verifyAudience("my-client", "my-client"); err != nil {
|
|
t.Errorf("Exact match should succeed: %v", err)
|
|
}
|
|
|
|
// Array contains
|
|
audArray := []interface{}{"client1", "my-client", "client2"}
|
|
if err := verifyAudience(audArray, "my-client"); err != nil {
|
|
t.Errorf("Array contains should succeed: %v", err)
|
|
}
|
|
|
|
// Mismatch
|
|
if err := verifyAudience("other-client", "my-client"); err == nil {
|
|
t.Error("Mismatch should fail")
|
|
}
|
|
|
|
// Missing/nil audience
|
|
if err := verifyAudience(nil, "my-client"); err == nil {
|
|
t.Error("Nil aud should fail")
|
|
}
|
|
}
|
|
|
|
// Test helper functions
|
|
func TestHelpers(t *testing.T) {
|
|
// Test nonce generation
|
|
nonce1, err := generateNonce()
|
|
if err != nil {
|
|
t.Fatalf("generateNonce failed: %v", err)
|
|
}
|
|
if len(nonce1) == 0 {
|
|
t.Error("Nonce should not be empty")
|
|
}
|
|
|
|
nonce2, _ := generateNonce()
|
|
if nonce1 == nonce2 {
|
|
t.Error("Nonces should be unique")
|
|
}
|
|
|
|
// Test code verifier generation
|
|
verifier1, err := generateCodeVerifier()
|
|
if err != nil {
|
|
t.Fatalf("generateCodeVerifier failed: %v", err)
|
|
}
|
|
if len(verifier1) == 0 {
|
|
t.Error("Code verifier should not be empty")
|
|
}
|
|
|
|
verifier2, _ := generateCodeVerifier()
|
|
if verifier1 == verifier2 {
|
|
t.Error("Code verifiers should be unique")
|
|
}
|
|
}
|
|
|
|
// Test HTTP client factory
|
|
func TestHTTPClientCreation(t *testing.T) {
|
|
// Default client
|
|
client1 := CreateDefaultHTTPClient()
|
|
if client1 == nil {
|
|
t.Fatal("CreateDefaultHTTPClient returned nil")
|
|
}
|
|
if client1.Timeout != 30*time.Second {
|
|
t.Errorf("Expected 30s timeout, got %v", client1.Timeout)
|
|
}
|
|
|
|
// Token client
|
|
client2 := CreateTokenHTTPClient()
|
|
if client2 == nil {
|
|
t.Fatal("CreateTokenHTTPClient returned nil")
|
|
}
|
|
if client2.Timeout != 10*time.Second {
|
|
t.Errorf("Expected 10s timeout, got %v", client2.Timeout)
|
|
}
|
|
|
|
// Custom config client
|
|
config := HTTPClientConfig{
|
|
Timeout: 5 * time.Second,
|
|
DisableCompression: true,
|
|
DisableKeepAlives: true,
|
|
}
|
|
client3 := CreateHTTPClientWithConfig(config)
|
|
if client3 == nil {
|
|
t.Fatal("CreateHTTPClientWithConfig returned nil")
|
|
}
|
|
if client3.Timeout != 5*time.Second {
|
|
t.Errorf("Expected 5s timeout, got %v", client3.Timeout)
|
|
}
|
|
}
|
|
|
|
// Test metadata cache
|
|
func TestMetadataCaching(t *testing.T) {
|
|
var wg sync.WaitGroup
|
|
logger := NewLogger("debug")
|
|
cache := NewMetadataCacheWithLogger(&wg, logger)
|
|
if cache == nil {
|
|
t.Fatal("NewMetadataCache returned nil")
|
|
}
|
|
// Basic test that cache was created
|
|
}
|
|
|
|
// Test JWT parsing
|
|
func TestJWTParsing(t *testing.T) {
|
|
header := base64.RawURLEncoding.EncodeToString([]byte(`{"alg":"RS256","typ":"JWT"}`))
|
|
claims := map[string]interface{}{
|
|
"sub": "user123",
|
|
"exp": time.Now().Add(1 * time.Hour).Unix(),
|
|
}
|
|
claimsJSON, _ := json.Marshal(claims)
|
|
payload := base64.RawURLEncoding.EncodeToString(claimsJSON)
|
|
signature := base64.RawURLEncoding.EncodeToString([]byte("signature"))
|
|
|
|
token := header + "." + payload + "." + signature
|
|
|
|
parsedJWT, err := parseJWT(token)
|
|
if err != nil {
|
|
t.Fatalf("Failed to parse JWT: %v", err)
|
|
}
|
|
|
|
if parsedJWT.Claims["sub"] != "user123" {
|
|
t.Errorf("Expected sub=user123, got %v", parsedJWT.Claims["sub"])
|
|
}
|
|
}
|