Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
169 / 169
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('integration');
11
12use Phexium\Plugin\Cache\Adapter\RedisCache;
13
14beforeEach(function (): void {
15    $this->redis = new Redis();
16    $this->redis->connect('redis', 6379);
17    $this->cache = new RedisCache($this->redis, [stdClass::class]);
18});
19
20afterEach(function (): void {
21    $this->redis->flushDB();
22    $this->redis->close();
23});
24
25describe('Basic operations', function (): void {
26    it('returns null for non-existent key', function (): void {
27        expect($this->cache->get('non-existent'))->toBeNull();
28    });
29
30    it('returns default for non-existent key', function (): void {
31        expect($this->cache->get('non-existent', 'default'))->toBe('default');
32    });
33
34    it('stores and retrieves value', function (): void {
35        $this->cache->set('key', 'value');
36
37        expect($this->cache->get('key'))->toBe('value');
38    });
39
40    it('returns true on successful set()', function (): void {
41        expect($this->cache->set('key', 'value'))->toBeTrue();
42    });
43
44    it('overwrites existing value', function (): void {
45        $this->cache->set('key', 'first');
46        $this->cache->set('key', 'second');
47
48        expect($this->cache->get('key'))->toBe('second');
49    });
50
51    it('returns false for non-existent key on has()', function (): void {
52        expect($this->cache->has('non-existent'))->toBeFalse();
53    });
54
55    it('returns true for existing key on has()', function (): void {
56        $this->cache->set('key', 'value');
57
58        expect($this->cache->has('key'))->toBeTrue();
59    });
60
61    it('removes existing key on delete()', function (): void {
62        $this->cache->set('key', 'value');
63
64        expect($this->cache->delete('key'))->toBeTrue();
65        expect($this->cache->has('key'))->toBeFalse();
66    });
67
68    it('returns false when deleting non-existent key', function (): void {
69        expect($this->cache->delete('non-existent'))->toBeFalse();
70    });
71
72    it('removes all keys on clear()', function (): void {
73        $this->cache->set('key1', 'value1');
74        $this->cache->set('key2', 'value2');
75
76        $this->cache->clear();
77
78        expect($this->cache->has('key1'))->toBeFalse();
79        expect($this->cache->has('key2'))->toBeFalse();
80    });
81
82    it('returns true on clear()', function (): void {
83        expect($this->cache->clear())->toBeTrue();
84    });
85});
86
87describe('TTL and expiration', function (): void {
88    it('deletes existing entry with TTL zero', function (): void {
89        $this->cache->set('key', 'value');
90        expect($this->cache->has('key'))->toBeTrue();
91
92        expect($this->cache->set('key', 'value', 0))->toBeTrue();
93
94        expect($this->cache->has('key'))->toBeFalse();
95    });
96
97    it('deletes existing entry with negative TTL', function (): void {
98        $this->cache->set('key', 'value');
99        expect($this->cache->has('key'))->toBeTrue();
100
101        $this->cache->set('key', 'new-value', -1);
102
103        expect($this->cache->has('key'))->toBeFalse();
104    });
105
106    it('never expires value without TTL', function (): void {
107        $this->cache->set('key', 'value');
108
109        expect($this->cache->has('key'))->toBeTrue();
110        expect($this->cache->get('key'))->toBe('value');
111
112        expect($this->redis->ttl('key'))->toBe(-1);
113    });
114
115    it('converts integer TTL from seconds to milliseconds', function (): void {
116        $this->cache->set('key', 'value', 3600);
117
118        $pttl = $this->redis->pttl('key');
119
120        expect($pttl)->toBeGreaterThan(3_599_000)
121            ->toBeLessThanOrEqual(3_600_000)
122        ;
123    });
124
125    it('converts DateInterval TTL to milliseconds', function (): void {
126        $this->cache->set('key', 'value', new DateInterval('PT1H'));
127
128        $pttl = $this->redis->pttl('key');
129
130        expect($pttl)->toBeGreaterThan(3_599_000)
131            ->toBeLessThanOrEqual(3_600_000)
132        ;
133    });
134
135    it('stores value with 1ms TTL instead of deleting', function (): void {
136        $ttl = new DateInterval('PT0S');
137        $ttl->f = 0.001; // 1ms
138
139        $this->cache->set('key', 'value', $ttl);
140
141        expect($this->redis->pttl('key'))->toBeGreaterThanOrEqual(0);
142    });
143
144    it('stores value with TTL 100ms', function (): void {
145        $ttl = new DateInterval('PT0S');
146        $ttl->f = 0.01; // 0.01 sec
147
148        $this->cache->set('key', 'value', $ttl);
149
150        expect($this->cache->has('key'))->toBeTrue();
151
152        usleep(12_000); // 0.012 sec
153
154        expect($this->cache->has('key'))->toBeFalse();
155    });
156});
157
158describe('Special values', function (): void {
159    it('stores object value', function (): void {
160        $object = new stdClass();
161        $object->name = 'test';
162        $this->cache->set('object', $object);
163
164        $retrieved = $this->cache->get('object');
165        expect($retrieved)->toBeInstanceOf(stdClass::class);
166        expect($retrieved->name)->toBe('test');
167    });
168
169    it('stores array value', function (): void {
170        $data = ['foo' => 'bar', 'nested' => ['a' => 1]];
171        $this->cache->set('array', $data);
172
173        expect($this->cache->get('array'))->toBe($data);
174    });
175
176    it('stores null as valid value', function (): void {
177        $this->cache->set('null-key', null);
178
179        expect($this->cache->has('null-key'))->toBeTrue();
180        expect($this->cache->get('null-key'))->toBeNull();
181    });
182
183    it('stores false as valid value', function (): void {
184        $this->cache->set('false-key', false);
185
186        expect($this->cache->has('false-key'))->toBeTrue();
187        expect($this->cache->get('false-key'))->toBeFalse();
188    });
189
190    it('stores empty string as valid value', function (): void {
191        $this->cache->set('empty-key', '');
192
193        expect($this->cache->has('empty-key'))->toBeTrue();
194        expect($this->cache->get('empty-key'))->toBe('');
195    });
196
197    it('stores zero as valid value', function (): void {
198        $this->cache->set('zero-key', 0);
199
200        expect($this->cache->has('zero-key'))->toBeTrue();
201        expect($this->cache->get('zero-key'))->toBe(0);
202    });
203
204    it('rejects deserialization of non-allowed classes', function (): void {
205        // Store object using a cache that allows DateTime
206        $cacheWithDateTime = new RedisCache($this->redis, [DateTime::class]);
207        $cacheWithDateTime->set('datetime', new DateTime('2026-01-27'));
208
209        // Retrieve with a cache that does not allow DateTime
210        $restrictedCache = new RedisCache($this->redis, []);
211        $retrieved = $restrictedCache->get('datetime');
212
213        // Should return __PHP_Incomplete_Class, not DateTime
214        expect($retrieved)->not->toBeInstanceOf(DateTime::class);
215        expect($retrieved::class)->toBe('__PHP_Incomplete_Class');
216    });
217});
218
219describe('Batch operations', function (): void {
220    it('returns values with defaults on getMultiple()', function (): void {
221        $this->cache->set('key1', 'value1');
222
223        $result = $this->cache->getMultiple(['key1', 'key2'], 'default');
224
225        expect($result)->toBe(['key1' => 'value1', 'key2' => 'default']);
226    });
227
228    it('stores all values on setMultiple()', function (): void {
229        $result = $this->cache->setMultiple(['key1' => 'value1', 'key2' => 'value2']);
230
231        expect($result)->toBeTrue();
232        expect($this->cache->get('key1'))->toBe('value1');
233        expect($this->cache->get('key2'))->toBe('value2');
234    });
235
236    it('removes specified keys on deleteMultiple()', function (): void {
237        $this->cache->set('key1', 'value1');
238        $this->cache->set('key2', 'value2');
239        $this->cache->set('key3', 'value3');
240
241        $result = $this->cache->deleteMultiple(['key1', 'key2']);
242
243        expect($result)->toBeTrue();
244        expect($this->cache->has('key1'))->toBeFalse();
245        expect($this->cache->has('key2'))->toBeFalse();
246        expect($this->cache->has('key3'))->toBeTrue();
247    });
248});