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