100, 'name' => 'replica_1'], ['weight' => 200, 'name' => 'replica_2'], // 2x weight ['weight' => 50, 'name' => 'replica_3'], // 0.5x weight ]; $totalWeight = array_sum(array_column($replicas, 'weight')); // 350 // Simulate weighted selection distribution $selections = []; $iterations = 1000; for ($i = 0; $i < $iterations; $i++) { $randomValue = mt_rand(0, $totalWeight - 1); $currentWeight = 0; foreach ($replicas as $index => $replica) { $currentWeight += $replica['weight']; if ($randomValue < $currentWeight) { $selections[$index] = ($selections[$index] ?? 0) + 1; break; } } } // Calculate percentages $percentages = []; foreach ($selections as $index => $count) { $percentages[$index] = ($count / $iterations) * 100; } // Expected distributions (with some tolerance for randomness) $expectedPercentages = [ 0 => (100 / 350) * 100, // ~28.57% 1 => (200 / 350) * 100, // ~57.14% 2 => (50 / 350) * 100, // ~14.29% ]; // Verify distribution is roughly correct (±5% tolerance) foreach ($expectedPercentages as $index => $expected) { $actual = $percentages[$index] ?? 0; expect($actual)->toBeGreaterThan($expected - 5) ->and($actual)->toBeLessThan($expected + 5); } // Verify replica_2 gets roughly twice as many selections as replica_1 $ratio = ($percentages[1] ?? 0) / ($percentages[0] ?? 1); expect($ratio)->toBeGreaterThan(1.5) ->and($ratio)->toBeLessThan(2.5); // 2.0 ± 0.5 }); test('weight adjustment based on load factor works correctly', function () { // Test load-based weight adjustment $baseWeight = 100; $maxConnections = 10; // Test cases: [current_connections, expected_load_factor] $testCases = [ [0, 1.0], // No load = full weight [5, 0.5], // 50% load = 50% weight [8, 0.2], // 80% load = 20% weight [10, 0.1], // 100% load = minimum 10% weight [15, 0.1], // Over capacity = minimum 10% weight ]; foreach ($testCases as [$currentConnections, $expectedLoadFactor]) { $loadFactor = $maxConnections > 0 ? 1 - ($currentConnections / $maxConnections) : 1; $loadFactor = max(0.1, $loadFactor); // Minimum 10% weight expect($loadFactor)->toBeGreaterThanOrEqual($expectedLoadFactor - 0.001) ->and($loadFactor)->toBeLessThanOrEqual($expectedLoadFactor + 0.001); $adjustedWeight = (int)round($baseWeight * $loadFactor); $expectedWeight = (int)round($baseWeight * $expectedLoadFactor); expect($adjustedWeight)->toBe($expectedWeight); } }); test('response time factor adjusts weight correctly', function () { // Test response time-based weight adjustment $baseWeight = 100; // Test cases: [avg_response_time_ms, expected_min_factor] $testCases = [ [50.0, 1.0], // Very fast = full weight (100/50 = 2.0, capped at 1.0) [100.0, 1.0], // Fast = full weight (100/100 = 1.0) [200.0, 0.5], // Slow = 50% weight (100/200 = 0.5) [500.0, 0.2], // Very slow = 20% weight (100/500 = 0.2) [1000.0, 0.1], // Extremely slow = minimum 10% weight ]; foreach ($testCases as [$responseTime, $expectedMinFactor]) { $responseFactor = $responseTime > 0 ? min(1.0, 100 / $responseTime) : 1; $responseFactor = max(0.1, $responseFactor); // Minimum 10% weight expect($responseFactor)->toBeGreaterThanOrEqual($expectedMinFactor - 0.01) ->and($responseFactor)->toBeLessThanOrEqual($expectedMinFactor + 0.01); } }); test('connection counting tracks connections correctly', function () { // Test connection count tracking logic $connectionCounts = [0, 0, 0]; // 3 replicas, all start at 0 // Simulate connection increments $operations = [ ['replica' => 0, 'operation' => 'increment'], ['replica' => 1, 'operation' => 'increment'], ['replica' => 0, 'operation' => 'increment'], ['replica' => 2, 'operation' => 'increment'], ['replica' => 1, 'operation' => 'increment'], ['replica' => 0, 'operation' => 'decrement'], ['replica' => 1, 'operation' => 'increment'], ]; foreach ($operations as $op) { $replicaIndex = $op['replica']; if ($op['operation'] === 'increment') { $connectionCounts[$replicaIndex]++; } else { $connectionCounts[$replicaIndex] = max(0, $connectionCounts[$replicaIndex] - 1); } } // Expected final counts expect($connectionCounts[0])->toBe(1); // 2 increments, 1 decrement = 1 expect($connectionCounts[1])->toBe(3); // 3 increments = 3 expect($connectionCounts[2])->toBe(1); // 1 increment = 1 // Test decrement doesn't go below 0 $connectionCounts[2]--; $connectionCounts[2] = max(0, $connectionCounts[2]); expect($connectionCounts[2])->toBe(0); $connectionCounts[2]--; // Should stay at 0 $connectionCounts[2] = max(0, $connectionCounts[2]); expect($connectionCounts[2])->toBe(0); }); test('response time history maintains correct window size', function () { // Test response time history window management $maxHistorySize = 5; // Smaller window for testing $responseHistory = []; // Add response times $responseTimes = [100, 150, 200, 120, 180, 90, 110, 160]; foreach ($responseTimes as $time) { $responseHistory[] = $time; // Keep only last N response times if (count($responseHistory) > $maxHistorySize) { array_shift($responseHistory); } } // Should contain only the last 5 values expect($responseHistory)->toHaveCount($maxHistorySize); expect($responseHistory)->toBe([120, 180, 90, 110, 160]); // Last 5 values // Let's recalculate correctly $responseHistory = []; foreach ($responseTimes as $time) { $responseHistory[] = $time; if (count($responseHistory) > $maxHistorySize) { array_shift($responseHistory); } } expect($responseHistory)->toHaveCount($maxHistorySize); expect($responseHistory)->toBe([120, 180, 90, 110, 160]); // Last 5 values // Test average calculation $average = array_sum($responseHistory) / count($responseHistory); $expectedAverage = (120 + 180 + 90 + 110 + 160) / 5; // 132 expect($average)->toBe($expectedAverage); }); test('load balancing strategy selection logic', function () { // Test different load balancing strategies $strategies = [ 'ROUND_ROBIN' => 'Simple round-robin selection', 'WEIGHTED' => 'Weight-based selection with load adjustment', 'LEAST_CONNECTIONS' => 'Select replica with fewest connections', 'RESPONSE_TIME' => 'Select replica with best response time', ]; // Simulate replica data $replicas = [ ['connections' => 5, 'weight' => 100, 'response_time' => 120], ['connections' => 8, 'weight' => 150, 'response_time' => 90], ['connections' => 3, 'weight' => 80, 'response_time' => 200], ]; // Test LEAST_CONNECTIONS logic $minConnections = min(array_column($replicas, 'connections')); $leastConnectionsIndex = array_search($minConnections, array_column($replicas, 'connections')); expect($leastConnectionsIndex)->toBe(2); // Index 2 has 3 connections (minimum) // Test RESPONSE_TIME logic $minResponseTime = min(array_column($replicas, 'response_time')); $fastestReplicaIndex = array_search($minResponseTime, array_column($replicas, 'response_time')); expect($fastestReplicaIndex)->toBe(1); // Index 1 has 90ms response time (minimum) // Test weight calculation with all factors foreach ($replicas as $index => $replica) { $maxConnections = 10; $loadFactor = max(0.1, 1 - ($replica['connections'] / $maxConnections)); $responseFactor = max(0.1, min(1.0, 100 / $replica['response_time'])); $adjustedWeight = (int)($replica['weight'] * $loadFactor * $responseFactor); // Verify minimum weight is maintained expect($adjustedWeight)->toBeGreaterThanOrEqual(1); } }); test('routing statistics provide comprehensive monitoring data', function () { // Test statistics generation logic $replicas = [ [ 'healthy' => true, 'connections' => 5, 'max_connections' => 10, 'weight' => 100, 'total_queries' => 1000, 'failed_queries' => 10, 'response_samples' => 50, ], [ 'healthy' => false, 'connections' => 0, 'max_connections' => 10, 'weight' => 150, 'total_queries' => 0, 'failed_queries' => 0, 'response_samples' => 0, ], [ 'healthy' => true, 'connections' => 8, 'max_connections' => 10, 'weight' => 80, 'total_queries' => 500, 'failed_queries' => 5, 'response_samples' => 25, ], ]; // Calculate expected statistics $totalReplicas = count($replicas); $healthyReplicas = count(array_filter($replicas, fn ($r) => $r['healthy'])); expect($totalReplicas)->toBe(3); expect($healthyReplicas)->toBe(2); // Test load percentage calculation foreach ($replicas as $index => $replica) { $loadPercentage = ($replica['connections'] / $replica['max_connections']) * 100; if ($index === 0) { expect($loadPercentage)->toBe(50.0); // 5/10 = 50% } elseif ($index === 2) { expect($loadPercentage)->toBe(80.0); // 8/10 = 80% } } // Test success rate calculation foreach ($replicas as $replica) { $totalQueries = $replica['total_queries']; $failedQueries = $replica['failed_queries']; if ($totalQueries === 0) { $successRate = 100.0; } else { $successfulQueries = $totalQueries - $failedQueries; $successRate = ($successfulQueries / $totalQueries) * 100; } if ($replica['total_queries'] === 1000) { expect($successRate)->toBe(99.0); // 990/1000 = 99% } elseif ($replica['total_queries'] === 500) { expect($successRate)->toBe(99.0); // 495/500 = 99% } else { expect($successRate)->toBe(100.0); // No queries = 100% } } });