Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
161 / 161
n/a
0 / 0
CRAP
n/a
0 / 0
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10pest()->group('unit');
11
12use Phexium\Plugin\Cache\Adapter\InMemoryCache;
13use Phexium\Plugin\Clock\Adapter\FrozenClock;
14use Phexium\Plugin\Clock\Adapter\OffsetClock;
15
16beforeEach(function (): void {
17    $this->clock = new OffsetClock(new FrozenClock('2025-01-01T12:00:00+00:00'));
18    $this->cache = new InMemoryCache($this->clock);
19});
20
21describe('Basic operations', function (): void {
22    it('returns null for non-existent key', function (): void {
23        expect($this->cache->get('non-existent'))->toBeNull();
24    });
25
26    it('returns default for non-existent key', function (): void {
27        expect($this->cache->get('non-existent', 'default'))->toBe('default');
28    });
29
30    it('stores and retrieves value', function (): void {
31        $this->cache->set('key', 'value');
32
33        expect($this->cache->get('key'))->toBe('value');
34    });
35
36    it('returns true on successful set()', function (): void {
37        expect($this->cache->set('key', 'value'))->toBeTrue();
38    });
39
40    it('overwrites existing value', function (): void {
41        $this->cache->set('key', 'first');
42        $this->cache->set('key', 'second');
43
44        expect($this->cache->get('key'))->toBe('second');
45    });
46
47    it('returns false for non-existent key on has()', function (): void {
48        expect($this->cache->has('non-existent'))->toBeFalse();
49    });
50
51    it('returns true for existing key on has()', function (): void {
52        $this->cache->set('key', 'value');
53
54        expect($this->cache->has('key'))->toBeTrue();
55    });
56
57    it('removes existing key on delete()', function (): void {
58        $this->cache->set('key', 'value');
59
60        expect($this->cache->delete('key'))->toBeTrue();
61        expect($this->cache->has('key'))->toBeFalse();
62    });
63
64    it('returns false when deleting non-existent key', function (): void {
65        expect($this->cache->delete('non-existent'))->toBeFalse();
66    });
67
68    it('removes all keys on clear()', function (): void {
69        $this->cache->set('key1', 'value1');
70        $this->cache->set('key2', 'value2');
71
72        $this->cache->clear();
73
74        expect($this->cache->has('key1'))->toBeFalse();
75        expect($this->cache->has('key2'))->toBeFalse();
76    });
77
78    it('returns true on clear()', function (): void {
79        expect($this->cache->clear())->toBeTrue();
80    });
81});
82
83describe('TTL and expiration', function (): void {
84    it('does not store value with TTL zero', function (): void {
85        expect($this->cache->set('key', 'value', 0))->toBeTrue();
86        expect($this->cache->delete('key'))->toBeFalse();
87    });
88
89    it('stores value with TTL 1', function (): void {
90        $this->cache->set('key', 'value', 1);
91
92        expect($this->cache->has('key'))->toBeTrue();
93    });
94
95    it('deletes existing entry with negative TTL', function (): void {
96        $this->cache->set('key', 'value');
97        expect($this->cache->has('key'))->toBeTrue();
98
99        $this->cache->set('key', 'new-value', -1);
100
101        expect($this->cache->has('key'))->toBeFalse();
102    });
103
104    it('supports DateInterval TTL', function (): void {
105        $this->cache->set('key', 'value', new DateInterval('PT60S'));
106
107        expect($this->cache->has('key'))->toBeTrue();
108        expect($this->cache->get('key'))->toBe('value');
109
110        $this->clock->advanceSeconds(59);
111        expect($this->cache->has('key'))->toBeTrue();
112
113        $this->clock->advanceSeconds(1);
114        expect($this->cache->has('key'))->toBeFalse();
115    });
116
117    it('expires value after TTL seconds', function (): void {
118        $this->cache->set('key', 'value', 60);
119
120        expect($this->cache->has('key'))->toBeTrue();
121        expect($this->cache->get('key'))->toBe('value');
122
123        $this->clock->advanceSeconds(59);
124        expect($this->cache->has('key'))->toBeTrue();
125        expect($this->cache->get('key'))->toBe('value');
126
127        $this->clock->advanceSeconds(1);
128        expect($this->cache->has('key'))->toBeFalse();
129        expect($this->cache->get('key'))->toBeNull();
130    });
131
132    it('never expires value without TTL', function (): void {
133        $this->cache->set('key', 'value');
134
135        expect($this->cache->has('key'))->toBeTrue();
136        expect($this->cache->get('key'))->toBe('value');
137
138        $this->clock->advanceSeconds(10 * 365 * 24 * 60 * 60);
139
140        expect($this->cache->has('key'))->toBeTrue();
141        expect($this->cache->get('key'))->toBe('value');
142    });
143
144    it('lazily removes expired items on access', function (): void {
145        $this->cache->set('key', 'value', 60);
146
147        $this->clock->advanceSeconds(61);
148
149        expect($this->cache->has('key'))->toBeFalse();
150        expect($this->cache->delete('key'))->toBeFalse();
151    });
152});
153
154describe('Special values', function (): void {
155    it('stores object value', function (): void {
156        $object = new stdClass();
157        $this->cache->set('object', $object);
158
159        expect($this->cache->get('object'))->toBe($object);
160    });
161
162    it('stores array value', function (): void {
163        $data = ['foo' => 'bar', 'nested' => ['a' => 1]];
164        $this->cache->set('array', $data);
165
166        expect($this->cache->get('array'))->toBe($data);
167    });
168
169    it('stores null as valid value', function (): void {
170        $this->cache->set('null-key', null);
171
172        expect($this->cache->has('null-key'))->toBeTrue();
173        expect($this->cache->get('null-key'))->toBeNull();
174    });
175
176    it('stores false as valid value', function (): void {
177        $this->cache->set('false-key', false);
178
179        expect($this->cache->has('false-key'))->toBeTrue();
180        expect($this->cache->get('false-key'))->toBeFalse();
181    });
182
183    it('stores empty string as valid value', function (): void {
184        $this->cache->set('empty-key', '');
185
186        expect($this->cache->has('empty-key'))->toBeTrue();
187        expect($this->cache->get('empty-key'))->toBe('');
188    });
189
190    it('stores zero as valid value', function (): void {
191        $this->cache->set('zero-key', 0);
192
193        expect($this->cache->has('zero-key'))->toBeTrue();
194        expect($this->cache->get('zero-key'))->toBe(0);
195    });
196});
197
198describe('Batch operations', function (): void {
199    it('returns values with defaults on getMultiple()', function (): void {
200        $this->cache->set('key1', 'value1');
201
202        $result = $this->cache->getMultiple(['key1', 'key2'], 'default');
203
204        expect($result)->toBe(['key1' => 'value1', 'key2' => 'default']);
205    });
206
207    it('stores all values on setMultiple()', function (): void {
208        $result = $this->cache->setMultiple(['key1' => 'value1', 'key2' => 'value2']);
209
210        expect($result)->toBeTrue();
211        expect($this->cache->get('key1'))->toBe('value1');
212        expect($this->cache->get('key2'))->toBe('value2');
213    });
214
215    it('applies TTL on setMultiple()', function (): void {
216        $this->cache->setMultiple(['key1' => 'value1', 'key2' => 'value2'], 60);
217
218        $this->clock->advanceSeconds(61);
219
220        expect($this->cache->has('key1'))->toBeFalse();
221        expect($this->cache->has('key2'))->toBeFalse();
222    });
223
224    it('removes specified keys on deleteMultiple()', function (): void {
225        $this->cache->set('key1', 'value1');
226        $this->cache->set('key2', 'value2');
227        $this->cache->set('key3', 'value3');
228
229        $result = $this->cache->deleteMultiple(['key1', 'key2']);
230
231        expect($result)->toBeTrue();
232        expect($this->cache->has('key1'))->toBeFalse();
233        expect($this->cache->has('key2'))->toBeFalse();
234        expect($this->cache->has('key3'))->toBeTrue();
235    });
236});