DatabaseCacheIntegrationTest::testSet()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
rs 10
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Kodus\Cache\Tests;
6
7
use PDO;
8
use PDOException;
9
use PHPUnit\Framework\TestCase;
10
use Psr\SimpleCache\InvalidArgumentException;
11
use TypeError;
12
13
abstract class DatabaseCacheIntegrationTest extends TestCase
14
{
15
    const TABLE_NAME  = "test_cache";
16
    const DEFAULT_TTL = 86400;
17
18
    /**
19
     * @var TestableDatabaseCache
20
     */
21
    protected $cache;
22
23
    /**
24
     * @var PDO
25
     */
26
    private static $pdo;
27
28
    public static function setUpBeforeClass(): void
29
    {
30
        $name = static::getConnectionName();
31
32
        self::$pdo = new PDO(
33
            constant("DB_CACHE_TEST_{$name}_DSN"),
34
            constant("DB_CACHE_TEST_{$name}_USERNAME"),
35
            constant("DB_CACHE_TEST_{$name}_PASSWORD")
36
        );
37
38
        try {
39
            self::$pdo->exec("DROP TABLE " . self::TABLE_NAME);
40
        } catch (PDOException $error) {
41
            // ignored.
42
        }
43
    }
44
45
    abstract protected static function getConnectionName(): string;
46
47
    /**
48
     * Data provider for invalid cache keys.
49
     *
50
     * @return array
51
     */
52
    public static function invalidStringKeys()
53
    {
54
        return [
55
            [''],
56
            ['{str'],
57
            ['rand{'],
58
            ['rand{str'],
59
            ['rand}str'],
60
            ['rand(str'],
61
            ['rand)str'],
62
            ['rand/str'],
63
            ['rand\\str'],
64
            ['rand@str'],
65
            ['rand:str'],
66
        ];
67
    }
68
69
    /**
70
     * Data provider for invalid cache keys.
71
     *
72
     * @return array
73
     */
74
    public static function invalidNonStringKeys()
75
    {
76
        return [
77
            [true],
78
            [false],
79
            [null],
80
            [2],
81
            [2.5],
82
            [new \stdClass()],
83
            [['array']],
84
        ];
85
    }
86
87
    /**
88
     * Data provider for valid keys.
89
     *
90
     * @return array
91
     */
92
    public static function validKeys()
93
    {
94
        return [
95
            ['AbC19_.'],
96
            ['1234567890123456789012345678901234567890123456789012345678901234'],
97
        ];
98
    }
99
100
    /**
101
     * Data provider for invalid array keys.
102
     *
103
     * @return array
104
     */
105
    public static function invalidArrayKeys()
106
    {
107
        return [
108
            [''],
109
            [true],
110
            [false],
111
            [null],
112
            [2.5],
113
            ['{str'],
114
            ['rand{'],
115
            ['rand{str'],
116
            ['rand}str'],
117
            ['rand(str'],
118
            ['rand)str'],
119
            ['rand/str'],
120
            ['rand\\str'],
121
            ['rand@str'],
122
            ['rand:str'],
123
            [new \stdClass()],
124
            [['array']],
125
        ];
126
    }
127
128
    /**
129
     * Data provider for valid data to store.
130
     *
131
     * @return array
132
     */
133
    public static function validData()
134
    {
135
        return [
136
            ['AbC19_.'],
137
            [4711],
138
            [47.11],
139
            [true],
140
            [null],
141
            [['key' => 'value']],
142
            [new \stdClass()],
143
        ];
144
    }
145
146
    /**
147
     * @return array
148
     */
149
    public static function invalidTtl()
150
    {
151
        return [
152
            [''],
153
            [true],
154
            [false],
155
            ['abc'],
156
            [2.5],
157
            [' 1'], // can be casted to a int
158
            ['12foo'], // can be casted to a int
159
            ['025'], // can be interpreted as hex
160
            [new \stdClass()],
161
            [['array']],
162
        ];
163
    }
164
165
    public function testSet()
166
    {
167
        $result = $this->cache->set('key', 'value');
168
        $this->assertTrue($result, 'set() must return true if success');
169
        $this->assertEquals('value', $this->cache->get('key'));
170
    }
171
172
    public function testSetMultipleNoIterable()
173
    {
174
        $this->expectException(TypeError::class);
175
176
        $this->cache->setMultiple('key');
0 ignored issues
show
Bug introduced by
'key' of type string is incompatible with the type iterable expected by parameter $values of Kodus\Cache\DatabaseCache::setMultiple(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

176
        $this->cache->setMultiple(/** @scrutinizer ignore-type */ 'key');
Loading history...
177
    }
178
179
    public function testObjectAsDefaultValue()
180
    {
181
        $obj = new \stdClass();
182
        $obj->foo = 'value';
183
        $this->assertEquals($obj, $this->cache->get('key', $obj));
184
    }
185
186
    public function testGet()
187
    {
188
        $this->assertNull($this->cache->get('key'));
189
        $this->assertEquals('foo', $this->cache->get('key', 'foo'));
190
191
        $this->cache->set('key', 'value');
192
        $this->assertEquals('value', $this->cache->get('key', 'foo'));
193
    }
194
195
    /**
196
     * @dataProvider validData
197
     */
198
    public function testSetValidData($data)
199
    {
200
        $this->cache->set('key', $data);
201
        $this->assertEquals($data, $this->cache->get('key'));
202
    }
203
204
    public function testSetTtl()
205
    {
206
        $result = $this->cache->set('key1', 'value', 1);
207
        $this->assertTrue($result, 'set() must return true if success');
208
        $this->assertEquals('value', $this->cache->get('key1'));
209
        $this->sleep(2);
210
        $this->assertNull($this->cache->get('key1'), 'Value must expire after ttl.');
211
212
        $this->cache->set('key2', 'value', new \DateInterval('PT1S'));
213
        $this->assertEquals('value', $this->cache->get('key2'));
214
        $this->sleep(2);
215
        $this->assertNull($this->cache->get('key2'), 'Value must expire after ttl.');
216
    }
217
218
    public function testDeleteMultiple()
219
    {
220
        $this->assertTrue($this->cache->deleteMultiple([]), 'Deleting a empty array should return true');
221
        $this->assertTrue($this->cache->deleteMultiple(['key']),
222
            'Deleting a value that does not exist should return true');
223
224
        $this->cache->set('key0', 'value0');
225
        $this->cache->set('key1', 'value1');
226
        $this->assertTrue($this->cache->deleteMultiple(['key0', 'key1']), 'Delete must return true on success');
227
        $this->assertNull($this->cache->get('key0'), 'Values must be deleted on deleteMultiple()');
228
        $this->assertNull($this->cache->get('key1'), 'Values must be deleted on deleteMultiple()');
229
    }
230
231
    /**
232
     * @dataProvider invalidTtl
233
     */
234
    public function testSetMultipleInvalidTtl($ttl)
235
    {
236
        $this->expectException(TypeError::class);
237
238
        $this->cache->setMultiple(['key' => 'value'], $ttl);
239
    }
240
241
    public function testSetExpiredTtl()
242
    {
243
        $this->cache->set('key0', 'value');
244
        $this->cache->set('key0', 'value', 0);
245
        $this->assertNull($this->cache->get('key0'));
246
        $this->assertFalse($this->cache->has('key0'));
247
248
        $this->cache->set('key1', 'value', -1);
249
        $this->assertNull($this->cache->get('key1'));
250
        $this->assertFalse($this->cache->has('key1'));
251
    }
252
253
    public function testNullOverwrite()
254
    {
255
        $this->cache->set('key', 5);
256
        $this->cache->set('key', null);
257
258
        $this->assertNull($this->cache->get('key'), 'Setting null to a key must overwrite previous value');
259
    }
260
261
    public function testDataTypeObject()
262
    {
263
        $object = new \stdClass();
264
        $object->a = 'foo';
265
        $this->cache->set('key', $object);
266
        $result = $this->cache->get('key');
267
        $this->assertTrue(is_object($result), 'Wrong data type. If we store object we must get an object back.');
268
        $this->assertEquals($object, $result);
269
    }
270
271
    public function testBinaryData()
272
    {
273
        $data = '';
274
        for ($i = 0; $i < 256; $i++) {
275
            $data .= chr($i);
276
        }
277
278
        $this->cache->set('key', $data);
279
        $result = $this->cache->get('key');
280
        $this->assertTrue($data === $result, 'Binary data must survive a round trip.');
281
    }
282
283
    public function testObjectDoesNotChangeInCache()
284
    {
285
        $obj = new \stdClass();
286
        $obj->foo = 'value';
287
        $this->cache->set('key', $obj);
288
        $obj->foo = 'changed';
289
290
        $cacheObject = $this->cache->get('key');
291
        $this->assertEquals('value', $cacheObject->foo, 'Object in cache should not have their values changed.');
292
    }
293
294
    public function testSetMultipleWithIntegerArrayKey()
295
    {
296
        $result = $this->cache->setMultiple(['0' => 'value0']);
297
        $this->assertTrue($result, 'setMultiple() must return true if success');
298
        $this->assertEquals('value0', $this->cache->get('0'));
299
    }
300
301
    public function testSetMultiple()
302
    {
303
        $result = $this->cache->setMultiple(['key0' => 'value0', 'key1' => 'value1']);
304
        $this->assertTrue($result, 'setMultiple() must return true if success');
305
        $this->assertEquals('value0', $this->cache->get('key0'));
306
        $this->assertEquals('value1', $this->cache->get('key1'));
307
    }
308
309
    public function testDataTypeInteger()
310
    {
311
        $this->cache->set('key', 5);
312
        $result = $this->cache->get('key');
313
        $this->assertTrue(5 === $result, 'Wrong data type. If we store an int we must get an int back.');
314
        $this->assertTrue(is_int($result), 'Wrong data type. If we store an int we must get an int back.');
315
    }
316
317
    public function testDelete()
318
    {
319
        $this->assertTrue($this->cache->delete('key'), 'Deleting a value that does not exist should return true');
320
        $this->cache->set('key', 'value');
321
        $this->assertTrue($this->cache->delete('key'), 'Delete must return true on success');
322
        $this->assertNull($this->cache->get('key'), 'Values must be deleted on delete()');
323
    }
324
325
    public function testDeleteMultipleGenerator()
326
    {
327
        $gen = function () {
328
            yield 1 => 'key0';
329
            yield 1 => 'key1';
330
        };
331
        $this->cache->set('key0', 'value0');
332
        $this->assertTrue($this->cache->deleteMultiple($gen()), 'Deleting a generator should return true');
333
334
        $this->assertNull($this->cache->get('key0'), 'Values must be deleted on deleteMultiple()');
335
        $this->assertNull($this->cache->get('key1'), 'Values must be deleted on deleteMultiple()');
336
    }
337
338
    /**
339
     * @dataProvider invalidNonStringKeys
340
     */
341
    public function testHasInvalidNonStringKeys($key)
342
    {
343
        $this->expectException(TypeError::class);
344
        $this->cache->has($key);
345
    }
346
347
    /**
348
     * @dataProvider invalidStringKeys
349
     */
350
    public function testHasInvalidStringKeys($key)
351
    {
352
        $this->expectException(InvalidArgumentException::class);
353
        $this->cache->has($key);
354
    }
355
356
    public function testDataTypeArray()
357
    {
358
        $array = ['a' => 'foo', 2 => 'bar'];
359
        $this->cache->set('key', $array);
360
        $result = $this->cache->get('key');
361
        $this->assertTrue(is_array($result), 'Wrong data type. If we store array we must get an array back.');
362
        $this->assertEquals($array, $result);
363
    }
364
365
    /**
366
     * @dataProvider invalidStringKeys
367
     */
368
    public function testGetInvalidKeys($key)
369
    {
370
        $this->expectException(InvalidArgumentException::class);
371
        $this->cache->get($key);
372
    }
373
374
    public function testSetMultipleWithGenerator()
375
    {
376
        $gen = function () {
377
            yield 'key0' => 'value0';
378
            yield 'key1' => 'value1';
379
        };
380
381
        $this->cache->setMultiple($gen());
382
        $this->assertEquals('value0', $this->cache->get('key0'));
383
        $this->assertEquals('value1', $this->cache->get('key1'));
384
    }
385
386
    public function testHas()
387
    {
388
        $this->assertFalse($this->cache->has('key0'));
389
        $this->cache->set('key0', 'value0');
390
        $this->assertTrue($this->cache->has('key0'));
391
    }
392
393
    /**
394
     * @dataProvider invalidStringKeys
395
     */
396
    public function testDeleteMultipleInvalidKeys($key)
397
    {
398
        $this->expectException(InvalidArgumentException::class);
399
        $this->cache->deleteMultiple(['key1', $key, 'key2']);
400
    }
401
402
    /**
403
     * @dataProvider invalidArrayKeys
404
     */
405
    public function testSetMultipleInvalidKeys($key)
406
    {
407
        $this->expectException(InvalidArgumentException::class);
408
409
        $values = function () use ($key) {
410
            yield 'key1' => 'foo';
411
            yield $key => 'bar';
412
            yield 'key2' => 'baz';
413
        };
414
415
        $this->cache->setMultiple($values());
416
    }
417
418
    /**
419
     * @dataProvider invalidStringKeys
420
     */
421
    public function testDeleteInvalidKeys($key)
422
    {
423
        $this->expectException(InvalidArgumentException::class);
424
425
        $this->cache->delete($key);
426
    }
427
428
    public function testGetMultiple()
429
    {
430
        $result = $this->cache->getMultiple(['key0', 'key1']);
431
        $keys = [];
432
        foreach ($result as $i => $r) {
433
            $keys[] = $i;
434
            $this->assertNull($r);
435
        }
436
        sort($keys);
437
        $this->assertSame(['key0', 'key1'], $keys);
438
439
        $this->cache->set('key3', 'value');
440
        $result = $this->cache->getMultiple(['key2', 'key3', 'key4'], 'foo');
441
        $keys = [];
442
        foreach ($result as $key => $r) {
443
            $keys[] = $key;
444
            if ($key === 'key3') {
445
                $this->assertEquals('value', $r);
446
            } else {
447
                $this->assertEquals('foo', $r);
448
            }
449
        }
450
        sort($keys);
451
        $this->assertSame(['key2', 'key3', 'key4'], $keys);
452
    }
453
454
    public function testGetMultipleWithGenerator()
455
    {
456
        $gen = function () {
457
            yield 1 => 'key0';
458
            yield 1 => 'key1';
459
        };
460
461
        $this->cache->set('key0', 'value0');
462
        $result = $this->cache->getMultiple($gen());
463
        $keys = [];
464
        foreach ($result as $key => $r) {
465
            $keys[] = $key;
466
            if ($key === 'key0') {
467
                $this->assertEquals('value0', $r);
468
            } elseif ($key === 'key1') {
469
                $this->assertNull($r);
470
            } else {
471
                $this->assertFalse(true, 'This should not happend');
472
            }
473
        }
474
        sort($keys);
475
        $this->assertSame(['key0', 'key1'], $keys);
476
        $this->assertEquals('value0', $this->cache->get('key0'));
477
        $this->assertNull($this->cache->get('key1'));
478
    }
479
480
    public function testBasicUsageWithLongKey()
481
    {
482
        $key = str_repeat('a', 300);
483
484
        $this->assertFalse($this->cache->has($key));
485
        $this->assertTrue($this->cache->set($key, 'value'));
486
487
        $this->assertTrue($this->cache->has($key));
488
        $this->assertSame('value', $this->cache->get($key));
489
490
        $this->assertTrue($this->cache->delete($key));
491
492
        $this->assertFalse($this->cache->has($key));
493
    }
494
495
    public function testDataTypeFloat()
496
    {
497
        $float = 1.23456789;
498
        $this->cache->set('key', $float);
499
        $result = $this->cache->get('key');
500
        $this->assertTrue(is_float($result), 'Wrong data type. If we store float we must get an float back.');
501
        $this->assertEquals($float, $result);
502
    }
503
504
    /**
505
     * @dataProvider invalidStringKeys
506
     */
507
    public function testSetInvalidKeys($key)
508
    {
509
        $this->expectException(InvalidArgumentException::class);
510
        $this->cache->set($key, 'foobar');
511
    }
512
513
    public function testDataTypeString()
514
    {
515
        $this->cache->set('key', '5');
516
        $result = $this->cache->get('key');
517
        $this->assertTrue('5' === $result, 'Wrong data type. If we store a string we must get an string back.');
518
        $this->assertTrue(is_string($result), 'Wrong data type. If we store a string we must get an string back.');
519
    }
520
521
    public function testSetMultipleTtl()
522
    {
523
        $this->cache->setMultiple(['key2' => 'value2', 'key3' => 'value3'], 1);
524
        $this->assertEquals('value2', $this->cache->get('key2'));
525
        $this->assertEquals('value3', $this->cache->get('key3'));
526
        $this->sleep(2);
527
        $this->assertNull($this->cache->get('key2'), 'Value must expire after ttl.');
528
        $this->assertNull($this->cache->get('key3'), 'Value must expire after ttl.');
529
530
        $this->cache->setMultiple(['key4' => 'value4'], new \DateInterval('PT1S'));
531
        $this->assertEquals('value4', $this->cache->get('key4'));
532
        $this->sleep(2);
533
        $this->assertNull($this->cache->get('key4'), 'Value must expire after ttl.');
534
    }
535
536
    protected function sleep(int $time)
537
    {
538
        $this->cache->timeTravel($time);
539
    }
540
541
    public function testCleanExpired()
542
    {
543
        $this->cache->set('key0', 'value', 5);
544
        $this->cache->set('key1', 'value', 10);
545
546
        $this->cache->timeTravel(5);
547
        $this->cache->cleanExpired();
548
549
        $this->assertFalse($this->cache->has('key0'), "key0 expires after 5 seconds");
550
        $this->assertTrue($this->cache->has('key1'), "key1 has not expired");
551
552
        $this->cache->timeTravel(5);
553
        $this->cache->cleanExpired();
554
555
        $this->assertFalse($this->cache->has('key1'), "key1 expires after 10 seconds");
556
    }
557
558
    public function testGetMultipleNoIterable()
559
    {
560
        $this->expectException(TypeError::class);
561
562
        $this->cache->getMultiple('key');
0 ignored issues
show
Bug introduced by
'key' of type string is incompatible with the type iterable expected by parameter $keys of Kodus\Cache\DatabaseCache::getMultiple(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

562
        $this->cache->getMultiple(/** @scrutinizer ignore-type */ 'key');
Loading history...
563
    }
564
565
    /**
566
     * @dataProvider validKeys
567
     */
568
    public function testSetValidKeys($key)
569
    {
570
        $this->cache->set($key, 'foobar');
571
        $this->assertEquals('foobar', $this->cache->get($key));
572
    }
573
574
    public function testClear()
575
    {
576
        $this->assertTrue($this->cache->clear(), 'Clearing an empty cache should return true');
577
        $this->cache->set('key', 'value');
578
        $this->assertTrue($this->cache->clear(), 'Delete must return true on success');
579
        $this->assertNull($this->cache->get('key'), 'Values must be deleted on clear()');
580
    }
581
582
    /**
583
     * @dataProvider invalidStringKeys
584
     */
585
    public function testGetMultipleInvalidKeys($key)
586
    {
587
        $this->expectException(InvalidArgumentException::class);
588
589
        $this->cache->getMultiple(['key1', $key, 'key2']);
590
    }
591
592
    public function testSetMultipleExpiredTtl()
593
    {
594
        $this->cache->setMultiple(['key0' => 'value0', 'key1' => 'value1'], 0);
595
        $this->assertNull($this->cache->get('key0'));
596
        $this->assertNull($this->cache->get('key1'));
597
    }
598
599
    /**
600
     * @dataProvider validKeys
601
     */
602
    public function testSetMultipleValidKeys($key)
603
    {
604
        $this->cache->setMultiple([$key => 'foobar']);
605
        $result = $this->cache->getMultiple([$key]);
606
        $keys = [];
607
        foreach ($result as $i => $r) {
608
            $keys[] = $i;
609
            $this->assertEquals($key, $i);
610
            $this->assertEquals('foobar', $r);
611
        }
612
        $this->assertSame([$key], $keys);
613
    }
614
615
    public function testDeleteMultipleNoIterable()
616
    {
617
        $this->expectException(TypeError::class);
618
619
        $this->cache->deleteMultiple('key');
0 ignored issues
show
Bug introduced by
'key' of type string is incompatible with the type iterable expected by parameter $keys of Kodus\Cache\DatabaseCache::deleteMultiple(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

619
        $this->cache->deleteMultiple(/** @scrutinizer ignore-type */ 'key');
Loading history...
620
    }
621
622
    public function testDataTypeBoolean()
623
    {
624
        $this->cache->set('key', false);
625
        $result = $this->cache->get('key');
626
        $this->assertTrue(is_bool($result), 'Wrong data type. If we store boolean we must get an boolean back.');
627
        $this->assertFalse($result);
628
        $this->assertTrue($this->cache->has('key'), 'has() should return true when true are stored. ');
629
    }
630
631
    /**
632
     * @dataProvider invalidTtl
633
     */
634
    public function testSetInvalidTtl($ttl)
635
    {
636
        $this->expectException(TypeError::class);
637
638
        $this->cache->set('key', 'value', $ttl);
639
    }
640
641
    /**
642
     * @dataProvider validData
643
     */
644
    public function testSetMultipleValidData($data)
645
    {
646
        $this->cache->setMultiple(['key' => $data]);
647
        $result = $this->cache->getMultiple(['key']);
648
        $keys = [];
649
        foreach ($result as $i => $r) {
650
            $keys[] = $i;
651
            $this->assertEquals($data, $r);
652
        }
653
        $this->assertSame(['key'], $keys);
654
    }
655
656
    protected function setUp(): void
657
    {
658
        $this->cache = new TestableDatabaseCache(
659
            self::$pdo,
660
            self::TABLE_NAME,
661
            self::DEFAULT_TTL
662
        );
663
    }
664
665
    protected function tearDown(): void
666
    {
667
        if ($this->cache !== null) {
668
            $this->cache->clear();
669
        }
670
    }
671
}
672