MongoCacheTest.php$0 ➔ serialize()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
dl 0
loc 5
rs 10
c 1
b 0
f 0
1
<?php
2
namespace SubjectivePHPTest\Psr\SimpleCache;
3
4
use DateTime;
5
use DateTimeZone;
6
use MongoDB\BSON\UTCDateTime;
7
use MongoDB\Client;
8
use MongoDB\Collection;
9
use SubjectivePHP\Psr\SimpleCache\InvalidArgumentException;
10
use SubjectivePHP\Psr\SimpleCache\MongoCache;
11
use SubjectivePHP\Psr\SimpleCache\Serializer\SerializerInterface;
12
13
/**
14
 * Defines unit tests for the MongoCache class.
15
 *
16
 * @coversDefaultClass \SubjectivePHP\Psr\SimpleCache\MongoCache
17
 * @covers ::__construct
18
 * @covers ::<private>
19
 * @covers ::<protected>
20
 */
21
final class MongoCacheTest extends \PHPUnit\Framework\TestCase
22
{
23
    /**
24
     * Mongo Collection to use in tests.
25
     *
26
     * @var Collection
27
     */
28
    private $collection;
29
30
    /**
31
     * Cache instance to us in tests.
32
     *
33
     * @var MongoCache
34
     */
35
    private $cache;
36
37
    /**
38
     * set up each test.
39
     *
40
     * @return void
41
     */
42
    public function setUp(): void
43
    {
44
        $this->collection = (new Client())->selectDatabase('testing')->selectCollection('cache');
45
        $this->collection->drop();
46
        $this->cache = new MongoCache($this->collection, $this->getSerializer());
47
    }
48
49
    /**
50
     * @test
51
     *
52
     * @return void
53
     */
54
    public function useWithoutSerializer()
55
    {
56
        $cache = new MongoCache($this->collection);
57
        $data = ['a', 'b', 'c'];
58
        $cache->set('foo', $data);
59
        $this->assertSame($data, $cache->get('foo'));
60
    }
61
62
    /**
63
     * Verify behavior of get() when the key is not found.
64
     *
65
     * @test
66
     * @covers ::get
67
     *
68
     * @return void
69
     */
70
    public function getNotFound()
71
    {
72
        $default = new \StdClass();
73
        $this->assertSame($default, $this->cache->get('key', $default));
74
    }
75
76
    /**
77
     * Verify basic behavior of get().
78
     *
79
     * @test
80
     * @covers ::get
81
     *
82
     * @return void
83
     */
84
    public function get()
85
    {
86
        $this->collection->insertOne(
87
            [
88
                '_id' => 'key',
89
                'data' => [
90
                    'timestamp' => 1491782286,
91
                    'timezone' => 'America/New_York',
92
                ],
93
            ]
94
        );
95
96
        $dateTime = new DateTime('@1491782286', new DateTimeZone('America/New_York'));
97
        $this->assertEquals($dateTime, $this->cache->get('key'));
98
    }
99
100
    /**
101
     * Verify basic behavior of set().
102
     *
103
     * @test
104
     * @covers ::set
105
     *
106
     * @return void
107
     */
108
    public function set()
109
    {
110
        $ttl = \DateInterval::createFromDateString('1 day');
111
        $dateTime = new DateTime('2017-04-09 20:54:04', new DateTimeZone('Pacific/Honolulu'));
112
        $this->cache->set('key', $dateTime, $ttl);
113
        $expires = new UTCDateTime((new \DateTime('now'))->add($ttl)->getTimestamp() * 1000);
114
        $this->assertDateTimeDocument('key', $expires, $dateTime);
115
    }
116
117
    /**
118
     * Verify behavior of set() with invalid $ttl value.
119
     *
120
     * @test
121
     * @covers ::set
122
     *
123
     * @return void
124
     */
125
    public function setInvalidTTL()
126
    {
127
        $this->expectException(\Psr\SimpleCache\InvalidArgumentException::class);
128
        $this->expectExceptionMessage('$ttl must be null, an integer or \DateInterval instance');
129
        $this->cache->set('key', new DateTime(), new DateTime());
0 ignored issues
show
Bug introduced by
new DateTime() of type DateTime is incompatible with the type DateInterval|integer|null expected by parameter $ttl of SubjectivePHP\Psr\SimpleCache\MongoCache::set(). ( Ignorable by Annotation )

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

129
        $this->cache->set('key', new DateTime(), /** @scrutinizer ignore-type */ new DateTime());
Loading history...
130
    }
131
132
    /**
133
     * Verify behavior of set() with empty $key.
134
     *
135
     * @test
136
     * @covers ::set
137
     *
138
     * @return void
139
     */
140
    public function setEmptyKey()
141
    {
142
        $this->expectException(\Psr\SimpleCache\InvalidArgumentException::class);
143
        $this->expectExceptionMessage('$key must be a valid non empty string');
144
        $this->cache->set('', new DateTime());
145
    }
146
147
    /**
148
     * Verify behavior of set() with non string $key.
149
     *
150
     * @test
151
     * @covers ::set
152
     *
153
     * @return void
154
     */
155
    public function setNonStringKey()
156
    {
157
        $this->expectException(\Psr\SimpleCache\InvalidArgumentException::class);
158
        $this->expectExceptionMessage('$key must be a valid non empty string');
159
        $this->cache->set(new \StdClass(), new DateTime());
0 ignored issues
show
Bug introduced by
new StdClass() of type StdClass is incompatible with the type string expected by parameter $key of SubjectivePHP\Psr\SimpleCache\MongoCache::set(). ( Ignorable by Annotation )

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

159
        $this->cache->set(/** @scrutinizer ignore-type */ new \StdClass(), new DateTime());
Loading history...
160
    }
161
162
    /**
163
     * Verify behavior of set() with string $key containing reserved characters.
164
     *
165
     * @test
166
     * @covers ::set
167
     *
168
     * @return void
169
     */
170
    public function setKeyContainsReservedCharacters()
171
    {
172
        $this->expectException(\Psr\SimpleCache\InvalidArgumentException::class);
173
        $this->expectExceptionMessage('Key \'key with {, ) & @\' contains unsupported characters');
174
        $this->cache->set('key with {, ) & @', new DateTime());
175
    }
176
177
    /**
178
     * Verify basic behavior of delete().
179
     *
180
     * @test
181
     * @covers ::delete
182
     *
183
     * @return void
184
     */
185
    public function delete()
186
    {
187
        $this->collection->insertOne(['_id' => 'key1']);
188
        $this->collection->insertOne(['_id' => 'key2']);
189
190
        $this->assertTrue($this->cache->delete('key1'));
191
192
        $actual = $this->collection->find(
193
            [],
194
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
195
        )->toArray();
196
197
        $this->assertEquals([['_id' => 'key2']], $actual);
198
    }
199
200
    /**
201
     * Verify behavior of delete() when mongo exception is thrown.
202
     *
203
     * @test
204
     * @covers ::delete
205
     *
206
     * @return void
207
     */
208
    public function deleteMongoException()
209
    {
210
        $mockCollection = $this->getFailingCollectionMock('deleteOne');
211
        $cache = new MongoCache($mockCollection, $this->getSerializer());
212
        $this->assertFalse($cache->delete('key'));
213
    }
214
215
    /**
216
     * Verify basic behavior of clear().
217
     *
218
     * @test
219
     * @covers ::clear
220
     *
221
     * @return void
222
     */
223
    public function clear()
224
    {
225
        $this->collection->insertOne(['_id' => 'key1']);
226
        $this->collection->insertOne(['_id' => 'key2']);
227
228
        $this->assertTrue($this->cache->clear());
229
230
        $actual = $this->collection->find(
231
            [],
232
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
233
        )->toArray();
234
235
        $this->assertSame([], $actual);
236
    }
237
238
    /**
239
     * Verify behavior of clear() when mongo exception is thrown.
240
     *
241
     * @test
242
     * @covers ::clear
243
     *
244
     * @return void
245
     */
246
    public function clearMongoException()
247
    {
248
        $mockCollection = $this->getFailingCollectionMock('deleteMany');
249
        $cache = new MongoCache($mockCollection, $this->getSerializer());
250
        $this->assertFalse($cache->clear());
251
    }
252
253
    /**
254
     * Verify basic behavior of getMultiple
255
     *
256
     * @test
257
     * @covers ::getMultiple
258
     *
259
     * @return void
260
     */
261
    public function getMultiple()
262
    {
263
        $this->collection->insertOne(
264
            [
265
                '_id' => 'key1',
266
                'data' => [
267
                    'timestamp' => 1491782286,
268
                    'timezone' => 'America/New_York',
269
                ],
270
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
271
            ]
272
        );
273
        $this->collection->insertOne(
274
            [
275
                '_id' => 'key3',
276
                'data' => [
277
                    'timestamp' => 1491807244,
278
                    'timezone' => 'Pacific/Honolulu',
279
                ],
280
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
281
            ]
282
        );
283
284
        $default = new \StdClass();
285
286
        $dates = $this->cache->getMultiple(['key1', 'key2', 'key3', 'key4'], $default);
287
288
        $this->assertCount(4, $dates);
289
290
        $this->assertSame('2017-04-09 23:58:06', $dates['key1']->format('Y-m-d H:i:s'));
291
        $this->assertSame($default, $dates['key2']);
292
        $this->assertSame('2017-04-10 06:54:04', $dates['key3']->format('Y-m-d H:i:s'));
293
        $this->assertSame($default, $dates['key4']);
294
    }
295
296
    /**
297
     * Verify basic behavior of setMultiple().
298
     *
299
     * @test
300
     * @covers ::setMultiple
301
     *
302
     * @return void
303
     */
304
    public function setMultple()
305
    {
306
        $dates = [
307
            'key1' => new DateTime(),
308
            'key2' => new DateTime(),
309
        ];
310
311
        $this->assertTrue($this->cache->setMultiple($dates, 86400));
312
        $expires = new UTCDateTime((time() + 86400) * 1000);
313
        $this->assertDateTimeDocument('key1', $expires, $dates['key1']);
314
        $this->assertDateTimeDocument('key2', $expires, $dates['key2']);
315
    }
316
317
    /**
318
     * Verify behavior of setMultiple() when mongo throws an exception.
319
     *
320
     * @test
321
     * @covers ::setMultiple
322
     *
323
     * @return void
324
     */
325
    public function setMultpleMongoException()
326
    {
327
        $mockCollection = $this->getFailingCollectionMock('updateOne');
328
        $cache = new MongoCache($mockCollection, $this->getSerializer());
329
        $responses = ['key1' => new DateTime(), 'key2' => new DateTime()];
330
        $this->assertFalse($cache->setMultiple($responses, 86400));
331
    }
332
333
    /**
334
     * Verify basic behavior of deleteMultiple().
335
     *
336
     * @test
337
     * @covers ::deleteMultiple
338
     *
339
     * @return void
340
     */
341
    public function deleteMultiple()
342
    {
343
        $this->collection->insertOne(['_id' => 'key1']);
344
        $this->collection->insertOne(['_id' => 'key2']);
345
        $this->collection->insertOne(['_id' => 'key3']);
346
347
        $this->assertTrue($this->cache->deleteMultiple(['key1', 'key3']));
348
349
        $actual = $this->collection->find(
350
            [],
351
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
352
        )->toArray();
353
354
        $this->assertEquals([['_id' => 'key2']], $actual);
355
    }
356
357
    /**
358
     * Verify behavior of deleteMultiple() when mongo throws an exception.
359
     *
360
     * @test
361
     * @covers ::deleteMultiple
362
     *
363
     * @return void
364
     */
365
    public function deleteMultipleMongoException()
366
    {
367
        $mockCollection = $this->getFailingCollectionMock('deleteMany');
368
        $cache = new MongoCache($mockCollection, $this->getSerializer());
369
        $this->assertFalse($cache->deleteMultiple(['key1', 'key3']));
370
    }
371
372
    /**
373
     * Verify basic behavior of has().
374
     *
375
     * @test
376
     * @covers ::has
377
     *
378
     * @return void
379
     */
380
    public function has()
381
    {
382
        $this->collection->insertOne(['_id' => 'key1']);
383
        $this->assertTrue($this->cache->has('key1'));
384
        $this->assertFalse($this->cache->has('key2'));
385
    }
386
387
    /**
388
     * Helper method to assert the contents of a mongo document.
389
     *
390
     * @param string      $key      The _id value to assert.
391
     * @param UTCDateTime $expires  The expected expires value.
392
     * @param DateTime    $expected The expected DateTime value.
393
     *
394
     * @return void
395
     */
396
    private function assertDateTimeDocument(string $key, UTCDateTime $expires, DateTime $expected)
397
    {
398
        $actual = $this->collection->findOne(
399
            ['_id' => $key],
400
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
401
        );
402
403
        $this->assertSame($expires->toDateTime()->getTimestamp(), $actual['expires']->toDateTime()->getTimestamp());
404
405
        $this->assertSame(
406
            [
407
                '_id' => $key,
408
                'data' => [
409
                    'timestamp' => $expected->getTimestamp(),
410
                    'timezone' => $expected->getTimeZone()->getName(),
411
                ],
412
                'expires' => $actual['expires'],
413
            ],
414
            $actual
415
        );
416
    }
417
418
    private function getFailingCollectionMock(string $methodToFail) : Collection
419
    {
420
        $mock = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock();
421
        $mock->method($methodToFail)->will($this->throwException(new \Exception()));
422
        return $mock;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $mock returns the type PHPUnit\Framework\MockObject\MockObject which is incompatible with the type-hinted return MongoDB\Collection.
Loading history...
423
    }
424
425
    /**
426
     * Helper method to get a SerializerInterface instance.
427
     *
428
     * @return SerializerInterface
429
     */
430
    private function getSerializer() : SerializerInterface
431
    {
432
        return new class implements SerializerInterface
433
        {
434
            /**
435
             * @see SerializerInterface::unserialize().
436
             *
437
             * @param mixed $data The serialized data.
438
             *
439
             * @return DateTime
440
             */
441
            public function unserialize($data)
442
            {
443
                return new DateTime("@{$data['timestamp']}", timezone_open($data['timezone']));
444
            }
445
446
            /**
447
             * @see SerializerInterface::serialize().
448
             *
449
             * @param mixed $value The data to serialize.
450
             *
451
             * @return array
452
             */
453
            public function serialize($value)
454
            {
455
                return [
456
                    'timestamp' => $value->getTimestamp(),
457
                    'timezone' => $value->getTimezone()->getName(),
458
                ];
459
            }
460
        };
461
    }
462
}
463