[TASK] Switch skip condition with annotations in RedisBackendTest
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Functional / Cache / Backend / RedisBackendTest.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests\Functional\Cache\Backend;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16 use TYPO3\CMS\Core\Cache\Backend\RedisBackend;
17 use TYPO3\CMS\Core\Cache\Exception\InvalidDataException;
18 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
19 use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
20
21 /**
22 * Test case for the cache to redis backend
23 *
24 * Warning:
25 * These functional tests use and flush redis database numbers 0 and 1 on the
26 * redis host specified by environment variable typo3RedisHost
27 *
28 * @requires extension redis
29 */
30 class RedisBackendTest extends FunctionalTestCase
31 {
32 /**
33 * Set up
34 */
35 protected function setUp()
36 {
37 // Note this functional does NOT call parent::setUp() since it does
38 // not need a full blown instance and database
39 if (!getenv('typo3TestingRedisHost')) {
40 $this->markTestSkipped('environment variable "typo3TestingRedisHost" must be set to run this test');
41 }
42 // Note we assume that if that typo3TestingRedisHost env is set, we can use that for testing,
43 // there is no test to see if the daemon is actually up and running. Tests will fail if env
44 // is set but daemon is down.
45 }
46
47 /**
48 * Sets up the redis cache backend used for testing
49 */
50 protected function setUpSubject(array $backendOptions = []): RedisBackend
51 {
52 // We know this env is set, otherwise setUp() would skip the tests
53 $backendOptions['hostname'] = getenv('typo3TestingRedisHost');
54 // If typo3TestingRedisPort env is set, use it, otherwise fall back to standard port
55 $env = getenv('typo3TestingRedisPort');
56 $backendOptions['port'] = is_string($env) ? (int)$env : 6379;
57
58 $frontendProphecy = $this->prophesize(FrontendInterface::class);
59 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
60
61 $subject = new RedisBackend('Testing', $backendOptions);
62 $subject->setCache($frontendProphecy->reveal());
63 $subject->initializeObject();
64 $subject->flush();
65 return $subject;
66 }
67
68 /**
69 * Sets up a test-internal redis connection to check implementation details
70 */
71 protected function setUpRedis(): \Redis
72 {
73 // We know this env is set, otherwise setUp() would skip the tests
74 $redisHost = getenv('typo3TestingRedisHost');
75 // If typo3TestingRedisPort env is set, use it, otherwise fall back to standard port
76 $env = getenv('typo3TestingRedisPort');
77 $redisPort = is_string($env) ? (int)$env : 6379;
78
79 $redis = new \Redis();
80 $redis->connect($redisHost, $redisPort);
81 return $redis;
82 }
83
84 /**
85 * @test
86 */
87 public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNotAnInteger()
88 {
89 $this->expectException(\InvalidArgumentException::class);
90 $this->expectExceptionCode(1279763057);
91
92 $this->setUpSubject(['database' => 'foo']);
93 }
94
95 /**
96 * @test
97 */
98 public function setDatabaseThrowsExceptionIfGivenDatabaseNumberIsNegative()
99 {
100 $this->expectException(\InvalidArgumentException::class);
101 $this->expectExceptionCode(1279763534);
102
103 $this->setUpSubject(['database' => -1]);
104 }
105
106 /**
107 * @test
108 */
109 public function setCompressionThrowsExceptionIfCompressionParameterIsNotOfTypeBoolean()
110 {
111 $this->expectException(\InvalidArgumentException::class);
112 $this->expectExceptionCode(1289679153);
113
114 $this->setUpSubject(['compression' => 'foo']);
115 }
116
117 /**
118 * @test
119 */
120 public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotInteger()
121 {
122 $this->expectException(\InvalidArgumentException::class);
123 $this->expectExceptionCode(1289679154);
124
125 $this->setUpSubject(['compressionLevel' => 'foo']);
126 }
127
128 /**
129 * @test
130 */
131 public function setCompressionLevelThrowsExceptionIfCompressionLevelIsNotBetweenMinusOneAndNine()
132 {
133 $this->expectException(\InvalidArgumentException::class);
134 $this->expectExceptionCode(1289679155);
135
136 $this->setUpSubject(['compressionLevel' => 11]);
137 }
138
139 /**
140 * @test
141 */
142 public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNotInteger()
143 {
144 $this->expectException(\InvalidArgumentException::class);
145 $this->expectExceptionCode(1487849315);
146
147 $this->setUpSubject(['connectionTimeout' => 'foo']);
148 }
149
150 /**
151 * @test
152 */
153 public function setConnectionTimeoutThrowsExceptionIfConnectionTimeoutIsNegative()
154 {
155 $this->expectException(\InvalidArgumentException::class);
156 $this->expectExceptionCode(1487849326);
157
158 $this->setUpSubject(['connectionTimeout' => -1]);
159 }
160
161 /**
162 * @test
163 */
164 public function setThrowsExceptionIfIdentifierIsNotAString()
165 {
166 $this->expectException(\InvalidArgumentException::class);
167 $this->expectExceptionCode(1377006651);
168
169 $subject = $this->setUpSubject();
170 $subject->set([], 'data');
171 }
172
173 /**
174 * @test
175 */
176 public function setThrowsExceptionIfDataIsNotAString()
177 {
178 $this->expectException(InvalidDataException::class);
179 $this->expectExceptionCode(1279469941);
180
181 $subject = $this->setUpSubject();
182 $subject->set($this->getUniqueId('identifier'), []);
183 }
184
185 /**
186 * @test
187 */
188 public function setThrowsExceptionIfLifetimeIsNegative()
189 {
190 $this->expectException(\InvalidArgumentException::class);
191 $this->expectExceptionCode(1279487573);
192
193 $subject = $this->setUpSubject();
194 $subject->set($this->getUniqueId('identifier'), 'data', [], -42);
195 }
196
197 /**
198 * @test
199 */
200 public function setThrowsExceptionIfLifetimeIsNotNullOrAnInteger()
201 {
202 $this->expectException(\InvalidArgumentException::class);
203 $this->expectExceptionCode(1279488008);
204
205 $subject = $this->setUpSubject();
206 $subject->set($this->getUniqueId('identifier'), 'data', [], []);
207 }
208
209 /**
210 * @test
211 */
212 public function setStoresEntriesInSelectedDatabase()
213 {
214 $redis = $this->setUpRedis();
215 $redis->select(1);
216 $subject = $this->setUpSubject(['database' => 1]);
217 $identifier = $this->getUniqueId('identifier');
218 $subject->set($identifier, 'data');
219 $result = $redis->exists('identData:' . $identifier);
220 if (is_int($result)) {
221 // Since 3.1.4 of phpredis/phpredis the return types has been changed
222 $result = (bool)$result;
223 }
224 $this->assertTrue($result);
225 }
226
227 /**
228 * @test
229 */
230 public function setSavesStringDataTypeForIdentifierToDataEntry()
231 {
232 $subject = $this->setUpSubject();
233 $redis = $this->setUpRedis();
234 $identifier = $this->getUniqueId('identifier');
235 $subject->set($identifier, 'data');
236 $this->assertSame(\Redis::REDIS_STRING, $redis->type('identData:' . $identifier));
237 }
238
239 /**
240 * @test
241 */
242 public function setSavesEntryWithDefaultLifeTime()
243 {
244 $subject = $this->setUpSubject();
245 $redis = $this->setUpRedis();
246 $identifier = $this->getUniqueId('identifier');
247 $defaultLifetime = 42;
248 $subject->setDefaultLifetime($defaultLifetime);
249 $subject->set($identifier, 'data');
250 $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
251 $this->assertSame($defaultLifetime, $lifetimeRegisteredInBackend);
252 }
253
254 /**
255 * @test
256 */
257 public function setSavesEntryWithSpecifiedLifeTime()
258 {
259 $subject = $this->setUpSubject();
260 $redis = $this->setUpRedis();
261 $identifier = $this->getUniqueId('identifier');
262 $lifetime = 43;
263 $subject->set($identifier, 'data', [], $lifetime);
264 $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
265 $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
266 }
267
268 /**
269 * @test
270 */
271 public function setSavesEntryWithUnlimitedLifeTime()
272 {
273 $subject = $this->setUpSubject();
274 $redis = $this->setUpRedis();
275 $identifier = $this->getUniqueId('identifier');
276 $subject->set($identifier, 'data', [], 0);
277 $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
278 $this->assertSame(31536000, $lifetimeRegisteredInBackend);
279 }
280
281 /**
282 * @test
283 */
284 public function setOverwritesExistingEntryWithNewData()
285 {
286 $subject = $this->setUpSubject();
287 $data = 'data 1';
288 $identifier = $this->getUniqueId('identifier');
289 $subject->set($identifier, $data);
290 $otherData = 'data 2';
291 $subject->set($identifier, $otherData);
292 $fetchedData = $subject->get($identifier);
293 $this->assertSame($otherData, $fetchedData);
294 }
295
296 /**
297 * @test
298 */
299 public function setOverwritesExistingEntryWithSpecifiedLifetime()
300 {
301 $subject = $this->setUpSubject();
302 $redis = $this->setUpRedis();
303 $data = 'data';
304 $identifier = $this->getUniqueId('identifier');
305 $subject->set($identifier, $data);
306 $lifetime = 42;
307 $subject->set($identifier, $data, [], $lifetime);
308 $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
309 $this->assertSame($lifetime, $lifetimeRegisteredInBackend);
310 }
311
312 /**
313 * @test
314 */
315 public function setOverwritesExistingEntryWithNewDefaultLifetime()
316 {
317 $subject = $this->setUpSubject();
318 $redis = $this->setUpRedis();
319 $data = 'data';
320 $identifier = $this->getUniqueId('identifier');
321 $lifetime = 42;
322 $subject->set($identifier, $data, [], $lifetime);
323 $newDefaultLifetime = 43;
324 $subject->setDefaultLifetime($newDefaultLifetime);
325 $subject->set($identifier, $data, [], $newDefaultLifetime);
326 $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
327 $this->assertSame($newDefaultLifetime, $lifetimeRegisteredInBackend);
328 }
329
330 /**
331 * @test
332 */
333 public function setOverwritesExistingEntryWithNewUnlimitedLifetime()
334 {
335 $subject = $this->setUpSubject();
336 $redis = $this->setUpRedis();
337 $data = 'data';
338 $identifier = $this->getUniqueId('identifier');
339 $lifetime = 42;
340 $subject->set($identifier, $data, [], $lifetime);
341 $subject->set($identifier, $data, [], 0);
342 $lifetimeRegisteredInBackend = $redis->ttl('identData:' . $identifier);
343 $this->assertSame(31536000, $lifetimeRegisteredInBackend);
344 }
345
346 /**
347 * @test
348 */
349 public function setSavesSetDataTypeForIdentifierToTagsSet()
350 {
351 $subject = $this->setUpSubject();
352 $redis = $this->setUpRedis();
353 $identifier = $this->getUniqueId('identifier');
354 $subject->set($identifier, 'data', ['tag']);
355 $this->assertSame(\Redis::REDIS_SET, $redis->type('identTags:' . $identifier));
356 }
357
358 /**
359 * @test
360 */
361 public function setSavesSpecifiedTagsInIdentifierToTagsSet()
362 {
363 $subject = $this->setUpSubject();
364 $redis = $this->setUpRedis();
365 $identifier = $this->getUniqueId('identifier');
366 $tags = ['thatTag', 'thisTag'];
367 $subject->set($identifier, 'data', $tags);
368 $savedTags = $redis->sMembers('identTags:' . $identifier);
369 sort($savedTags);
370 $this->assertSame($tags, $savedTags);
371 }
372
373 /**
374 * @test
375 */
376 public function setRemovesAllPreviouslySetTagsFromIdentifierToTagsSet()
377 {
378 $subject = $this->setUpSubject();
379 $redis = $this->setUpRedis();
380 $identifier = $this->getUniqueId('identifier');
381 $tags = ['fooTag', 'barTag'];
382 $subject->set($identifier, 'data', $tags);
383 $subject->set($identifier, 'data', []);
384 $this->assertSame([], $redis->sMembers('identTags:' . $identifier));
385 }
386
387 /**
388 * @test
389 */
390 public function setRemovesMultiplePreviouslySetTagsFromIdentifierToTagsSet()
391 {
392 $subject = $this->setUpSubject();
393 $redis = $this->setUpRedis();
394 $identifier = $this->getUniqueId('identifier');
395 $firstTagSet = ['tag1', 'tag2', 'tag3', 'tag4'];
396 $subject->set($identifier, 'data', $firstTagSet);
397 $secondTagSet = ['tag1', 'tag3'];
398 $subject->set($identifier, 'data', $secondTagSet);
399 $actualTagSet = $redis->sMembers('identTags:' . $identifier);
400 sort($actualTagSet);
401 $this->assertSame($secondTagSet, $actualTagSet);
402 }
403
404 /**
405 * @test
406 */
407 public function setSavesSetDataTypeForTagToIdentifiersSet()
408 {
409 $subject = $this->setUpSubject();
410 $redis = $this->setUpRedis();
411 $identifier = $this->getUniqueId('identifier');
412 $tag = 'tag';
413 $subject->set($identifier, 'data', [$tag]);
414 $this->assertSame(\Redis::REDIS_SET, $redis->type('tagIdents:' . $tag));
415 }
416
417 /**
418 * @test
419 */
420 public function setSavesIdentifierInTagToIdentifiersSetOfSpecifiedTag()
421 {
422 $subject = $this->setUpSubject();
423 $redis = $this->setUpRedis();
424 $identifier = $this->getUniqueId('identifier');
425 $tag = 'thisTag';
426 $subject->set($identifier, 'data', [$tag]);
427 $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
428 $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
429 }
430
431 /**
432 * @test
433 */
434 public function setAppendsSecondIdentifierInTagToIdentifiersEntry()
435 {
436 $subject = $this->setUpSubject();
437 $redis = $this->setUpRedis();
438 $firstIdentifier = $this->getUniqueId('identifier1-');
439 $tag = 'thisTag';
440 $subject->set($firstIdentifier, 'data', [$tag]);
441 $secondIdentifier = $this->getUniqueId('identifier2-');
442 $subject->set($secondIdentifier, 'data', [$tag]);
443 $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
444 sort($savedTagToIdentifiersMemberArray);
445 $identifierArray = [$firstIdentifier, $secondIdentifier];
446 sort($identifierArray);
447 $this->assertSame([$firstIdentifier, $secondIdentifier], $savedTagToIdentifiersMemberArray);
448 }
449
450 /**
451 * @test
452 */
453 public function setRemovesIdentifierFromTagToIdentifiersEntryIfTagIsOmittedOnConsecutiveSet()
454 {
455 $subject = $this->setUpSubject();
456 $redis = $this->setUpRedis();
457 $identifier = $this->getUniqueId('identifier');
458 $tag = 'thisTag';
459 $subject->set($identifier, 'data', [$tag]);
460 $subject->set($identifier, 'data', []);
461 $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
462 $this->assertSame([], $savedTagToIdentifiersMemberArray);
463 }
464
465 /**
466 * @test
467 */
468 public function setAddsIdentifierInTagToIdentifiersEntryIfTagIsAddedOnConsecutiveSet()
469 {
470 $subject = $this->setUpSubject();
471 $redis = $this->setUpRedis();
472 $identifier = $this->getUniqueId('identifier');
473 $subject->set($identifier, 'data');
474 $tag = 'thisTag';
475 $subject->set($identifier, 'data', [$tag]);
476 $savedTagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
477 $this->assertSame([$identifier], $savedTagToIdentifiersMemberArray);
478 }
479
480 /**
481 * @test
482 */
483 public function setSavesCompressedDataWithEnabledCompression()
484 {
485 $subject = $this->setUpSubject([
486 'compression' => true
487 ]);
488 $redis = $this->setUpRedis();
489 $identifier = $this->getUniqueId('identifier');
490 $data = 'some data ' . microtime();
491 $subject->set($identifier, $data);
492 $uncompresedStoredData = '';
493 try {
494 $uncompresedStoredData = @gzuncompress($redis->get('identData:' . $identifier));
495 } catch (\Exception $e) {
496 }
497 $this->assertEquals($data, $uncompresedStoredData, 'Original and compressed data don\'t match');
498 }
499
500 /**
501 * @test
502 */
503 public function setSavesPlaintextDataWithEnabledCompressionAndCompressionLevel0()
504 {
505 $subject = $this->setUpSubject([
506 'compression' => true,
507 'compressionLevel' => 0
508 ]);
509 $redis = $this->setUpRedis();
510 $identifier = $this->getUniqueId('identifier');
511 $data = 'some data ' . microtime();
512 $subject->set($identifier, $data);
513 $this->assertGreaterThan(0, substr_count($redis->get('identData:' . $identifier), $data), 'Plaintext data not found');
514 }
515
516 /**
517 * @test
518 */
519 public function hasThrowsExceptionIfIdentifierIsNotAString()
520 {
521 $this->expectException(\InvalidArgumentException::class);
522 $this->expectExceptionCode(1377006653);
523
524 $subject = $this->setUpSubject();
525 $subject->has([]);
526 }
527
528 /**
529 * @test
530 */
531 public function hasReturnsFalseForNotExistingEntry()
532 {
533 $subject = $this->setUpSubject();
534 $identifier = $this->getUniqueId('identifier');
535 $this->assertFalse($subject->has($identifier));
536 }
537
538 /**
539 * @test
540 */
541 public function hasReturnsTrueForPreviouslySetEntry()
542 {
543 $subject = $this->setUpSubject();
544 $identifier = $this->getUniqueId('identifier');
545 $subject->set($identifier, 'data');
546 $this->assertTrue($subject->has($identifier));
547 }
548
549 /**
550 * @test
551 */
552 public function getThrowsExceptionIfIdentifierIsNotAString()
553 {
554 $this->expectException(\InvalidArgumentException::class);
555 //@todo Add exception code with redis extension
556
557 $subject = $this->setUpSubject();
558 $subject->get([]);
559 }
560
561 /**
562 * @test
563 */
564 public function getReturnsPreviouslyCompressedSetEntry()
565 {
566 $subject = $this->setUpSubject([
567 'compression' => true
568 ]);
569 $data = 'data';
570 $identifier = $this->getUniqueId('identifier');
571 $subject->set($identifier, $data);
572 $fetchedData = $subject->get($identifier);
573 $this->assertSame($data, $fetchedData);
574 }
575
576 /**
577 * @test
578 */
579 public function getReturnsPreviouslySetEntry()
580 {
581 $subject = $this->setUpSubject();
582 $data = 'data';
583 $identifier = $this->getUniqueId('identifier');
584 $subject->set($identifier, $data);
585 $fetchedData = $subject->get($identifier);
586 $this->assertSame($data, $fetchedData);
587 }
588
589 /**
590 * @test
591 */
592 public function removeThrowsExceptionIfIdentifierIsNotAString()
593 {
594 $this->expectException(\InvalidArgumentException::class);
595 $this->expectExceptionCode(1377006654);
596
597 $subject = $this->setUpSubject();
598 $subject->remove([]);
599 }
600
601 /**
602 * @test
603 */
604 public function removeReturnsFalseIfNoEntryWasDeleted()
605 {
606 $subject = $this->setUpSubject();
607 $this->assertFalse($subject->remove($this->getUniqueId('identifier')));
608 }
609
610 /**
611 * @test
612 */
613 public function removeReturnsTrueIfAnEntryWasDeleted()
614 {
615 $subject = $this->setUpSubject();
616 $identifier = $this->getUniqueId('identifier');
617 $subject->set($identifier, 'data');
618 $this->assertTrue($subject->remove($identifier));
619 }
620
621 /**
622 * @test
623 */
624 public function removeDeletesEntryFromCache()
625 {
626 $subject = $this->setUpSubject();
627 $identifier = $this->getUniqueId('identifier');
628 $subject->set($identifier, 'data');
629 $subject->remove($identifier);
630 $this->assertFalse($subject->has($identifier));
631 }
632
633 /**
634 * @test
635 */
636 public function removeDeletesIdentifierToTagEntry()
637 {
638 $subject = $this->setUpSubject();
639 $redis = $this->setUpRedis();
640 $identifier = $this->getUniqueId('identifier');
641 $tag = 'thisTag';
642 $subject->set($identifier, 'data', [$tag]);
643 $subject->remove($identifier);
644 $result = $redis->exists('identTags:' . $identifier);
645 if (is_int($result)) {
646 // Since 3.1.4 of phpredis/phpredis the return types has been changed
647 $result = (bool)$result;
648 }
649 $this->assertFalse($result);
650 }
651
652 /**
653 * @test
654 */
655 public function removeDeletesIdentifierFromTagToIdentifiersSet()
656 {
657 $subject = $this->setUpSubject();
658 $redis = $this->setUpRedis();
659 $identifier = $this->getUniqueId('identifier');
660 $tag = 'thisTag';
661 $subject->set($identifier, 'data', [$tag]);
662 $subject->remove($identifier);
663 $tagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
664 $this->assertSame([], $tagToIdentifiersMemberArray);
665 }
666
667 /**
668 * @test
669 */
670 public function removeDeletesIdentifierFromTagToIdentifiersSetWithMultipleEntries()
671 {
672 $subject = $this->setUpSubject();
673 $redis = $this->setUpRedis();
674 $firstIdentifier = $this->getUniqueId('identifier');
675 $secondIdentifier = $this->getUniqueId('identifier');
676 $tag = 'thisTag';
677 $subject->set($firstIdentifier, 'data', [$tag]);
678 $subject->set($secondIdentifier, 'data', [$tag]);
679 $subject->remove($firstIdentifier);
680 $tagToIdentifiersMemberArray = $redis->sMembers('tagIdents:' . $tag);
681 $this->assertSame([$secondIdentifier], $tagToIdentifiersMemberArray);
682 }
683
684 /**
685 * @test
686 */
687 public function findIdentifiersByTagThrowsExceptionIfTagIsNotAString()
688 {
689 $this->expectException(\InvalidArgumentException::class);
690 $this->expectExceptionCode(1377006655);
691
692 $subject = $this->setUpSubject();
693 $subject->findIdentifiersByTag([]);
694 }
695
696 /**
697 * @test
698 */
699 public function findIdentifiersByTagReturnsEmptyArrayForNotExistingTag()
700 {
701 $subject = $this->setUpSubject();
702 $this->assertSame([], $subject->findIdentifiersByTag('thisTag'));
703 }
704
705 /**
706 * @test
707 */
708 public function findIdentifiersByTagReturnsAllIdentifiersTagedWithSpecifiedTag()
709 {
710 $subject = $this->setUpSubject();
711 $firstIdentifier = $this->getUniqueId('identifier1-');
712 $secondIdentifier = $this->getUniqueId('identifier2-');
713 $thirdIdentifier = $this->getUniqueId('identifier3-');
714 $tagsForFirstIdentifier = ['thisTag'];
715 $tagsForSecondIdentifier = ['thatTag'];
716 $tagsForThirdIdentifier = ['thisTag', 'thatTag'];
717 $subject->set($firstIdentifier, 'data', $tagsForFirstIdentifier);
718 $subject->set($secondIdentifier, 'data', $tagsForSecondIdentifier);
719 $subject->set($thirdIdentifier, 'data', $tagsForThirdIdentifier);
720 $expectedResult = [$firstIdentifier, $thirdIdentifier];
721 $actualResult = $subject->findIdentifiersByTag('thisTag');
722 sort($actualResult);
723 $this->assertSame($expectedResult, $actualResult);
724 }
725
726 /**
727 * @test
728 */
729 public function flushRemovesAllEntriesFromCache()
730 {
731 $subject = $this->setUpSubject();
732 $redis = $this->setUpRedis();
733 $identifier = $this->getUniqueId('identifier');
734 $subject->set($identifier, 'data');
735 $subject->flush();
736 $this->assertSame([], $redis->getKeys('*'));
737 }
738
739 /**
740 * @test
741 */
742 public function flushByTagThrowsExceptionIfTagIsNotAString()
743 {
744 $this->expectException(\InvalidArgumentException::class);
745 $this->expectExceptionCode(1377006656);
746
747 $subject = $this->setUpSubject();
748 $subject->flushByTag([]);
749 }
750
751 /**
752 * @test
753 */
754 public function flushByTagRemovesEntriesTaggedWithSpecifiedTag()
755 {
756 $subject = $this->setUpSubject();
757 $identifier = $this->getUniqueId('identifier');
758 $subject->set($identifier . 'A', 'data', ['tag1']);
759 $subject->set($identifier . 'B', 'data', ['tag2']);
760 $subject->set($identifier . 'C', 'data', ['tag1', 'tag2']);
761 $subject->flushByTag('tag1');
762 $expectedResult = [false, true, false];
763 $actualResult = [
764 $subject->has($identifier . 'A'),
765 $subject->has($identifier . 'B'),
766 $subject->has($identifier . 'C')
767 ];
768 $this->assertSame($expectedResult, $actualResult);
769 }
770
771 /**
772 * @test
773 */
774 public function flushByTagsRemovesEntriesTaggedWithSpecifiedTags()
775 {
776 $subject = $this->setUpSubject();
777 $identifier = $this->getUniqueId('identifier');
778 $subject->set($identifier . 'A', 'data', ['tag1']);
779 $subject->set($identifier . 'B', 'data', ['tag2']);
780 $subject->set($identifier . 'C', 'data', ['tag1', 'tag2']);
781 $subject->set($identifier . 'D', 'data', ['tag3']);
782 $subject->flushByTags(['tag1', 'tag2']);
783 $expectedResult = [false, false, false, true];
784 $actualResult = [
785 $subject->has($identifier . 'A'),
786 $subject->has($identifier . 'B'),
787 $subject->has($identifier . 'C'),
788 $subject->has($identifier . 'D')
789 ];
790 $this->assertSame($expectedResult, $actualResult);
791 }
792
793 /**
794 * @test
795 */
796 public function flushByTagRemovesTemporarySet()
797 {
798 $subject = $this->setUpSubject();
799 $redis = $this->setUpRedis();
800 $identifier = $this->getUniqueId('identifier');
801 $subject->set($identifier . 'A', 'data', ['tag1']);
802 $subject->set($identifier . 'C', 'data', ['tag1', 'tag2']);
803 $subject->flushByTag('tag1');
804 $this->assertSame([], $redis->getKeys('temp*'));
805 }
806
807 /**
808 * @test
809 */
810 public function flushByTagRemovesIdentifierToTagsSetOfEntryTaggedWithGivenTag()
811 {
812 $subject = $this->setUpSubject();
813 $redis = $this->setUpRedis();
814 $identifier = $this->getUniqueId('identifier');
815 $tag = 'tag1';
816 $subject->set($identifier, 'data', [$tag]);
817 $subject->flushByTag($tag);
818 $result = $redis->exists('identTags:' . $identifier);
819 if (is_int($result)) {
820 // Since 3.1.4 of phpredis/phpredis the return types has been changed
821 $result = (bool)$result;
822 }
823 $this->assertFalse($result);
824 }
825
826 /**
827 * @test
828 */
829 public function flushByTagDoesNotRemoveIdentifierToTagsSetOfUnrelatedEntry()
830 {
831 $subject = $this->setUpSubject();
832 $redis = $this->setUpRedis();
833 $identifierToBeRemoved = $this->getUniqueId('identifier');
834 $tagToRemove = 'tag1';
835 $subject->set($identifierToBeRemoved, 'data', [$tagToRemove]);
836 $identifierNotToBeRemoved = $this->getUniqueId('identifier');
837 $tagNotToRemove = 'tag2';
838 $subject->set($identifierNotToBeRemoved, 'data', [$tagNotToRemove]);
839 $subject->flushByTag($tagToRemove);
840 $this->assertSame([$tagNotToRemove], $redis->sMembers('identTags:' . $identifierNotToBeRemoved));
841 }
842
843 /**
844 * @test
845 */
846 public function flushByTagRemovesTagToIdentifiersSetOfGivenTag()
847 {
848 $subject = $this->setUpSubject();
849 $redis = $this->setUpRedis();
850 $identifier = $this->getUniqueId('identifier');
851 $tag = 'tag1';
852 $subject->set($identifier, 'data', [$tag]);
853 $subject->flushByTag($tag);
854 $result = $redis->exists('tagIdents:' . $tag);
855 if (is_int($result)) {
856 // Since 3.1.4 of phpredis/phpredis the return types has been changed
857 $result = (bool)$result;
858 }
859 $this->assertFalse($result);
860 }
861
862 /**
863 * @test
864 */
865 public function flushByTagRemovesIdentifiersTaggedWithGivenTagFromTagToIdentifiersSets()
866 {
867 $subject = $this->setUpSubject();
868 $redis = $this->setUpRedis();
869 $identifier = $this->getUniqueId('identifier');
870 $subject->set($identifier . 'A', 'data', ['tag1', 'tag2']);
871 $subject->set($identifier . 'B', 'data', ['tag1', 'tag2']);
872 $subject->set($identifier . 'C', 'data', ['tag2']);
873 $subject->flushByTag('tag1');
874 $this->assertSame([$identifier . 'C'], $redis->sMembers('tagIdents:tag2'));
875 }
876
877 /**
878 * @test
879 */
880 public function collectGarbageDoesNotRemoveNotExpiredIdentifierToDataEntry()
881 {
882 $subject = $this->setUpSubject();
883 $redis = $this->setUpRedis();
884 $identifier = $this->getUniqueId('identifier');
885 $subject->set($identifier . 'A', 'data', ['tag']);
886 $subject->set($identifier . 'B', 'data', ['tag']);
887 $redis->delete('identData:' . $identifier . 'A');
888 $subject->collectGarbage();
889 $result = $redis->exists('identData:' . $identifier . 'B');
890 if (is_int($result)) {
891 // Since 3.1.4 of phpredis/phpredis the return types has been changed
892 $result = (bool)$result;
893 }
894 $this->assertTrue($result);
895 }
896
897 /**
898 * @test
899 */
900 public function collectGarbageRemovesLeftOverIdentifierToTagsSet()
901 {
902 $subject = $this->setUpSubject();
903 $redis = $this->setUpRedis();
904 $identifier = $this->getUniqueId('identifier');
905 $subject->set($identifier . 'A', 'data', ['tag']);
906 $subject->set($identifier . 'B', 'data', ['tag']);
907 $redis->delete('identData:' . $identifier . 'A');
908 $subject->collectGarbage();
909 $expectedResult = [false, true];
910 $resultA = $redis->exists('identTags:' . $identifier . 'A');
911 $resultB = $redis->exists('identTags:' . $identifier . 'B');
912 if (is_int($resultA)) {
913 // Since 3.1.4 of phpredis/phpredis the return types has been changed
914 $resultA = (bool)$resultA;
915 }
916 if (is_int($resultB)) {
917 // Since 3.1.4 of phpredis/phpredis the return types has been changed
918 $resultB = (bool)$resultB;
919 }
920 $actualResult = [
921 $resultA,
922 $resultB
923 ];
924 $this->assertSame($expectedResult, $actualResult);
925 }
926
927 /**
928 * @test
929 */
930 public function collectGarbageRemovesExpiredIdentifierFromTagsToIdentifierSet()
931 {
932 $subject = $this->setUpSubject();
933 $redis = $this->setUpRedis();
934 $identifier = $this->getUniqueId('identifier');
935 $subject->set($identifier . 'A', 'data', ['tag1', 'tag2']);
936 $subject->set($identifier . 'B', 'data', ['tag2']);
937 $redis->delete('identData:' . $identifier . 'A');
938 $subject->collectGarbage();
939 $expectedResult = [
940 [],
941 [$identifier . 'B']
942 ];
943 $actualResult = [
944 $redis->sMembers('tagIdents:tag1'),
945 $redis->sMembers('tagIdents:tag2')
946 ];
947 $this->assertSame($expectedResult, $actualResult);
948 }
949 }