Skip to content

Commit 18f8237

Browse files
committed
Redis cache: improve test coverage
1 parent 99a09c0 commit 18f8237

File tree

2 files changed

+154
-4
lines changed

2 files changed

+154
-4
lines changed

tests/Cache/Redis/Concerns/MocksRedisConnections.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,18 @@ protected function createStore(
168168
* This eliminates the boilerplate of manually setting up RedisCluster mocks,
169169
* connection mocks, pool mocks, and pool factory mocks for each cluster test.
170170
*
171-
* Returns both the store and the cluster client mock so tests can set expectations:
171+
* Returns the store, cluster client mock, and connection mock so tests can set expectations:
172172
* ```php
173-
* [$store, $clusterClient] = $this->createClusterStore();
173+
* [$store, $clusterClient, $connection] = $this->createClusterStore();
174174
* $clusterClient->shouldNotReceive('pipeline');
175175
* $clusterClient->shouldReceive('zadd')->once()->andReturn(1);
176+
* $connection->shouldReceive('del')->once()->andReturn(1); // connection-level operations
176177
* ```
177178
*
178179
* @param string $prefix Cache key prefix
179180
* @param string $connectionName Redis connection name
180181
* @param string|null $tagMode Optional tag mode ('any' or 'all')
181-
* @return array{0: RedisStore, 1: m\MockInterface} [store, clusterClient]
182+
* @return array{0: RedisStore, 1: m\MockInterface, 2: m\MockInterface} [store, clusterClient, connection]
182183
*/
183184
protected function createClusterStore(
184185
string $prefix = 'prefix:',
@@ -199,6 +200,6 @@ protected function createClusterStore(
199200
$store->setTagMode($tagMode);
200201
}
201202

202-
return [$store, $clusterClient];
203+
return [$store, $clusterClient, $connection];
203204
}
204205
}

tests/Cache/Redis/Operations/AllTag/FlushTest.php

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,153 @@ public function testFlushTagKeyFormat(): void
249249

250250
$operation->execute(['_all:tag:my-special-tag:entries'], ['my-special-tag']);
251251
}
252+
253+
/**
254+
* @test
255+
*/
256+
public function testFlushInClusterModeUsesSequentialDel(): void
257+
{
258+
[$store, $clusterClient, $clusterConnection] = $this->createClusterStore();
259+
260+
// Mock GetEntries to return cache keys
261+
$getEntries = m::mock(GetEntries::class);
262+
$getEntries->shouldReceive('execute')
263+
->once()
264+
->with(['_all:tag:users:entries'])
265+
->andReturn(new LazyCollection(['key1', 'key2']));
266+
267+
// Cluster mode should NOT use pipeline
268+
$clusterClient->shouldNotReceive('pipeline');
269+
270+
// Should delete cache entries directly (sequential DEL)
271+
$clusterClient->shouldReceive('del')
272+
->once()
273+
->with('prefix:key1', 'prefix:key2')
274+
->andReturn(2);
275+
276+
// Should delete the tag sorted set
277+
$clusterConnection->shouldReceive('del')
278+
->once()
279+
->with('prefix:_all:tag:users:entries')
280+
->andReturn(1);
281+
282+
$operation = new Flush($store->getContext(), $getEntries);
283+
$operation->execute(['_all:tag:users:entries'], ['users']);
284+
}
285+
286+
/**
287+
* @test
288+
*/
289+
public function testFlushInClusterModeChunksLargeSets(): void
290+
{
291+
[$store, $clusterClient, $clusterConnection] = $this->createClusterStore();
292+
293+
// Create more than CHUNK_SIZE (1000) entries
294+
$entries = [];
295+
for ($i = 1; $i <= 1500; $i++) {
296+
$entries[] = "key{$i}";
297+
}
298+
299+
// Mock GetEntries to return many cache keys
300+
$getEntries = m::mock(GetEntries::class);
301+
$getEntries->shouldReceive('execute')
302+
->once()
303+
->with(['_all:tag:users:entries'])
304+
->andReturn(new LazyCollection($entries));
305+
306+
// Cluster mode should NOT use pipeline
307+
$clusterClient->shouldNotReceive('pipeline');
308+
309+
// First chunk: 1000 entries (sequential DEL)
310+
$firstChunkArgs = [];
311+
for ($i = 1; $i <= 1000; $i++) {
312+
$firstChunkArgs[] = "prefix:key{$i}";
313+
}
314+
$clusterClient->shouldReceive('del')
315+
->once()
316+
->with(...$firstChunkArgs)
317+
->andReturn(1000);
318+
319+
// Second chunk: 500 entries (sequential DEL)
320+
$secondChunkArgs = [];
321+
for ($i = 1001; $i <= 1500; $i++) {
322+
$secondChunkArgs[] = "prefix:key{$i}";
323+
}
324+
$clusterClient->shouldReceive('del')
325+
->once()
326+
->with(...$secondChunkArgs)
327+
->andReturn(500);
328+
329+
// Should delete the tag sorted set
330+
$clusterConnection->shouldReceive('del')
331+
->once()
332+
->with('prefix:_all:tag:users:entries')
333+
->andReturn(1);
334+
335+
$operation = new Flush($store->getContext(), $getEntries);
336+
$operation->execute(['_all:tag:users:entries'], ['users']);
337+
}
338+
339+
/**
340+
* @test
341+
*/
342+
public function testFlushInClusterModeWithMultipleTags(): void
343+
{
344+
[$store, $clusterClient, $clusterConnection] = $this->createClusterStore();
345+
346+
// Mock GetEntries to return cache keys from multiple tags
347+
$getEntries = m::mock(GetEntries::class);
348+
$getEntries->shouldReceive('execute')
349+
->once()
350+
->with(['_all:tag:users:entries', '_all:tag:posts:entries'])
351+
->andReturn(new LazyCollection(['user_key1', 'user_key2', 'post_key1']));
352+
353+
// Cluster mode should NOT use pipeline
354+
$clusterClient->shouldNotReceive('pipeline');
355+
356+
// Should delete all cache entries (sequential DEL)
357+
$clusterClient->shouldReceive('del')
358+
->once()
359+
->with('prefix:user_key1', 'prefix:user_key2', 'prefix:post_key1')
360+
->andReturn(3);
361+
362+
// Should delete both tag sorted sets
363+
$clusterConnection->shouldReceive('del')
364+
->once()
365+
->with('prefix:_all:tag:users:entries', 'prefix:_all:tag:posts:entries')
366+
->andReturn(2);
367+
368+
$operation = new Flush($store->getContext(), $getEntries);
369+
$operation->execute(['_all:tag:users:entries', '_all:tag:posts:entries'], ['users', 'posts']);
370+
}
371+
372+
/**
373+
* @test
374+
*/
375+
public function testFlushInClusterModeWithNoEntries(): void
376+
{
377+
[$store, $clusterClient, $clusterConnection] = $this->createClusterStore();
378+
379+
// Mock GetEntries to return empty collection
380+
$getEntries = m::mock(GetEntries::class);
381+
$getEntries->shouldReceive('execute')
382+
->once()
383+
->with(['_all:tag:users:entries'])
384+
->andReturn(new LazyCollection([]));
385+
386+
// Cluster mode should NOT use pipeline
387+
$clusterClient->shouldNotReceive('pipeline');
388+
389+
// No cache entries to delete - del should NOT be called on cluster client
390+
$clusterClient->shouldNotReceive('del');
391+
392+
// Should still delete the tag sorted set
393+
$clusterConnection->shouldReceive('del')
394+
->once()
395+
->with('prefix:_all:tag:users:entries')
396+
->andReturn(1);
397+
398+
$operation = new Flush($store->getContext(), $getEntries);
399+
$operation->execute(['_all:tag:users:entries'], ['users']);
400+
}
252401
}

0 commit comments

Comments
 (0)