Completed
Pull Request — master (#10)
by Chad
01:14
created

MongoCacheTest::deleteMultiple()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 9.7666
c 0
b 0
f 0
cc 1
nc 1
nop 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()
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
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
123
     * @expectedExceptionMessage $ttl must be null, an integer or \DateInterval instance
124
     *
125
     * @return void
126
     */
127
    public function setInvalidTTL()
128
    {
129
        $this->cache->set('key', new DateTime(), new DateTime());
0 ignored issues
show
Documentation introduced by
new \DateTime() is of type object<DateTime>, but the function expects a null|integer|object<DateInterval>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
130
    }
131
132
    /**
133
     * Verify behavior of set() with empty $key.
134
     *
135
     * @test
136
     * @covers ::set
137
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
138
     * @expectedExceptionMessage $key must be a valid non empty string
139
     *
140
     * @return void
141
     */
142
    public function setEmptyKey()
143
    {
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
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
153
     * @expectedExceptionMessage $key must be a valid non empty string
154
     *
155
     * @return void
156
     */
157
    public function setNonStringKey()
158
    {
159
        $this->cache->set(new \StdClass(), new DateTime());
0 ignored issues
show
Documentation introduced by
new \StdClass() is of type object<stdClass>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
160
    }
161
162
    /**
163
     * Verify behavior of set() with string $key containing reserved characters.
164
     *
165
     * @test
166
     * @covers ::set
167
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
168
     * @expectedExceptionMessage Key 'key with {, ) & @' contains unsupported characters
169
     *
170
     * @return void
171
     */
172
    public function setKeyContainsReservedCharacters()
173
    {
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
                'timestamp' => 1491782286,
267
                'timezone' => 'America/New_York',
268
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
269
            ]
270
        );
271
        $this->collection->insertOne(
272
            [
273
                '_id' => 'key3',
274
                'timestamp' => 1491807244,
275
                'timezone' => 'Pacific/Honolulu',
276
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
277
            ]
278
        );
279
280
        $default = new \StdClass();
281
282
        $dates = $this->cache->getMultiple(['key1', 'key2', 'key3', 'key4'], $default);
0 ignored issues
show
Documentation introduced by
array('key1', 'key2', 'key3', 'key4') is of type array<integer,string,{"0..."string","3":"string"}>, but the function expects a object<SubjectivePHP\Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
283
284
        $this->assertCount(4, $dates);
285
286
        $this->assertSame('2017-04-09 23:58:06', $dates['key1']->format('Y-m-d H:i:s'));
287
        $this->assertSame($default, $dates['key2']);
288
        $this->assertSame('2017-04-10 06:54:04', $dates['key3']->format('Y-m-d H:i:s'));
289
        $this->assertSame($default, $dates['key4']);
290
    }
291
292
    /**
293
     * Verify basic behavior of setMultiple().
294
     *
295
     * @test
296
     * @covers ::setMultiple
297
     *
298
     * @return void
299
     */
300
    public function setMultple()
301
    {
302
        $dates = [
303
            'key1' => new DateTime(),
304
            'key2' => new DateTime(),
305
        ];
306
307
        $this->assertTrue($this->cache->setMultiple($dates, 86400));
0 ignored issues
show
Documentation introduced by
$dates is of type array<string,object<Date...2":"object<DateTime>"}>, but the function expects a object<SubjectivePHP\Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
308
        $expires = new UTCDateTime((time() + 86400) * 1000);
309
        $this->assertDateTimeDocument('key1', $expires, $dates['key1']);
310
        $this->assertDateTimeDocument('key2', $expires, $dates['key2']);
311
    }
312
313
    /**
314
     * Verify behavior of setMultiple() when mongo throws an exception.
315
     *
316
     * @test
317
     * @covers ::setMultiple
318
     *
319
     * @return void
320
     */
321
    public function setMultpleMongoException()
322
    {
323
        $mockCollection = $this->getFailingCollectionMock('updateOne');
324
        $cache = new MongoCache($mockCollection, $this->getSerializer());
325
        $responses = ['key1' => new DateTime(), 'key2' => new DateTime()];
326
        $this->assertFalse($cache->setMultiple($responses, 86400));
0 ignored issues
show
Documentation introduced by
$responses is of type array<string,object<Date...2":"object<DateTime>"}>, but the function expects a object<SubjectivePHP\Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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']));
0 ignored issues
show
Documentation introduced by
array('key1', 'key3') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a object<SubjectivePHP\Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
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
    public function deleteMultipleMongoException()
362
    {
363
        $mockCollection = $this->getFailingCollectionMock('deleteMany');
364
        $cache = new MongoCache($mockCollection, $this->getSerializer());
365
        $this->assertFalse($cache->deleteMultiple(['key1', 'key3']));
0 ignored issues
show
Documentation introduced by
array('key1', 'key3') is of type array<integer,string,{"0":"string","1":"string"}>, but the function expects a object<SubjectivePHP\Psr\SimpleCache\iterable>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
366
    }
367
368
    /**
369
     * Verify basic behavior of has().
370
     *
371
     * @test
372
     * @covers ::has
373
     *
374
     * @return void
375
     */
376
    public function has()
377
    {
378
        $this->collection->insertOne(['_id' => 'key1']);
379
        $this->assertTrue($this->cache->has('key1'));
380
        $this->assertFalse($this->cache->has('key2'));
381
    }
382
383
    /**
384
     * Helper method to assert the contents of a mongo document.
385
     *
386
     * @param string      $key      The _id value to assert.
387
     * @param UTCDateTime $expires  The expected expires value.
388
     * @param DateTime    $expected The expected DateTime value.
389
     *
390
     * @return void
391
     */
392
    private function assertDateTimeDocument(string $key, UTCDateTime $expires, DateTime $expected)
393
    {
394
        $actual = $this->collection->findOne(
395
            ['_id' => $key],
396
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
397
        );
398
399
        $this->assertSame($expires->toDateTime()->getTimestamp(), $actual['expires']->toDateTime()->getTimestamp());
400
401
        $this->assertSame(
402
            [
403
                '_id' => $key,
404
                'expires' => $actual['expires'],
405
                'data' => [
406
                    'timestamp' => $expected->getTimestamp(),
407
                    'timezone' => $expected->getTimeZone()->getName(),
408
                ],
409
            ],
410
            $actual
411
        );
412
    }
413
414
    private function getFailingCollectionMock(string $methodToFail) : Collection
415
    {
416
        $mock = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock();
417
        $mock->method($methodToFail)->will($this->throwException(new \Exception()));
418
        return $mock;
419
    }
420
421
    /**
422
     * Helper method to get a SerializerInterface instance.
423
     *
424
     * @return SerializerInterface
425
     */
426
    private function getSerializer() : SerializerInterface
427
    {
428
        return new class implements SerializerInterface
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
429
        {
430
            /**
431
             * @see SerializerInterface::unserialize().
432
             *
433
             * @param mixed $data The serialized data.
434
             *
435
             * @return DateTime
436
             */
437
            public function unserialize($data)
438
            {
439
                return new DateTime("@{$data['timestamp']}", timezone_open($data['timezone']));
440
            }
441
442
            /**
443
             * @see SerializerInterface::serialize().
444
             *
445
             * @param mixed $value The data to serialize.
446
             *
447
             * @return array
448
             */
449
            public function serialize($value)
450
            {
451
                return [
452
                    'timestamp' => $value->getTimestamp(),
453
                    'timezone' => $value->getTimezone()->getName(),
454
                ];
455
            }
456
        };
457
    }
458
}
459