innerCache = new GeneralCache($driver, $serializer, $compression); $this->cache = new SmartCache($this->innerCache); }); describe('Basic Cache Operations', function () { it('sets and gets a single cache item', function () { $key = CacheKey::fromString('test-key'); $value = 'test-value'; $item = CacheItem::forSet($key, $value, Duration::fromMinutes(5)); $this->cache->set($item); $result = $this->cache->get($key); expect($result->isHit)->toBeTrue(); expect($result->value)->toBe('test-value'); expect($result->getItem($key)->isHit)->toBeTrue(); }); it('returns miss for non-existent key', function () { $key = CacheKey::fromString('non-existent'); $result = $this->cache->get($key); expect($result->isHit)->toBeFalse(); expect($result->value)->toBeNull(); }); it('sets multiple cache items in batch', function () { $item1 = CacheItem::forSet( CacheKey::fromString('key1'), 'value1', Duration::fromMinutes(5) ); $item2 = CacheItem::forSet( CacheKey::fromString('key2'), 'value2', Duration::fromMinutes(5) ); $success = $this->cache->set($item1, $item2); expect($success)->toBeTrue(); $result = $this->cache->get( CacheKey::fromString('key1'), CacheKey::fromString('key2') ); expect($result->count())->toBe(2); expect($result->isCompleteHit())->toBeTrue(); }); it('checks if cache keys exist', function () { $key1 = CacheKey::fromString('exists'); $key2 = CacheKey::fromString('not-exists'); $this->cache->set(CacheItem::forSet($key1, 'value')); $results = $this->cache->has($key1, $key2); expect($results['exists'])->toBeTrue(); expect($results['not-exists'])->toBeFalse(); }); it('forgets cache items', function () { $key = CacheKey::fromString('to-forget'); $this->cache->set(CacheItem::forSet($key, 'value')); expect($this->cache->get($key)->isHit)->toBeTrue(); $this->cache->forget($key); expect($this->cache->get($key)->isHit)->toBeFalse(); }); it('clears all cache', function () { $this->cache->set( CacheItem::forSet(CacheKey::fromString('key1'), 'value1'), CacheItem::forSet(CacheKey::fromString('key2'), 'value2') ); $success = $this->cache->clear(); expect($success)->toBeTrue(); expect($this->cache->get(CacheKey::fromString('key1'))->isHit)->toBeFalse(); expect($this->cache->get(CacheKey::fromString('key2'))->isHit)->toBeFalse(); }); }); describe('Remember Pattern', function () { it('executes callback on cache miss', function () { $key = CacheKey::fromString('remember-key'); $callbackExecuted = false; $callback = function () use (&$callbackExecuted) { $callbackExecuted = true; return 'computed-value'; }; $result = $this->cache->remember($key, $callback, Duration::fromMinutes(5)); expect($callbackExecuted)->toBeTrue(); expect($result->value)->toBe('computed-value'); }); it('returns cached value without executing callback on hit', function () { $key = CacheKey::fromString('cached-key'); // Set value first $this->cache->set(CacheItem::forSet($key, 'cached-value')); $callbackExecuted = false; $callback = function () use (&$callbackExecuted) { $callbackExecuted = true; return 'should-not-execute'; }; $result = $this->cache->remember($key, $callback); expect($callbackExecuted)->toBeFalse(); expect($result->value)->toBe('cached-value'); }); }); describe('Prefix Operations', function () { it('gets items by prefix', function () { $this->cache->set( CacheItem::forSet(CacheKey::fromString('user:1'), 'User 1'), CacheItem::forSet(CacheKey::fromString('user:2'), 'User 2'), CacheItem::forSet(CacheKey::fromString('post:1'), 'Post 1') ); // Verify driver supports scanning $driver = $this->cache->getDriver(); expect($driver)->toBeInstanceOf(\App\Framework\Cache\Contracts\Scannable::class); $prefix = CachePrefix::fromString('user:'); $result = $this->cache->get($prefix); // SmartCache uses scanPrefix on the underlying driver expect($result->count())->toBe(2); expect($result->getValue(CacheKey::fromString('user:1')))->toBe('User 1'); expect($result->getValue(CacheKey::fromString('user:2')))->toBe('User 2'); }); it('checks existence by prefix', function () { $this->cache->set( CacheItem::forSet(CacheKey::fromString('api:v1:users'), 'data'), CacheItem::forSet(CacheKey::fromString('api:v1:posts'), 'data') ); $prefix = CachePrefix::fromString('api:v1:'); $results = $this->cache->has($prefix); expect($results)->toHaveKey('api:v1:users'); expect($results)->toHaveKey('api:v1:posts'); expect($results['api:v1:users'])->toBeTrue(); }); it('forgets items by prefix', function () { $this->cache->set( CacheItem::forSet(CacheKey::fromString('temp:1'), 'data1'), CacheItem::forSet(CacheKey::fromString('temp:2'), 'data2'), CacheItem::forSet(CacheKey::fromString('perm:1'), 'data3') ); $prefix = CachePrefix::fromString('temp:'); $this->cache->forget($prefix); expect($this->cache->get(CacheKey::fromString('temp:1'))->isHit)->toBeFalse(); expect($this->cache->get(CacheKey::fromString('temp:2'))->isHit)->toBeFalse(); expect($this->cache->get(CacheKey::fromString('perm:1'))->isHit)->toBeTrue(); }); }); describe('Value Objects', function () { it('caches arrays correctly', function () { $key = CacheKey::fromNamespace('user', '123'); $value = ['id' => 123, 'name' => 'Test User', 'active' => true]; $this->cache->set(CacheItem::forSet($key, $value)); $result = $this->cache->get($key); expect($result->isHit)->toBeTrue(); expect($result->value)->toBeArray(); expect($result->value['id'])->toBe(123); expect($result->value['name'])->toBe('Test User'); }); it('handles complex nested arrays', function () { $key = CacheKey::forClass('TestClass', 'method'); $value = [ 'id' => 1, 'name' => 'Test', 'metadata' => [ 'created' => '2024-01-01', 'updated' => '2024-01-02', 'tags' => ['php', 'cache', 'testing'] ] ]; $this->cache->set(CacheItem::forSet($key, $value)); $result = $this->cache->get($key); expect($result->isHit)->toBeTrue(); expect($result->value['id'])->toBe(1); expect($result->value['metadata'])->toBeArray(); expect($result->value['metadata']['tags'])->toHaveCount(3); }); }); describe('Stats and Monitoring', function () { it('provides cache statistics', function () { $stats = $this->cache->getStats(); expect($stats)->toBeArray(); expect($stats)->toHaveKey('cache_type'); expect($stats['cache_type'])->toBe('SmartCache'); }); it('reports async capabilities', function () { $stats = $this->cache->getStats(); expect($stats)->toHaveKey('async_enabled'); expect($stats)->toHaveKey('async_available'); expect($stats)->toHaveKey('async_threshold'); }); it('reports pattern and prefix support', function () { $stats = $this->cache->getStats(); expect($stats)->toHaveKey('pattern_support'); expect($stats)->toHaveKey('prefix_support'); expect($stats['pattern_support'])->toBeTrue(); expect($stats['prefix_support'])->toBeTrue(); }); }); describe('Edge Cases', function () { it('handles empty operations gracefully', function () { expect($this->cache->set())->toBeTrue(); expect($this->cache->forget())->toBeTrue(); expect($this->cache->has())->toBe([]); expect($this->cache->get())->toBeInstanceOf(CacheResult::class); }); it('handles null values correctly', function () { $key = CacheKey::fromString('null-value'); $this->cache->set(CacheItem::forSet($key, null)); $result = $this->cache->get($key); expect($result->isHit)->toBeTrue(); expect($result->value)->toBeNull(); }); it('handles false values correctly', function () { $key = CacheKey::fromString('false-value'); $this->cache->set(CacheItem::forSet($key, false)); $result = $this->cache->get($key); expect($result->isHit)->toBeTrue(); expect($result->value)->toBeFalse(); }); it('handles empty string values', function () { $key = CacheKey::fromString('empty-string'); $this->cache->set(CacheItem::forSet($key, '')); $result = $this->cache->get($key); expect($result->isHit)->toBeTrue(); // Empty strings may be returned as null after serialization/deserialization expect($result->value === '' || $result->value === null)->toBeTrue(); }); }); describe('CacheResult Features', function () { it('provides hit and miss separation', function () { $this->cache->set(CacheItem::forSet(CacheKey::fromString('exists'), 'value')); $result = $this->cache->get( CacheKey::fromString('exists'), CacheKey::fromString('not-exists') ); expect($result->count())->toBe(2); expect($result->getHits()->count())->toBe(1); expect($result->getMisses()->count())->toBe(1); }); it('calculates hit ratio', function () { $this->cache->set( CacheItem::forSet(CacheKey::fromString('hit1'), 'value1'), CacheItem::forSet(CacheKey::fromString('hit2'), 'value2') ); $result = $this->cache->get( CacheKey::fromString('hit1'), CacheKey::fromString('hit2'), CacheKey::fromString('miss1'), CacheKey::fromString('miss2') ); expect($result->getHitRatio())->toBe(0.5); }); it('checks for complete hits', function () { $this->cache->set( CacheItem::forSet(CacheKey::fromString('key1'), 'value1'), CacheItem::forSet(CacheKey::fromString('key2'), 'value2') ); $result = $this->cache->get( CacheKey::fromString('key1'), CacheKey::fromString('key2') ); expect($result->isCompleteHit())->toBeTrue(); expect($result->isCompleteMiss())->toBeFalse(); }); it('checks for complete misses', function () { $result = $this->cache->get( CacheKey::fromString('miss1'), CacheKey::fromString('miss2') ); expect($result->isCompleteMiss())->toBeTrue(); expect($result->isCompleteHit())->toBeFalse(); }); }); describe('Wrapped Cache Access', function () { it('provides access to wrapped cache', function () { $wrapped = $this->cache->getWrappedCache(); expect($wrapped)->toBeInstanceOf(GeneralCache::class); }); it('provides access to underlying driver', function () { $driver = $this->cache->getDriver(); expect($driver)->toBeInstanceOf(InMemoryCache::class); }); it('checks driver interface support', function () { $supportsDriver = $this->cache->driverSupports(\App\Framework\Cache\CacheDriver::class); expect($supportsDriver)->toBeTrue(); }); }); });