[BUGFIX] Add missing namespace parts
[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\Components\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->assertSame(['idA' => 'idA'], $subject->findIdentifiersByTag('tagA'));
397 $this->assertSame(['idA' => 'idA', 'idB' => 'idB'], $subject->findIdentifiersByTag('tagB'));
398 $this->assertSame(['idB' => 'idB', 'idC' => 'idC'], $subject->findIdentifiersByTag('tagC'));
399 }
400
401 /**
402 * @test
403 */
404 public function flushByTagWorksWithEmptyCacheTablesWithMysql()
405 {
406 $subject = $this->getSubjectObject(true);
407 $subject->flushByTag('tagB');
408 }
409
410 /**
411 * @test
412 */
413 public function flushByTagsWorksWithEmptyCacheTablesWithMysql()
414 {
415 $subject = $this->getSubjectObject(true);
416 $subject->flushByTags(['tagB']);
417 }
418
419 /**
420 * @test
421 */
422 public function flushByTagRemovesCorrectRowsFromDatabaseWithMysql()
423 {
424 $subject = $this->getSubjectObject(true);
425 $subject->flushByTag('tagB');
426
427 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
428 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
429 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
430 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
431 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
432 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
433 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
434 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
435 }
436
437 /**
438 * @test
439 */
440 public function flushByTagsRemovesCorrectRowsFromDatabaseWithMysql()
441 {
442 $subject = $this->getSubjectObject(true);
443 $subject->flushByTags(['tagC', 'tagD']);
444
445 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
446 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
447 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
448 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
449 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
450 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
451 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
452 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
453 }
454
455 /**
456 * @test
457 */
458 public function flushByTagWorksWithEmptyCacheTablesWithNonMysql()
459 {
460 $subject = $this->getSubjectObject(true, false);
461 $subject->flushByTag('tagB');
462 }
463
464 /**
465 * @test
466 */
467 public function flushByTagsWorksWithEmptyCacheTablesWithNonMysql()
468 {
469 $subject = $this->getSubjectObject(true, false);
470 $subject->flushByTags(['tagB', 'tagC']);
471 }
472
473 /**
474 * @test
475 */
476 public function flushByTagRemovesCorrectRowsFromDatabaseWithNonMysql()
477 {
478 $subject = $this->getSubjectObject(true, false);
479 $subject->flushByTag('tagB');
480
481 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
482 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
483 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
484 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
485 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
486 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
487 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
488 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
489 }
490
491 /**
492 * @test
493 */
494 public function flushByTagsRemovesCorrectRowsFromDatabaseWithNonMysql()
495 {
496 $subject = $this->getSubjectObject(true, false);
497 $subject->flushByTags(['tagC', 'tagD']);
498
499 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
500 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
501 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
502 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idC']));
503 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
504 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
505 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
506 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
507 }
508
509 /**
510 * @test
511 */
512 public function collectGarbageWorksWithEmptyTableWithMysql()
513 {
514 $subject = $this->getSubjectObject(true);
515 $subject->collectGarbage();
516 }
517
518 /**
519 * @test
520 */
521 public function collectGarbageRemovesCacheEntryWithExpiredLifetimeWithMysql()
522 {
523 $frontendProphecy = $this->prophesize(FrontendInterface::class);
524 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
525
526 // Must be mocked here to test for "mysql" version implementation
527 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
528 ->setMethods(['isConnectionMysql'])
529 ->setConstructorArgs(['Testing'])
530 ->getMock();
531 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(true);
532 $subject->setCache($frontendProphecy->reveal());
533
534 // idA should be expired after EXEC_TIME manipulation, idB should stay
535 $subject->set('idA', 'dataA', [], 60);
536 $subject->set('idB', 'dataB', [], 240);
537
538 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
539
540 $subject->collectGarbage();
541
542 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
543 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
544 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
545 }
546
547 /**
548 * @test
549 */
550 public function collectGarbageRemovesTagEntriesForCacheEntriesWithExpiredLifetimeWithMysql()
551 {
552 $frontendProphecy = $this->prophesize(FrontendInterface::class);
553 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
554
555 // Must be mocked here to test for "mysql" version implementation
556 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
557 ->setMethods(['isConnectionMysql'])
558 ->setConstructorArgs(['Testing'])
559 ->getMock();
560 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(true);
561 $subject->setCache($frontendProphecy->reveal());
562
563 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
564 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
565 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
566
567 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
568
569 $subject->collectGarbage();
570
571 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
572 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
573 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
574 }
575
576 /**
577 * @test
578 */
579 public function collectGarbageRemovesOrphanedTagEntriesFromTagsTableWithMysql()
580 {
581 $frontendProphecy = $this->prophesize(FrontendInterface::class);
582 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
583
584 // Must be mocked here to test for "mysql" version implementation
585 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
586 ->setMethods(['isConnectionMysql'])
587 ->setConstructorArgs(['Testing'])
588 ->getMock();
589 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(true);
590 $subject->setCache($frontendProphecy->reveal());
591
592 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
593 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
594 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
595
596 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
597
598 // Push two orphaned tag row into db - tags that have no related cache record anymore for whatever reason
599 $tagsTableConnection->insert(
600 'cf_cache_pages_tags',
601 [
602 'identifier' => 'idC',
603 'tag' => 'tagC'
604 ]
605 );
606 $tagsTableConnection->insert(
607 'cf_cache_pages_tags',
608 [
609 'identifier' => 'idC',
610 'tag' => 'tagD'
611 ]
612 );
613
614 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
615
616 $subject->collectGarbage();
617
618 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
619 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
620 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
621 }
622
623 /**
624 * @test
625 */
626 public function collectGarbageWorksWithEmptyTableWithNonMysql()
627 {
628 $frontendProphecy = $this->prophesize(FrontendInterface::class);
629 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
630
631 // Must be mocked here to test for "mysql" version implementation
632 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
633 ->setMethods(['isConnectionMysql'])
634 ->setConstructorArgs(['Testing'])
635 ->getMock();
636 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
637 $subject->setCache($frontendProphecy->reveal());
638
639 $subject->collectGarbage();
640 }
641
642 /**
643 * @test
644 */
645 public function collectGarbageRemovesCacheEntryWithExpiredLifetimeWithNonMysql()
646 {
647 $frontendProphecy = $this->prophesize(FrontendInterface::class);
648 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
649
650 // Must be mocked here to test for "mysql" version implementation
651 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
652 ->setMethods(['isConnectionMysql'])
653 ->setConstructorArgs(['Testing'])
654 ->getMock();
655 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
656 $subject->setCache($frontendProphecy->reveal());
657
658 // idA should be expired after EXEC_TIME manipulation, idB should stay
659 $subject->set('idA', 'dataA', [], 60);
660 $subject->set('idB', 'dataB', [], 240);
661
662 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
663
664 $subject->collectGarbage();
665
666 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
667 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idA']));
668 $this->assertSame(1, $cacheTableConnection->count('*', 'cf_cache_pages', ['identifier' => 'idB']));
669 }
670
671 /**
672 * @test
673 */
674 public function collectGarbageRemovesTagEntriesForCacheEntriesWithExpiredLifetimeWithNonMysql()
675 {
676 $frontendProphecy = $this->prophesize(FrontendInterface::class);
677 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
678
679 // Must be mocked here to test for "mysql" version implementation
680 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
681 ->setMethods(['isConnectionMysql'])
682 ->setConstructorArgs(['Testing'])
683 ->getMock();
684 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
685 $subject->setCache($frontendProphecy->reveal());
686
687 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
688 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
689 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
690
691 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
692
693 $subject->collectGarbage();
694
695 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
696 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
697 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
698 }
699
700 /**
701 * @test
702 */
703 public function collectGarbageRemovesOrphanedTagEntriesFromTagsTableWithNonMysql()
704 {
705 $frontendProphecy = $this->prophesize(FrontendInterface::class);
706 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
707
708 // Must be mocked here to test for "mysql" version implementation
709 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
710 ->setMethods(['isConnectionMysql'])
711 ->setConstructorArgs(['Testing'])
712 ->getMock();
713 $subject->expects($this->once())->method('isConnectionMysql')->willReturn(false);
714 $subject->setCache($frontendProphecy->reveal());
715
716 // tag rows tagA and tagB should be removed by garbage collector after EXEC_TIME manipulation
717 $subject->set('idA', 'dataA', ['tagA', 'tagB'], 60);
718 $subject->set('idB', 'dataB', ['tagB', 'tagC'], 240);
719
720 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
721
722 // Push two orphaned tag row into db - tags that have no related cache record anymore for whatever reason
723 $tagsTableConnection->insert(
724 'cf_cache_pages_tags',
725 [
726 'identifier' => 'idC',
727 'tag' => 'tagC'
728 ]
729 );
730 $tagsTableConnection->insert(
731 'cf_cache_pages_tags',
732 [
733 'identifier' => 'idC',
734 'tag' => 'tagD'
735 ]
736 );
737
738 $GLOBALS['EXEC_TIME'] = $GLOBALS['EXEC_TIME'] + 120;
739
740 $subject->collectGarbage();
741
742 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idA']));
743 $this->assertSame(2, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idB']));
744 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', ['identifier' => 'idC']));
745 }
746
747 /**
748 * @test
749 */
750 public function flushLeavesCacheAndTagsTableEmpty()
751 {
752 $frontendProphecy = $this->prophesize(FrontendInterface::class);
753 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
754
755 $subject = new Typo3DatabaseBackend('Testing');
756 $subject->setCache($frontendProphecy->reveal());
757
758 $subject->set('idA', 'dataA', ['tagA', 'tagB']);
759
760 $subject->flush();
761
762 $cacheTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages');
763 $tagsTableConnection = (new ConnectionPool())->getConnectionForTable('cf_cache_pages_tags');
764 $this->assertSame(0, $cacheTableConnection->count('*', 'cf_cache_pages', []));
765 $this->assertSame(0, $tagsTableConnection->count('*', 'cf_cache_pages_tags', []));
766 }
767
768 /**
769 * @param bool $returnMockObject
770 * @param bool $isConnectionMysql
771 *
772 * @return Typo3DatabaseBackend
773 */
774 protected function getSubjectObject($returnMockObject = false, $isConnectionMysql = true)
775 {
776 $frontendProphecy = $this->prophesize(FrontendInterface::class);
777 $frontendProphecy->getIdentifier()->willReturn('cache_pages');
778
779 if (!$returnMockObject) {
780 $subject = new Typo3DatabaseBackend('Testing');
781 } else {
782 $subject = $this->getMockBuilder(Typo3DatabaseBackend::class)
783 ->setMethods(['isConnectionMysql'])
784 ->setConstructorArgs(['Testing'])
785 ->getMock();
786 $subject->expects($this->once())->method('isConnectionMysql')->willReturn($isConnectionMysql);
787 }
788 $subject->setCache($frontendProphecy->reveal());
789
790 $subject->set('idA', 'dataA', ['tagA', 'tagB']);
791 $subject->set('idB', 'dataB', ['tagB', 'tagC']);
792 $subject->set('idC', 'dataC', ['tagC', 'tagD']);
793
794 return $subject;
795 }
796 }