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