*/ public array $store = []; public function reset(): void { $this->store = []; } public function get(string $key): ?string { return $this->store[$key] ?? null; } public function setEx(string $key, string $value, int $ttl): void { $this->store[$key] = $value; } public function clear_keys(string $pattern): void { $regex = '/^' . str_replace('\*', '.*', preg_quote($pattern, '/')) . '$/'; foreach (array_keys($this->store) as $key) { if (preg_match($regex, $key) === 1) { unset($this->store[$key]); } } } } } beforeEach(function (): void { $this->oldTtl = getenv('ORDER_BOOKINGS_LIST_CACHE_TTL'); $this->redis = new OrderBookingsListCacheRedisFake(); order_bookings_list_cache::setAdapterForTests($this->redis); }); afterEach(function (): void { order_bookings_list_cache::setAdapterForTests(null); if ($this->oldTtl === false) { putenv('ORDER_BOOKINGS_LIST_CACHE_TTL'); return; } putenv('ORDER_BOOKINGS_LIST_CACHE_TTL=' . $this->oldTtl); }); it('uses sane ttl defaults and clamps negative ttl to zero', function (): void { putenv('ORDER_BOOKINGS_LIST_CACHE_TTL'); expect(order_bookings_list_cache::getTtl())->toBe(30); putenv('ORDER_BOOKINGS_LIST_CACHE_TTL=45'); expect(order_bookings_list_cache::getTtl())->toBe(45); putenv('ORDER_BOOKINGS_LIST_CACHE_TTL=-10'); expect(order_bookings_list_cache::getTtl())->toBe(0); }); it('builds deterministic keys for equivalent contexts', function (): void { $keyA = order_bookings_list_cache::buildKey([ 'route' => '/order-bookings', 'page' => 1, 'limit' => 100, 'search' => '', 'order' => ['datetime' => 'DESC'], 'filters' => [ 'customer_number' => '42', 'department' => ['3', '1'], 'order_id' => null, ], ]); $keyB = order_bookings_list_cache::buildKey([ 'search' => '', 'route' => '/order-bookings', 'limit' => 100, 'filters' => [ 'order_id' => null, 'department' => [1, 3], 'customer_number' => 42, ], 'order' => ['datetime' => 'DESC'], 'page' => 1, ]); expect($keyA)->toBe($keyB); expect($keyA)->toStartWith(order_bookings_list_cache::PREFIX); }); it('stores and retrieves full order-bookings payloads', function (): void { $key = order_bookings_list_cache::buildKey([ 'route' => '/order-bookings', 'page' => 1, 'limit' => 100, ]); $payload = [ 'success' => true, 'data' => [ ['id' => 123], ], 'meta' => [ 'pagination' => [ 'page' => 1, 'per_page' => 100, 'total' => 1, ], ], 'includes' => [], ]; order_bookings_list_cache::storePayload($key, $payload, 60); expect(order_bookings_list_cache::getPayload($key))->toBe($payload); }); it('ignores malformed cached payloads and clears order-bookings list caches', function (): void { $validKey = order_bookings_list_cache::buildKey([ 'route' => '/order-bookings', 'page' => 1, ]); $this->redis->store[$validKey] = '{"success":true,"data":[]}'; $this->redis->store[order_bookings_list_cache::PREFIX . 'broken'] = '{"not":"a payload"}'; $this->redis->store['other:cache:key'] = '{"leave":"me"}'; expect(order_bookings_list_cache::getPayload(order_bookings_list_cache::PREFIX . 'broken'))->toBeNull(); order_bookings_list_cache::clearAll(); expect($this->redis->store)->toHaveKey('other:cache:key'); expect($this->redis->store)->not->toHaveKey($validKey); expect($this->redis->store)->not->toHaveKey(order_bookings_list_cache::PREFIX . 'broken'); });