Failed Conditions
Pull Request — master (#9)
by Chad
01:27
created

tests/MongoCacheTest.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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 SubjectivePHP\Psr\SimpleCache\InvalidArgumentException;
9
use SubjectivePHP\Psr\SimpleCache\MongoCache;
10
use SubjectivePHP\Psr\SimpleCache\Serializer\SerializerInterface;
11
12
/**
13
 * Defines unit tests for the MongoCache class.
14
 *
15
 * @coversDefaultClass \SubjectivePHP\Psr\SimpleCache\MongoCache
16
 * @covers ::__construct
17
 * @covers ::<private>
18
 * @covers ::<protected>
19
 */
20
final class MongoCacheTest extends \PHPUnit\Framework\TestCase
21
{
22
    /**
23
     * Mongo Collection to use in tests.
24
     *
25
     * @var MongoDB\Collection
26
     */
27
    private $collection;
28
29
    /**
30
     * Cache instance to us in tests.
31
     *
32
     * @var MongoCache
33
     */
34
    private $cache;
35
36
    /**
37
     * set up each test.
38
     *
39
     * @return void
40
     */
41
    public function setUp()
42
    {
43
        $this->collection = (new Client())->selectDatabase('testing')->selectCollection('cache');
0 ignored issues
show
Documentation Bug introduced by
It seems like (new \MongoDB\Client())-...lectCollection('cache') of type object<MongoDB\Collection> is incompatible with the declared type object<SubjectivePHPTest...che\MongoDB\Collection> of property $collection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
44
        $this->collection->drop();
45
        $this->cache = new MongoCache($this->collection, $this->getSerializer());
46
    }
47
48
    /**
49
     * Verify behavior of get() when the key is not found.
50
     *
51
     * @test
52
     * @covers ::get
53
     *
54
     * @return void
55
     */
56
    public function getNotFound()
57
    {
58
        $default = new \StdClass();
59
        $this->assertSame($default, $this->cache->get('key', $default));
60
    }
61
62
    /**
63
     * Verify basic behavior of get().
64
     *
65
     * @test
66
     * @covers ::get
67
     *
68
     * @return void
69
     */
70
    public function get()
71
    {
72
        $json = json_encode(['status' => 'ok']);
0 ignored issues
show
$json is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
73
        $headers = ['Content-Type' => ['application/json'], 'eTag' => ['"an etag"']];
0 ignored issues
show
$headers is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
74
        $this->collection->insertOne(
75
            [
76
                '_id' => 'key',
77
                'timestamp' => 1491782286,
78
                'timezone' => 'America/New_York',
79
            ]
80
        );
81
82
        $dateTime = new DateTime('@1491782286', new DateTimeZone('America/New_York'));
83
        $this->assertEquals($dateTime, $this->cache->get('key'));
84
    }
85
86
    /**
87
     * Verify basic behavior of set().
88
     *
89
     * @test
90
     * @covers ::set
91
     *
92
     * @return void
93
     */
94
    public function set()
95
    {
96
        $ttl = \DateInterval::createFromDateString('1 day');
97
        $dateTime = new DateTime('2017-04-09 20:54:04', new DateTimeZone('Pacific/Honolulu'));
98
        $this->cache->set('key', $dateTime, $ttl);
99
        $expires = new UTCDateTime((new \DateTime('now'))->add($ttl)->getTimestamp() * 1000);
100
        $this->assertDateTimeDocument('key', $expires, $dateTime);
101
    }
102
103
    /**
104
     * Verify behavior of set() with invalid $ttl value.
105
     *
106
     * @test
107
     * @covers ::set
108
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
109
     * @expectedExceptionMessage $ttl must be null, an integer or \DateInterval instance
110
     *
111
     * @return void
112
     */
113
    public function setInvalidTTL()
114
    {
115
        $this->cache->set('key', new DateTime(), new DateTime());
116
    }
117
118
    /**
119
     * Verify behavior of set() with empty $key.
120
     *
121
     * @test
122
     * @covers ::set
123
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
124
     * @expectedExceptionMessage $key must be a valid non empty string
125
     *
126
     * @return void
127
     */
128
    public function setEmptyKey()
129
    {
130
        $this->cache->set('', new DateTime());
131
    }
132
133
    /**
134
     * Verify behavior of set() with non string $key.
135
     *
136
     * @test
137
     * @covers ::set
138
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
139
     * @expectedExceptionMessage $key must be a valid non empty string
140
     *
141
     * @return void
142
     */
143
    public function setNonStringKey()
144
    {
145
        $this->cache->set(new \StdClass(), new DateTime());
146
    }
147
148
    /**
149
     * Verify behavior of set() with string $key containing reserved characters.
150
     *
151
     * @test
152
     * @covers ::set
153
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
154
     * @expectedExceptionMessage Key 'key with {, ) & @' contains unsupported characters
155
     *
156
     * @return void
157
     */
158
    public function setKeyContainsReservedCharacters()
159
    {
160
        $this->cache->set('key with {, ) & @', new DateTime());
161
    }
162
163
    /**
164
     * Verify basic behavior of delete().
165
     *
166
     * @test
167
     * @covers ::delete
168
     *
169
     * @return void
170
     */
171
    public function delete()
172
    {
173
        $this->collection->insertOne(['_id' => 'key1']);
174
        $this->collection->insertOne(['_id' => 'key2']);
175
176
        $this->assertTrue($this->cache->delete('key1'));
177
178
        $actual = $this->collection->find(
179
            [],
180
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
181
        )->toArray();
182
183
        $this->assertEquals([['_id' => 'key2']], $actual);
184
    }
185
186
    /**
187
     * Verify behavior of delete() when mongo exception is thrown.
188
     *
189
     * @test
190
     * @covers ::delete
191
     *
192
     * @return void
193
     */
194 View Code Duplication
    public function deleteMongoException()
195
    {
196
        $mockCollection = $this->getMockBuilder(
197
            '\\MongoDB\\Collection',
198
            ['deleteOne', 'createIndex']
199
        )->disableOriginalConstructor()->getMock();
200
        $mockCollection->method('deleteOne')->will($this->throwException(new \Exception()));
201
        $cache = new MongoCache($mockCollection, $this->getSerializer());
202
        $this->assertFalse($cache->delete('key'));
203
    }
204
205
    /**
206
     * Verify basic behavior of clear().
207
     *
208
     * @test
209
     * @covers ::clear
210
     *
211
     * @return void
212
     */
213
    public function clear()
214
    {
215
        $this->collection->insertOne(['_id' => 'key1']);
216
        $this->collection->insertOne(['_id' => 'key2']);
217
218
        $this->assertTrue($this->cache->clear());
219
220
        $actual = $this->collection->find(
221
            [],
222
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
223
        )->toArray();
224
225
        $this->assertSame([], $actual);
226
    }
227
228
    /**
229
     * Verify behavior of clear() when mongo exception is thrown.
230
     *
231
     * @test
232
     * @covers ::clear
233
     *
234
     * @return void
235
     */
236 View Code Duplication
    public function clearMongoException()
237
    {
238
        $mockCollection = $this->getMockBuilder(
239
            '\\MongoDB\\Collection',
240
            ['deleteMany', 'createIndex']
241
        )->disableOriginalConstructor()->getMock();
242
        $mockCollection->method('deleteMany')->will($this->throwException(new \Exception()));
243
        $cache = new MongoCache($mockCollection, $this->getSerializer());
244
        $this->assertFalse($cache->clear());
245
    }
246
247
    /**
248
     * Verify basic behavior of getMultiple
249
     *
250
     * @test
251
     * @covers ::getMultiple
252
     *
253
     * @return void
254
     */
255
    public function getMultiple()
256
    {
257
        $json = json_encode(['status' => 'ok']);
0 ignored issues
show
$json is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
258
        $headers = ['Content-Type' => ['application/json'], 'eTag' => ['"an etag"']];
0 ignored issues
show
$headers is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
259
        $this->collection->insertOne(
260
            [
261
                '_id' => 'key1',
262
                'timestamp' => 1491782286,
263
                'timezone' => 'America/New_York',
264
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
265
            ]
266
        );
267
        $this->collection->insertOne(
268
            [
269
                '_id' => 'key3',
270
                'timestamp' => 1491807244,
271
                'timezone' => 'Pacific/Honolulu',
272
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
273
            ]
274
        );
275
276
        $default = new \StdClass();
277
278
        $dates = $this->cache->getMultiple(['key1', 'key2', 'key3', 'key4'], $default);
279
280
        $this->assertCount(4, $dates);
281
282
        $this->assertSame('2017-04-09 23:58:06', $dates['key1']->format('Y-m-d H:i:s'));
283
        $this->assertSame($default, $dates['key2']);
284
        $this->assertSame('2017-04-10 06:54:04', $dates['key3']->format('Y-m-d H:i:s'));
285
        $this->assertSame($default, $dates['key4']);
286
    }
287
288
    /**
289
     * Verify basic behavior of setMultiple().
290
     *
291
     * @test
292
     * @covers ::setMultiple
293
     *
294
     * @return void
295
     */
296
    public function setMultple()
297
    {
298
        $dates = [
299
            'key1' => new DateTime(),
300
            'key2' => new DateTime(),
301
        ];
302
303
        $this->assertTrue($this->cache->setMultiple($dates, 86400));
304
        $expires = new UTCDateTime((time() + 86400) * 1000);
305
        $this->assertDateTimeDocument('key1', $expires, $dates['key1']);
306
        $this->assertDateTimeDocument('key2', $expires, $dates['key2']);
307
    }
308
309
    /**
310
     * Verify behavior of setMultiple() when mongo throws an exception.
311
     *
312
     * @test
313
     * @covers ::setMultiple
314
     *
315
     * @return void
316
     */
317
    public function setMultpleMongoException()
318
    {
319
        $mockCollection = $this->getMockBuilder(
320
            '\\MongoDB\\Collection',
321
            ['updateOne', 'createIndex']
322
        )->disableOriginalConstructor()->getMock();
323
        $mockCollection->method('updateOne')->will($this->throwException(new \Exception()));
324
        $cache = new MongoCache($mockCollection, $this->getSerializer());
325
        $responses = ['key1' => new DateTime(), 'key2' => new DateTime()];
326
        $this->assertFalse($cache->setMultiple($responses, 86400));
327
    }
328
329
    /**
330
     * Verify basic behavior of deleteMultiple().
331
     *
332
     * @test
333
     * @covers ::deleteMultiple
334
     *
335
     * @return void
336
     */
337
    public function deleteMultiple()
338
    {
339
        $this->collection->insertOne(['_id' => 'key1']);
340
        $this->collection->insertOne(['_id' => 'key2']);
341
        $this->collection->insertOne(['_id' => 'key3']);
342
343
        $this->assertTrue($this->cache->deleteMultiple(['key1', 'key3']));
344
345
        $actual = $this->collection->find(
346
            [],
347
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
348
        )->toArray();
349
350
        $this->assertEquals([['_id' => 'key2']], $actual);
351
    }
352
353
    /**
354
     * Verify behavior of deleteMultiple() when mongo throws an exception.
355
     *
356
     * @test
357
     * @covers ::deleteMultiple
358
     *
359
     * @return void
360
     */
361 View Code Duplication
    public function deleteMultipleMongoException()
362
    {
363
        $mockCollection = $this->getMockBuilder(
364
            '\\MongoDB\\Collection',
365
            ['deleteMany', 'createIndex']
366
        )->disableOriginalConstructor()->getMock();
367
        $mockCollection->method('deleteMany')->will($this->throwException(new \Exception()));
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
                'expires' => $actual['expires'],
409
                'timestamp' => $expected->getTimestamp(),
410
                'timezone' => $expected->getTimeZone()->getName(),
411
            ],
412
            $actual
413
        );
414
    }
415
416
    /**
417
     * Helper method to get a SerializerInterface instance.
418
     *
419
     * @return SerializerInterface
420
     */
421
    private function getSerializer() : SerializerInterface
422
    {
423
        return new class implements SerializerInterface
424
        {
425
            /**
426
             * @see SerializerInterface::unserialize().
427
             *
428
             * @param mixed $data The serialized data.
429
             *
430
             * @return DateTime
431
             */
432
            public function unserialize($data)
433
            {
434
                return new DateTime("@{$data['timestamp']}", timezone_open($data['timezone']));
435
            }
436
437
            /**
438
             * @see SerializerInterface::serialize().
439
             *
440
             * @param mixed $value The data to serialize.
441
             *
442
             * @return array
443
             */
444
            public function serialize($value)
445
            {
446
                return [
447
                    'timestamp' => $value->getTimestamp(),
448
                    'timezone' => $value->getTimezone()->getName(),
449
                ];
450
            }
451
        };
452
    }
453
}
454