[BUGFIX] Fix functional tests for EXT:core on PostgreSQL
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / Functional / Cache / Backend / Typo3DatabaseBackendTest.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
17 use TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend;
18 use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
19 use TYPO3\CMS\Core\Database\Connection;
20 use TYPO3\CMS\Core\Database\ConnectionPool;
21
22 /**
23 * Test case
24 */
25 class Typo3DatabaseBackendTest extends \TYPO3\TestingFramework\Core\Functional\FunctionalTestCase
26 {
27
28 /**
29 * @test
30 */
31 public function getReturnsPreviouslySetEntry()
32 {
33 $frontendProphecy = $this->prophesize(FrontendInterface::class);
34 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
35
36 $subject = new Typo3DatabaseBackend('Testing');
37 $subject->setCache($frontendProphecy->reveal());
38
39 $subject->set('myIdentifier', 'myData');
40 $this->assertSame('myData', $subject->get('myIdentifier'));
41 }
42
43 /**
44 * @test
45 */
46 public function getReturnsPreviouslySetEntryWithNewContentIfSetWasCalledMultipleTimes()
47 {
48 $frontendProphecy = $this->prophesize(FrontendInterface::class);
49 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
50
51 $subject = new Typo3DatabaseBackend('Testing');
52 $subject->setCache($frontendProphecy->reveal());
53
54 $subject->set('myIdentifier', 'myData');
55 $subject->set('myIdentifier', 'myNewData');
56 $this->assertSame('myNewData', $subject->get('myIdentifier'));
57 }
58
59 /**
60 * @test
61 */
62 public function setInsertsDataWithTagsIntoCacheTable()
63 {
64 $frontendProphecy = $this->prophesize(FrontendInterface::class);
65 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
66
67 $subject = new Typo3DatabaseBackend('Testing');
68 $subject->setCache($frontendProphecy->reveal());
69
70 $subject->set('myIdentifier', 'myData', ['aTag', 'anotherTag']);
71
72 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
73 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
74 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'myIdentifier']));
75 $this->assertSame(1, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'myIdentifier', 'tag' => 'aTag']));
76 $this->assertSame(1, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'myIdentifier', 'tag' => 'anotherTag']));
77 }
78
79 /**
80 * @test
81 */
82 public function setStoresCompressedContent()
83 {
84 $frontendProphecy = $this->prophesize(FrontendInterface::class);
85 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
86
87 // Have backend with compression enabled
88 $subject = new Typo3DatabaseBackend('Testing', ['compression' => true]);
89 $subject->setCache($frontendProphecy->reveal());
90
91 $subject->set('myIdentifier', 'myCachedContent');
92
93 $row = (new ConnectionPool())
94 ->getConnectionForTable('cf_cache_pages')
95 ->select(
96 ['content'],
97 'cf_cache_pages',
98 ['identifier' => 'myIdentifier']
99 )
100 ->fetch();
101
102 // Content comes back uncompressed
103 $this->assertSame('myCachedContent', gzuncompress($row['content']));
104 }
105
106 /**
107 * @test
108 */
109 public function getReturnsFalseIfNoCacheEntryExists()
110 {
111 $frontendProphecy = $this->prophesize(FrontendInterface::class);
112 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
113
114 $subject = new Typo3DatabaseBackend('Testing');
115 $subject->setCache($frontendProphecy->reveal());
116
117 $this->assertFalse($subject->get('myIdentifier'));
118 }
119
120 /**
121 * @test
122 */
123 public function getReturnsFalseForExpiredCacheEntry()
124 {
125 $frontendProphecy = $this->prophesize(FrontendInterface::class);
126 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
127
128 // Push an expired row into db
129 (new ConnectionPool())->getConnectionForTable('cf_cache_pages')->insert(
130 'cf_cache_pages',
131 [
132 'identifier' => 'myIdentifier',
133 'expires' => $GLOBALS['EXEC_TIME'] - 60,
134 'content' => 'myCachedContent',
135 ],
136 [
137 'content' => Connection::PARAM_LOB
138 ]
139 );
140
141 $subject = new Typo3DatabaseBackend('Testing');
142 $subject->setCache($frontendProphecy->reveal());
143
144 $this->assertFalse($subject->get('myIdentifier'));
145 }
146
147 /**
148 * @test
149 */
150 public function getReturnsNotExpiredCacheEntry()
151 {
152 $frontendProphecy = $this->prophesize(FrontendInterface::class);
153 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
154
155 // Push a row into db
156 (new ConnectionPool())->getConnectionForTable('cf_cache_pages')->insert(
157 'cf_cache_pages',
158 [
159 'identifier' => 'myIdentifier',
160 'expires' => $GLOBALS['EXEC_TIME'] + 60,
161 'content' => 'myCachedContent',
162 ],
163 [
164 'content' => Connection::PARAM_LOB
165 ]
166 );
167
168 $subject = new Typo3DatabaseBackend('Testing');
169 $subject->setCache($frontendProphecy->reveal());
170
171 $this->assertSame('myCachedContent', $subject->get('myIdentifier'));
172 }
173
174 /**
175 * @test
176 */
177 public function getReturnsUnzipsNotExpiredCacheEntry()
178 {
179 $frontendProphecy = $this->prophesize(FrontendInterface::class);
180 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
181
182 // Push a compressed row into db
183 (new ConnectionPool())->getConnectionForTable('cf_cache_pages')->insert(
184 'cf_cache_pages',
185 [
186 'identifier' => 'myIdentifier',
187 'expires' => $GLOBALS['EXEC_TIME'] + 60,
188 'content' => gzcompress('myCachedContent'),
189 ],
190 [
191 'content' => Connection::PARAM_LOB
192 ]
193 );
194
195 // Have backend with compression enabled
196 $subject = new Typo3DatabaseBackend('Testing', ['compression' => true]);
197 $subject->setCache($frontendProphecy->reveal());
198
199 // Content comes back uncompressed
200 $this->assertSame('myCachedContent', $subject->get('myIdentifier'));
201 }
202
203 /**
204 * @test
205 */
206 public function getReturnsEmptyStringUnzipped()
207 {
208 $frontendProphecy = $this->prophesize(FrontendInterface::class);
209 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
210
211 // Push a compressed row into db
212 (new ConnectionPool())->getConnectionForTable('cf_cache_pages')->insert(
213 'cf_cache_pages',
214 [
215 'identifier' => 'myIdentifier',
216 'expires' => $GLOBALS['EXEC_TIME'] + 60,
217 'content' => gzcompress(''),
218 ],
219 [
220 'content' => Connection::PARAM_LOB
221 ]
222 );
223
224 // Have backend with compression enabled
225 $subject = new Typo3DatabaseBackend('Testing', ['compression' => true]);
226 $subject->setCache($frontendProphecy->reveal());
227
228 // Content comes back uncompressed
229 $this->assertSame('', $subject->get('myIdentifier'));
230 }
231
232 /**
233 * @test
234 */
235 public function hasReturnsFalseIfNoCacheEntryExists()
236 {
237 $frontendProphecy = $this->prophesize(FrontendInterface::class);
238 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
239
240 $subject = new Typo3DatabaseBackend('Testing');
241 $subject->setCache($frontendProphecy->reveal());
242
243 $this->assertFalse($subject->has('myIdentifier'));
244 }
245
246 /**
247 * @test
248 */
249 public function hasReturnsFalseForExpiredCacheEntry()
250 {
251 $frontendProphecy = $this->prophesize(FrontendInterface::class);
252 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
253
254 // Push an expired row into db
255 (new ConnectionPool())->getConnectionForTable('cf_cache_pages')->insert(
256 'cf_cache_pages',
257 [
258 'identifier' => 'myIdentifier',
259 'expires' => $GLOBALS['EXEC_TIME'] - 60,
260 'content' => 'myCachedContent',
261 ],
262 [
263 'content' => Connection::PARAM_LOB
264 ]
265 );
266
267 $subject = new Typo3DatabaseBackend('Testing');
268 $subject->setCache($frontendProphecy->reveal());
269
270 $this->assertFalse($subject->has('myIdentifier'));
271 }
272
273 /**
274 * @test
275 */
276 public function hasReturnsNotExpiredCacheEntry()
277 {
278 $frontendProphecy = $this->prophesize(FrontendInterface::class);
279 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
280
281 // Push a row into db
282 (new ConnectionPool())->getConnectionForTable('cf_cache_pages')->insert(
283 'cf_cache_pages',
284 [
285 'identifier' => 'myIdentifier',
286 'expires' => $GLOBALS['EXEC_TIME'] + 60,
287 'content' => 'myCachedContent',
288 ],
289 [
290 'content' => Connection::PARAM_LOB
291 ]
292 );
293
294 $subject = new Typo3DatabaseBackend('Testing');
295 $subject->setCache($frontendProphecy->reveal());
296
297 $this->assertTrue($subject->has('myIdentifier'));
298 }
299
300 /**
301 * @test
302 */
303 public function removeReturnsFalseIfNoEntryHasBeenRemoved()
304 {
305 $frontendProphecy = $this->prophesize(FrontendInterface::class);
306 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
307
308 $subject = new Typo3DatabaseBackend('Testing');
309 $subject->setCache($frontendProphecy->reveal());
310
311 $this->assertFalse($subject->remove('myIdentifier'));
312 }
313
314 /**
315 * @test
316 */
317 public function removeReturnsTrueIfAnEntryHasBeenRemoved()
318 {
319 $frontendProphecy = $this->prophesize(FrontendInterface::class);
320 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
321
322 // Push a row into db
323 (new ConnectionPool())->getConnectionForTable('cf_cache_pages')->insert(
324 'cf_cache_pages',
325 [
326 'identifier' => 'myIdentifier',
327 'expires' => $GLOBALS['EXEC_TIME'] + 60,
328 'content' => 'myCachedContent',
329 ],
330 [
331 'content' => Connection::PARAM_LOB
332 ]
333 );
334
335 $subject = new Typo3DatabaseBackend('Testing');
336 $subject->setCache($frontendProphecy->reveal());
337
338 $this->assertTrue($subject->remove('myIdentifier'));
339 }
340
341 /**
342 * @test
343 */
344 public function removeRemovesCorrectEntriesFromDatabase()
345 {
346 $frontendProphecy = $this->prophesize(FrontendInterface::class);
347 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
348
349 // Add one cache row to remove and another one that shouldn't be removed
350 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
351 $cacheTableConnection->bulkInsert(
352 'cf_cache_pages',
353 [
354 ['myIdentifier', $GLOBALS['EXEC_TIME'] + 60, 'myCachedContent'],
355 ['otherIdentifier', $GLOBALS['EXEC_TIME'] + 60, 'otherCachedContent'],
356 ],
357 ['identifier', 'expires', 'content'],
358 [
359 'content' => Connection::PARAM_LOB
360 ]
361 );
362 $subject = new Typo3DatabaseBackend('Testing');
363 $subject->setCache($frontendProphecy->reveal());
364
365 // Add a couple of tags
366 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
367 $tagsTableConnection->bulkInsert(
368 'cf_cache_pages_tags',
369 [
370 ['myIdentifier', 'aTag'],
371 ['myIdentifier', 'otherTag'],
372 ['otherIdentifier', 'aTag'],
373 ['otherIdentifier', 'otherTag'],
374 ],
375 ['identifier', 'tag']
376 );
377
378 $subject->remove('myIdentifier');
379
380 // cache row with removed identifier has been removed, other one exists
381 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'myIdentifier']));
382 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'otherIdentifier']));
383
384 // tags of myIdentifier should have been removed, others exist
385 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'myIdentifier']));
386 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'otherIdentifier']));
387 }
388
389 /**
390 * @test
391 */
392 public function findIdentifiersByTagReturnsIdentifierTaggedWithGivenTag()
393 {
394 $subject = $this->getSubjectObject();
395
396 $this->assertEquals(['idA' => 'idA'], $subject->findIdentifiersByTag('tagA'));
397 $this->assertEquals(['idA' => 'idA', 'idB' => 'idB'], $subject->findIdentifiersByTag('tagB'));
398 $this->assertEquals(['idB' => 'idB', 'idC' => 'idC'], $subject->findIdentifiersByTag('tagC'));
399 }
400
401 /**
402 * @test
403 *
404 * @group mysql
405 */
406 public function flushByTagWorksWithEmptyCacheTablesWithMysql()
407 {
408 $subject = $this->getSubjectObject(true);
409 $subject->flushByTag('tagB');
410 }
411
412 /**
413 * @test
414 *
415 * @group mysql
416 */
417 public function flushByTagsWorksWithEmptyCacheTablesWithMysql()
418 {
419 $subject = $this->getSubjectObject(true);
420 $subject->flushByTags(['tagB']);
421 }
422
423 /**
424 * @test
425 *
426 * @group mysql
427 */
428 public function flushByTagRemovesCorrectRowsFromDatabaseWithMysql()
429 {
430 $subject = $this->getSubjectObject(true);
431 $subject->flushByTag('tagB');
432
433 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
434 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
435 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
436 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
437 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
438 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
439 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
440 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
441 }
442
443 /**
444 * @test
445 *
446 * @group mysql
447 */
448 public function flushByTagsRemovesCorrectRowsFromDatabaseWithMysql()
449 {
450 $subject = $this->getSubjectObject(true);
451 $subject->flushByTags(['tagC', 'tagD']);
452
453 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
454 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
455 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
456 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
457 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
458 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
459 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
460 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
461 }
462
463 /**
464 * @test
465 */
466 public function flushByTagWorksWithEmptyCacheTablesWithNonMysql()
467 {
468 $subject = $this->getSubjectObject(true, false);
469 $subject->flushByTag('tagB');
470 }
471
472 /**
473 * @test
474 */
475 public function flushByTagsWorksWithEmptyCacheTablesWithNonMysql()
476 {
477 $subject = $this->getSubjectObject(true, false);
478 $subject->flushByTags(['tagB', 'tagC']);
479 }
480
481 /**
482 * @test
483 */
484 public function flushByTagRemovesCorrectRowsFromDatabaseWithNonMysql()
485 {
486 $subject = $this->getSubjectObject(true, false);
487 $subject->flushByTag('tagB');
488
489 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
490 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
491 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
492 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
493 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
494 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
495 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
496 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
497 }
498
499 /**
500 * @test
501 */
502 public function flushByTagsRemovesCorrectRowsFromDatabaseWithNonMysql()
503 {
504 $subject = $this->getSubjectObject(true, false);
505 $subject->flushByTags(['tagC', 'tagD']);
506
507 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
508 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
509 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
510 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
511 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
512 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
513 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
514 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
515 }
516
517 /**
518 * @test
519 *
520 * @group mysql
521 */
522 public function collectGarbageWorksWithEmptyTableWithMysql()
523 {
524 $subject = $this->getSubjectObject(true);
525 $subject->collectGarbage();
526 }
527
528 /**
529 * @test
530 *
531 * @group mysql
532 */
533 public function collectGarbageRemovesCacheEntryWithExpiredLifetimeWithMysql()
534 {
535 $frontendProphecy = $this->prophesize(FrontendInterface::class);
536 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
537
538 // Must be mocked here to test for "mysql" version implementation
539 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
540 ->setMethods(['isConnectionMysql'])
541 ->setConstructorArgs(['Testing'])
542 ->getMock();
543 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(true);
544 $subject->setCache($frontendProphecy->reveal());
545
546 // idA should be expired after EXEC_TIME manipulation, idB should stay
547 $subject->set('idA', 'dataA', [], 60);
548 $subject->set('idB', 'dataB', [], 240);
549
550 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
551
552 $subject->collectGarbage();
553
554 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
555 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
556 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
557 }
558
559 /**
560 * @test
561 *
562 * @group mysql
563 */
564 public function collectGarbageRemovesTagEntriesForCacheEntriesWithExpiredLifetimeWithMysql()
565 {
566 $frontendProphecy = $this->prophesize(FrontendInterface::class);
567 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
568
569 // Must be mocked here to test for "mysql" version implementation
570 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
571 ->setMethods(['isConnectionMysql'])
572 ->setConstructorArgs(['Testing'])
573 ->getMock();
574 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(true);
575 $subject->setCache($frontendProphecy->reveal());
576
577 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
578 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
579 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
580
581 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
582
583 $subject->collectGarbage();
584
585 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
586 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
587 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
588 }
589
590 /**
591 * @test
592 *
593 * @group mysql
594 */
595 public function collectGarbageRemovesOrphanedTagEntriesFromTagsTableWithMysql()
596 {
597 $frontendProphecy = $this->prophesize(FrontendInterface::class);
598 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
599
600 // Must be mocked here to test for "mysql" version implementation
601 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
602 ->setMethods(['isConnectionMysql'])
603 ->setConstructorArgs(['Testing'])
604 ->getMock();
605 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(true);
606 $subject->setCache($frontendProphecy->reveal());
607
608 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
609 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
610 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
611
612 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
613
614 // Push two orphaned tag row into db - tags that have no related cache record anymore for whatever reason
615 $tagsTableConnection->insert(
616 'cf_cache_pages_tags',
617 [
618 'identifier' => 'idC',
619 'tag' => 'tagC'
620 ]
621 );
622 $tagsTableConnection->insert(
623 'cf_cache_pages_tags',
624 [
625 'identifier' => 'idC',
626 'tag' => 'tagD'
627 ]
628 );
629
630 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
631
632 $subject->collectGarbage();
633
634 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
635 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
636 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
637 }
638
639 /**
640 * @test
641 */
642 public function collectGarbageWorksWithEmptyTableWithNonMysql()
643 {
644 $frontendProphecy = $this->prophesize(FrontendInterface::class);
645 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
646
647 // Must be mocked here to test for "mysql" version implementation
648 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
649 ->setMethods(['isConnectionMysql'])
650 ->setConstructorArgs(['Testing'])
651 ->getMock();
652 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
653 $subject->setCache($frontendProphecy->reveal());
654
655 $subject->collectGarbage();
656 }
657
658 /**
659 * @test
660 */
661 public function collectGarbageRemovesCacheEntryWithExpiredLifetimeWithNonMysql()
662 {
663 $frontendProphecy = $this->prophesize(FrontendInterface::class);
664 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
665
666 // Must be mocked here to test for "mysql" version implementation
667 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
668 ->setMethods(['isConnectionMysql'])
669 ->setConstructorArgs(['Testing'])
670 ->getMock();
671 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
672 $subject->setCache($frontendProphecy->reveal());
673
674 // idA should be expired after EXEC_TIME manipulation, idB should stay
675 $subject->set('idA', 'dataA', [], 60);
676 $subject->set('idB', 'dataB', [], 240);
677
678 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
679
680 $subject->collectGarbage();
681
682 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
683 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
684 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
685 }
686
687 /**
688 * @test
689 */
690 public function collectGarbageRemovesTagEntriesForCacheEntriesWithExpiredLifetimeWithNonMysql()
691 {
692 $frontendProphecy = $this->prophesize(FrontendInterface::class);
693 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
694
695 // Must be mocked here to test for "mysql" version implementation
696 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
697 ->setMethods(['isConnectionMysql'])
698 ->setConstructorArgs(['Testing'])
699 ->getMock();
700 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
701 $subject->setCache($frontendProphecy->reveal());
702
703 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
704 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
705 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
706
707 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
708
709 $subject->collectGarbage();
710
711 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
712 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
713 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
714 }
715
716 /**
717 * @test
718 */
719 public function collectGarbageRemovesOrphanedTagEntriesFromTagsTableWithNonMysql()
720 {
721 $frontendProphecy = $this->prophesize(FrontendInterface::class);
722 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
723
724 // Must be mocked here to test for "mysql" version implementation
725 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
726 ->setMethods(['isConnectionMysql'])
727 ->setConstructorArgs(['Testing'])
728 ->getMock();
729 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
730 $subject->setCache($frontendProphecy->reveal());
731
732 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
733 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
734 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
735
736 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
737
738 // Push two orphaned tag row into db - tags that have no related cache record anymore for whatever reason
739 $tagsTableConnection->insert(
740 'cf_cache_pages_tags',
741 [
742 'identifier' => 'idC',
743 'tag' => 'tagC'
744 ]
745 );
746 $tagsTableConnection->insert(
747 'cf_cache_pages_tags',
748 [
749 'identifier' => 'idC',
750 'tag' => 'tagD'
751 ]
752 );
753
754 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
755
756 $subject->collectGarbage();
757
758 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
759 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
760 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
761 }
762
763 /**
764 * @test
765 */
766 public function flushLeavesCacheAndTagsTableEmpty()
767 {
768 $frontendProphecy = $this->prophesize(FrontendInterface::class);
769 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
770
771 $subject = new Typo3DatabaseBackend('Testing');
772 $subject->setCache($frontendProphecy->reveal());
773
774 $subject->set('idA', 'dataA', ['tagA', 'tagB']);
775
776 $subject->flush();
777
778 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
779 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
780 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', []));
781 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', []));
782 }
783
784 /**
785 * @param bool $returnMockObject
786 * @param bool $isConnectionMysql
787 *
788 * @return Typo3DatabaseBackend
789 */
790 protected function getSubjectObject($returnMockObject = false, $isConnectionMysql = true)
791 {
792 $frontendProphecy = $this->prophesize(FrontendInterface::class);
793 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
794
795 if (!$returnMockObject) {
796 $subject = new Typo3DatabaseBackend('Testing');
797 } else {
798 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
799 ->setMethods(['isConnectionMysql'])
800 ->setConstructorArgs(['Testing'])
801 ->getMock();
802 $subject->expects($this->once())->method('isConnectionMysql')->willReturn($isConnectionMysql);
803 }
804 $subject->setCache($frontendProphecy->reveal());
805
806 $subject->set('idA', 'dataA', ['tagA', 'tagB']);
807 $subject->set('idB', 'dataB', ['tagB', 'tagC']);
808 $subject->set('idC', 'dataC', ['tagC', 'tagD']);
809
810 return $subject;
811 }
812 }