['a', 'b'], 'k2' => 42, 'k3' => 'value']; $writer = new \Cacti\Cache\SharedCache('sc_roundtrip', ['cache_dir' => $dir, 'backends' => ['file']]); expect($writer->store($data, 'token-1'))->toBeTrue(); expect($writer->isShared())->toBeTrue(); // a fresh instance has no in-process memo, so it must read from disk $reader = new \Cacti\Cache\SharedCache('sc_roundtrip', ['cache_dir' => $dir, 'backends' => ['file']]); expect($reader->fetch())->toEqual($data); expect($reader->checksum())->toBe('token-1'); } finally { sharedCacheRmTree($dir); } }); test('invalidate removes the stored entry from the file tier', function () { $dir = sharedCacheTmpDir(); try { $writer = new \Cacti\Cache\SharedCache('sc_invalidate', ['cache_dir' => $dir, 'backends' => ['file']]); $writer->store(['v' => 1], 'token'); expect(is_file($dir . '/sc_invalidate.cache'))->toBeTrue(); $writer->invalidate(); expect(is_file($dir . '/sc_invalidate.cache'))->toBeFalse(); $reader = new \Cacti\Cache\SharedCache('sc_invalidate', ['cache_dir' => $dir, 'backends' => ['file']]); expect($reader->fetch())->toBeFalse(); } finally { sharedCacheRmTree($dir); } }); test('a corrupt payload is treated as a miss rather than crashing', function () { $dir = sharedCacheTmpDir(); try { // valid JSON envelope, but the payload is not a serialized value file_put_contents($dir . '/sc_corrupt.cache', json_encode([ 'checksum' => 'token', 'payload' => base64_encode('this-is-not-serialized'), ])); $cache = new \Cacti\Cache\SharedCache('sc_corrupt', ['cache_dir' => $dir, 'backends' => ['file']]); expect($cache->fetch())->toBeFalse(); } finally { sharedCacheRmTree($dir); } }); test('malformed JSON in the cache file is treated as a miss', function () { $dir = sharedCacheTmpDir(); try { file_put_contents($dir . '/sc_badjson.cache', 'not-valid-json{'); $cache = new \Cacti\Cache\SharedCache('sc_badjson', ['cache_dir' => $dir, 'backends' => ['file']]); expect($cache->fetch())->toBeFalse(); expect($cache->checksum())->toBeNull(); } finally { sharedCacheRmTree($dir); } }); test('a non-string checksum field does not raise a TypeError', function () { $dir = sharedCacheTmpDir(); try { /* a corrupt file could decode to an array-valued checksum; readChecksum() * must treat that as absent instead of returning a non-string */ file_put_contents($dir . '/sc_badsum.cache', json_encode([ 'checksum' => ['unexpected', 'array'], 'payload' => base64_encode(serialize(['x' => 1])), ])); $cache = new \Cacti\Cache\SharedCache('sc_badsum', ['cache_dir' => $dir, 'backends' => ['file']]); expect($cache->checksum())->toBeNull(); } finally { sharedCacheRmTree($dir); } }); test('a failed higher-tier write falls through without serving stale data', function () { $dir = sharedCacheTmpDir(); try { /* occupy the cache path with a directory so the file tier's atomic * rename() always fails - deterministic even when the suite runs as * root, where permission bits would be ignored */ mkdir($dir . '/sc_fallback.cache', 0770, true); $writer = new \Cacti\Cache\SharedCache('sc_fallback', ['cache_dir' => $dir, 'backends' => ['file', 'static']]); // no cross-process tier accepted the write expect($writer->store(['fresh' => true], 'new'))->toBeFalse(); /* a fresh consumer still gets the new value from the process-local * fallback tier and never the unreadable stale directory entry */ $reader = new \Cacti\Cache\SharedCache('sc_fallback', ['cache_dir' => $dir, 'backends' => ['file', 'static']]); expect($reader->fetch())->toEqual(['fresh' => true]); } finally { @rmdir($dir . '/sc_fallback.cache'); sharedCacheRmTree($dir); } });