@@ -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