File: /home/xedaptot/ai.naniguide.com/tests/Unit/Cache/AppCacheTest.php
<?php
use App\Library\Cache\AppCache;
use App\Library\Cache\Cacheable;
use App\Library\Cache\CacheScope;
use Illuminate\Support\Facades\Cache;
uses(Tests\TestCase::class);
beforeEach(function () {
Cache::flush();
AppCache::reset();
});
function fakeCacheable(string $key, array $manifest = []): Cacheable
{
return new class($key, $manifest) implements Cacheable {
public function __construct(private string $key, private array $manifest) {}
public function cacheKey(): string { return $this->key; }
public function cacheManifest(): array { return $this->manifest; }
};
}
test('for() returns a CacheScope', function () {
$subject = fakeCacheable('model:Foo:1');
expect(AppCache::for($subject))->toBeInstanceOf(CacheScope::class);
});
test('for() memoizes per scope string', function () {
$a = fakeCacheable('model:Foo:1');
$b = fakeCacheable('model:Foo:1');
$h1 = AppCache::for($a);
$h2 = AppCache::for($b);
expect($h1)->toBe($h2);
});
test('for() lazy-loads manifest into CacheScope', function () {
$calls = 0;
$subject = fakeCacheable('model:Bar:1', [
'Count' => function () use (&$calls) { $calls++; return 42; },
]);
$handle = AppCache::for($subject);
$handle->refresh();
expect($calls)->toBe(1);
expect($handle->read('Count'))->toBe(42);
});
test('for() parses manifest array shape with ttl', function () {
$subject = fakeCacheable('model:TTL:1', [
'Short' => ['compute' => fn () => 'v', 'ttl' => 60],
]);
AppCache::for($subject)->refresh();
expect(AppCache::for($subject)->read('Short'))->toBe('v');
$this->travel(61)->seconds();
expect(AppCache::for($subject)->read('Short', 'fallback'))->toBe('fallback');
});
test('for() rejects malformed manifest entry', function () {
$subject = fakeCacheable('model:Bad:1', [
'Bad' => 'not-a-closure',
]);
expect(fn () => AppCache::for($subject))
->toThrow(InvalidArgumentException::class);
});
test('for() does not re-register entries on second call', function () {
$calls = 0;
$subject = fakeCacheable('model:Baz:1', [
'A' => function () use (&$calls) { $calls++; return 1; },
]);
AppCache::for($subject);
AppCache::for($subject);
AppCache::for($subject)->refresh();
expect($calls)->toBe(1);
});
test('for() rejects unknown entry access', function () {
$subject = fakeCacheable('model:Strict:1', [
'Known' => fn () => 'v',
]);
$handle = AppCache::for($subject);
expect(fn () => $handle->read('Unknown'))
->toThrow(InvalidArgumentException::class, "Unknown cache entry 'Unknown'");
});
test('reset() clears process-level memoization', function () {
$subject = fakeCacheable('reset.ns');
$h1 = AppCache::for($subject);
AppCache::reset();
$h2 = AppCache::for($subject);
expect($h1)->not->toBe($h2);
});