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