strategy = new AdaptiveTtlCacheStrategy( enabled: true, learningWindow: 10, minTtl: Duration::fromSeconds(30), maxTtl: Duration::fromHours(2) ); }); describe('Basic Operations', function () { it('is enabled by default', function () { expect($this->strategy->isEnabled())->toBeTrue(); expect($this->strategy->getName())->toBe('adaptive_ttl'); }); it('tracks cache access patterns', function () { $key = CacheKey::fromString('test-key'); // Record multiple accesses for ($i = 0; $i < 5; $i++) { $this->strategy->onCacheAccess($key, true); } $stats = $this->strategy->getStats(); expect($stats['strategy'])->toBe('AdaptiveTtlCacheStrategy'); expect($stats['total_tracked_keys'])->toBe(1); expect($stats['enabled'])->toBeTrue(); }); it('provides default TTL for new keys', function () { $key = CacheKey::fromString('new-key'); $originalTtl = Duration::fromMinutes(10); $adaptedTtl = $this->strategy->onCacheSet($key, 'value', $originalTtl); expect($adaptedTtl)->toBeInstanceOf(Duration::class); expect($adaptedTtl->toSeconds())->toBeGreaterThan(29); // >= Min TTL (30) expect($adaptedTtl->toSeconds())->toBeLessThan(7201); // <= Max TTL (7200) }); it('clears all tracking data', function () { $key = CacheKey::fromString('test-key'); $this->strategy->onCacheAccess($key, true); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(1); $this->strategy->clear(); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(0); }); it('forgets key-specific data', function () { $key1 = CacheKey::fromString('key1'); $key2 = CacheKey::fromString('key2'); $this->strategy->onCacheAccess($key1, true); $this->strategy->onCacheAccess($key2, true); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(2); $this->strategy->onCacheForget($key1); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(1); }); }); describe('TTL Adaptation', function () { it('extends TTL for frequently accessed keys', function () { $key = CacheKey::fromString('hot-key'); // Simulate high access count with good hit rate for ($i = 0; $i < 15; $i++) { $this->strategy->onCacheAccess($key, true); // High hit rate } $originalTtl = Duration::fromMinutes(30); $adaptedTtl = $this->strategy->onCacheSet($key, 'value', $originalTtl); // Should extend TTL for frequently accessed keys expect($adaptedTtl->toSeconds())->toBeGreaterThan($originalTtl->toSeconds() - 1); }); it('reduces TTL for rarely accessed keys', function () { $key = CacheKey::fromString('cold-key'); // Simulate low access count with poor hit rate $this->strategy->onCacheAccess($key, false); $this->strategy->onCacheAccess($key, false); $originalTtl = Duration::fromMinutes(30); $adaptedTtl = $this->strategy->onCacheSet($key, 'value', $originalTtl); // Should reduce or maintain TTL for rarely accessed keys expect($adaptedTtl->toSeconds())->toBeLessThan($originalTtl->toSeconds() + 1); }); it('enforces minimum TTL bounds', function () { $key = CacheKey::fromString('min-bound-key'); $veryShortTtl = Duration::fromSeconds(5); // Below minimum $adaptedTtl = $this->strategy->onCacheSet($key, 'value', $veryShortTtl); // Should be at least the minimum TTL expect($adaptedTtl->toSeconds())->toBeGreaterThan(29); expect($adaptedTtl->toSeconds())->toBeLessThan(61); // Shouldn't be much higher }); it('enforces maximum TTL bounds', function () { $key = CacheKey::fromString('max-bound-key'); $veryLongTtl = Duration::fromHours(24); // Above maximum $adaptedTtl = $this->strategy->onCacheSet($key, 'value', $veryLongTtl); // Should be capped at maximum TTL (2 hours = 7200 seconds) expect($adaptedTtl->toSeconds())->toBeLessThan(7201); expect($adaptedTtl->toSeconds())->toBeGreaterThan(7199); }); }); describe('Statistics and Insights', function () { it('provides comprehensive statistics', function () { $key1 = CacheKey::fromString('key1'); $key2 = CacheKey::fromString('key2'); // Simulate access patterns for ($i = 0; $i < 5; $i++) { $this->strategy->onCacheAccess($key1, true); } for ($i = 0; $i < 3; $i++) { $this->strategy->onCacheAccess($key2, false); } $stats = $this->strategy->getStats(); expect($stats)->toHaveKeys([ 'strategy', 'enabled', 'total_tracked_keys', 'learning_window', 'ttl_bounds', 'adaptation_factors', 'key_patterns' ]); }); it('tracks hit rate per key', function () { $key = CacheKey::fromString('mixed-key'); // 3 hits, 2 misses = 60% hit rate $this->strategy->onCacheAccess($key, true); $this->strategy->onCacheAccess($key, true); $this->strategy->onCacheAccess($key, true); $this->strategy->onCacheAccess($key, false); $this->strategy->onCacheAccess($key, false); $stats = $this->strategy->getStats(); expect($stats['key_patterns'])->toHaveKey('mixed-key'); expect($stats['key_patterns']['mixed-key']['hit_rate'])->toBe(0.6); expect($stats['key_patterns']['mixed-key']['total_requests'])->toBe(5); }); }); describe('Disabled Strategy', function () { it('does nothing when disabled', function () { $disabledStrategy = new AdaptiveTtlCacheStrategy(enabled: false); $key = CacheKey::fromString('disabled-key'); $disabledStrategy->onCacheAccess($key, true); $stats = $disabledStrategy->getStats(); expect($stats['enabled'])->toBeFalse(); expect($stats['total_tracked_keys'])->toBe(0); }); }); }); describe('HeatMapCacheStrategy', function () { beforeEach(function () { $this->strategy = new HeatMapCacheStrategy( enabled: true, maxTrackedKeys: 100, hotThreshold: 10, coldThreshold: 1, analysisWindowHours: 2 ); }); describe('Basic Operations', function () { it('is enabled by default', function () { expect($this->strategy->isEnabled())->toBeTrue(); expect($this->strategy->getName())->toBe('heat_map'); }); it('tracks cache access patterns', function () { $key = CacheKey::fromString('test-key'); $this->strategy->onCacheAccess($key, true, Duration::fromMilliseconds(50)); $stats = $this->strategy->getStats(); expect($stats['strategy'])->toBe('HeatMapCacheStrategy'); expect($stats['total_tracked_keys'])->toBe(1); }); it('respects max tracked keys limit', function () { // Create more keys than the limit for ($i = 0; $i < 150; $i++) { $key = CacheKey::fromString("key-{$i}"); $this->strategy->onCacheAccess($key, true); } $stats = $this->strategy->getStats(); expect($stats['total_tracked_keys'])->toBeLessThan(101); }); it('clears all heat map data', function () { $key = CacheKey::fromString('test-key'); $this->strategy->onCacheAccess($key, true); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(1); $this->strategy->clear(); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(0); }); it('forgets key-specific data', function () { $key1 = CacheKey::fromString('key1'); $key2 = CacheKey::fromString('key2'); $this->strategy->onCacheAccess($key1, true); $this->strategy->onCacheAccess($key2, true); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(2); $this->strategy->onCacheForget($key1); expect($this->strategy->getStats()['total_tracked_keys'])->toBe(1); }); }); describe('Heat Map Analysis', function () { it('identifies hot keys', function () { $hotKey = CacheKey::fromString('hot-key'); // Simulate frequent access (above hot threshold) for ($i = 0; $i < 25; $i++) { $this->strategy->onCacheAccess($hotKey, true, Duration::fromMilliseconds(10)); } $hotKeys = $this->strategy->getHotKeys(10); expect($hotKeys)->toHaveKey('hot-key'); expect($hotKeys['hot-key'])->toBeGreaterThan(10); }); it('provides heat map analysis', function () { $key = CacheKey::fromString('test-key'); for ($i = 0; $i < 5; $i++) { $this->strategy->onCacheAccess($key, true, Duration::fromMilliseconds(10)); } $analysis = $this->strategy->getHeatMapAnalysis(); expect($analysis)->toHaveKeys([ 'total_tracked_keys', 'hot_keys', 'cold_keys', 'performance_insights', 'analysis_window_hours' ]); expect($analysis['total_tracked_keys'])->toBe(1); }); it('tracks retrieval time statistics', function () { $key = CacheKey::fromString('timed-key'); $this->strategy->onCacheAccess($key, true, Duration::fromMilliseconds(150)); $this->strategy->onCacheAccess($key, true, Duration::fromMilliseconds(100)); $stats = $this->strategy->getStats(); expect($stats['total_tracked_keys'])->toBe(1); }); }); describe('Performance Bottlenecks', function () { it('identifies slow retrieval bottlenecks', function () { $slowKey = CacheKey::fromString('slow-key'); // Simulate slow retrieval times (>100ms) for ($i = 0; $i < 10; $i++) { $this->strategy->onCacheAccess($slowKey, true, Duration::fromMilliseconds(250)); } $bottlenecks = $this->strategy->getPerformanceBottlenecks(); // Should identify slow-key as a bottleneck expect($bottlenecks)->toBeArray(); if (!empty($bottlenecks)) { expect($bottlenecks[0]['key'])->toBe('slow-key'); expect($bottlenecks[0])->toHaveKeys([ 'key', 'impact_score', 'type', 'avg_retrieval_time_ms', 'hit_rate', 'access_count', 'recommendation' ]); } }); it('identifies low hit rate bottlenecks', function () { $missKey = CacheKey::fromString('miss-key'); // Simulate low hit rate (<50%) for ($i = 0; $i < 10; $i++) { $this->strategy->onCacheAccess($missKey, false, Duration::fromMilliseconds(50)); } $bottlenecks = $this->strategy->getPerformanceBottlenecks(); expect($bottlenecks)->toBeArray(); }); }); describe('Statistics and Insights', function () { it('provides comprehensive statistics', function () { $key = CacheKey::fromString('stats-key'); for ($i = 0; $i < 5; $i++) { $this->strategy->onCacheAccess($key, true, Duration::fromMilliseconds(30)); } $stats = $this->strategy->getStats(); expect($stats)->toHaveKeys([ 'strategy', 'enabled', 'total_tracked_keys', 'max_tracked_keys', 'thresholds', 'analysis', 'performance_bottlenecks' ]); }); it('tracks write operations', function () { $key = CacheKey::fromString('write-key'); $this->strategy->onCacheSet($key, 'some value', Duration::fromMinutes(5)); // Write operations are tracked but don't affect key count $stats = $this->strategy->getStats(); expect($stats['total_tracked_keys'])->toBe(0); // No access yet }); }); describe('Disabled Strategy', function () { it('does nothing when disabled', function () { $disabledStrategy = new HeatMapCacheStrategy(enabled: false); $key = CacheKey::fromString('disabled-key'); $disabledStrategy->onCacheAccess($key, true); $stats = $disabledStrategy->getStats(); expect($stats['enabled'])->toBeFalse(); expect($stats['total_tracked_keys'])->toBe(0); }); }); }); describe('PredictiveCacheStrategy', function () { beforeEach(function () { $this->strategy = new PredictiveCacheStrategy( enabled: true, predictionWindowHours: 24, confidenceThreshold: 0.7, maxConcurrentWarming: 5 ); }); describe('Basic Operations', function () { it('is enabled by default', function () { expect($this->strategy->isEnabled())->toBeTrue(); expect($this->strategy->getName())->toBe('predictive'); }); it('tracks cache access patterns', function () { $key = CacheKey::fromString('test-key'); $this->strategy->onCacheAccess($key, true); $this->strategy->onCacheAccess($key, true); $stats = $this->strategy->getStats(); expect($stats['strategy'])->toBe('PredictiveCacheStrategy'); expect($stats['total_patterns'])->toBe(1); }); it('does not modify TTL', function () { $key = CacheKey::fromString('test-key'); $originalTtl = Duration::fromMinutes(10); $resultTtl = $this->strategy->onCacheSet($key, 'value', $originalTtl); expect($resultTtl->toSeconds())->toBe($originalTtl->toSeconds()); }); it('clears pattern data', function () { $key = CacheKey::fromString('test-key'); $this->strategy->onCacheAccess($key, true); expect($this->strategy->getStats()['total_patterns'])->toBe(1); $this->strategy->clear(); expect($this->strategy->getStats()['total_patterns'])->toBe(0); }); it('forgets key-specific data', function () { $key1 = CacheKey::fromString('key1'); $key2 = CacheKey::fromString('key2'); $this->strategy->onCacheAccess($key1, true); $this->strategy->onCacheAccess($key2, true); expect($this->strategy->getStats()['total_patterns'])->toBe(2); $this->strategy->onCacheForget($key1); expect($this->strategy->getStats()['total_patterns'])->toBe(1); }); }); describe('Pattern Learning', function () { it('records access patterns with context', function () { $key = CacheKey::fromString('pattern-key'); $this->strategy->recordAccess($key, [ 'is_hit' => true, 'retrieval_time_ms' => 50 ]); $stats = $this->strategy->getStats(); expect($stats['total_patterns'])->toBe(1); }); it('tracks cache dependencies', function () { $primaryKey = CacheKey::fromString('primary-key'); $dependentKey = CacheKey::fromString('dependent-key'); $this->strategy->recordDependency($primaryKey, $dependentKey); $stats = $this->strategy->getStats(); expect($stats['total_patterns'])->toBe(1); }); it('generates predictions based on patterns', function () { $key = CacheKey::fromString('predictable-key'); // Record multiple accesses to establish pattern for ($i = 0; $i < 5; $i++) { $this->strategy->onCacheAccess($key, true); } $predictions = $this->strategy->generatePredictions(); expect($predictions)->toBeArray(); }); }); describe('Warming Callbacks', function () { it('registers warming callback for key', function () { $key = CacheKey::fromString('warming-key'); $callbackExecuted = false; $callback = function () use (&$callbackExecuted) { $callbackExecuted = true; return 'warmed value'; }; $this->strategy->registerWarmingCallback($key, $callback); $stats = $this->strategy->getStats(); expect($stats['total_patterns'])->toBe(1); }); it('performs predictive warming with callback', function () { $key = CacheKey::fromString('warm-key'); $callbackExecuted = false; $callback = function () use (&$callbackExecuted) { $callbackExecuted = true; return 'warmed value'; }; $this->strategy->registerWarmingCallback($key, $callback); // Record accesses to build confidence for ($i = 0; $i < 10; $i++) { $this->strategy->onCacheAccess($key, true); } $results = $this->strategy->performPredictiveWarming(); expect($results)->toBeArray(); }); }); describe('Statistics and Insights', function () { it('provides comprehensive statistics', function () { $key = CacheKey::fromString('stats-key'); for ($i = 0; $i < 3; $i++) { $this->strategy->onCacheAccess($key, true); } $stats = $this->strategy->getStats(); expect($stats)->toHaveKeys([ 'strategy', 'enabled', 'total_patterns', 'active_warming_jobs', 'completed_warming_operations', 'successful_warming_operations', 'warming_success_rate', 'avg_warming_time_ms', 'confidence_threshold', 'prediction_window_hours' ]); }); it('tracks warming success rate', function () { $stats = $this->strategy->getStats(); expect($stats['warming_success_rate'])->toBeFloat(); expect($stats['avg_warming_time_ms'])->toBeFloat(); }); }); describe('Disabled Strategy', function () { it('does nothing when disabled', function () { $disabledStrategy = new PredictiveCacheStrategy(enabled: false); $key = CacheKey::fromString('disabled-key'); $disabledStrategy->onCacheAccess($key, true); $stats = $disabledStrategy->getStats(); expect($stats['enabled'])->toBeFalse(); expect($stats['total_patterns'])->toBe(0); }); }); });