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

MongoCacheTest::useWithoutSerializer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
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
                'timestamp' => 1491782286,
90
                'timezone' => 'America/New_York',
91
            ]
92
        );
93
94
        $dateTime = new DateTime('@1491782286', new DateTimeZone('America/New_York'));
95
        $this->assertEquals($dateTime, $this->cache->get('key'));
96
    }
97
98
    /**
99
     * Verify basic behavior of set().
100
     *
101
     * @test
102
     * @covers ::set
103
     *
104
     * @return void
105
     */
106
    public function set()
107
    {
108
        $ttl = \DateInterval::createFromDateString('1 day');
109
        $dateTime = new DateTime('2017-04-09 20:54:04', new DateTimeZone('Pacific/Honolulu'));
110
        $this->cache->set('key', $dateTime, $ttl);
111
        $expires = new UTCDateTime((new \DateTime('now'))->add($ttl)->getTimestamp() * 1000);
112
        $this->assertDateTimeDocument('key', $expires, $dateTime);
113
    }
114
115
    /**
116
     * Verify behavior of set() with invalid $ttl value.
117
     *
118
     * @test
119
     * @covers ::set
120
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
121
     * @expectedExceptionMessage $ttl must be null, an integer or \DateInterval instance
122
     *
123
     * @return void
124
     */
125
    public function setInvalidTTL()
126
    {
127
        $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...
128
    }
129
130
    /**
131
     * Verify behavior of set() with empty $key.
132
     *
133
     * @test
134
     * @covers ::set
135
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
136
     * @expectedExceptionMessage $key must be a valid non empty string
137
     *
138
     * @return void
139
     */
140
    public function setEmptyKey()
141
    {
142
        $this->cache->set('', new DateTime());
143
    }
144
145
    /**
146
     * Verify behavior of set() with non string $key.
147
     *
148
     * @test
149
     * @covers ::set
150
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
151
     * @expectedExceptionMessage $key must be a valid non empty string
152
     *
153
     * @return void
154
     */
155
    public function setNonStringKey()
156
    {
157
        $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...
158
    }
159
160
    /**
161
     * Verify behavior of set() with string $key containing reserved characters.
162
     *
163
     * @test
164
     * @covers ::set
165
     * @expectedException \Psr\SimpleCache\InvalidArgumentException
166
     * @expectedExceptionMessage Key 'key with {, ) & @' contains unsupported characters
167
     *
168
     * @return void
169
     */
170
    public function setKeyContainsReservedCharacters()
171
    {
172
        $this->cache->set('key with {, ) & @', new DateTime());
173
    }
174
175
    /**
176
     * Verify basic behavior of delete().
177
     *
178
     * @test
179
     * @covers ::delete
180
     *
181
     * @return void
182
     */
183
    public function delete()
184
    {
185
        $this->collection->insertOne(['_id' => 'key1']);
186
        $this->collection->insertOne(['_id' => 'key2']);
187
188
        $this->assertTrue($this->cache->delete('key1'));
189
190
        $actual = $this->collection->find(
191
            [],
192
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
193
        )->toArray();
194
195
        $this->assertEquals([['_id' => 'key2']], $actual);
196
    }
197
198
    /**
199
     * Verify behavior of delete() when mongo exception is thrown.
200
     *
201
     * @test
202
     * @covers ::delete
203
     *
204
     * @return void
205
     */
206
    public function deleteMongoException()
207
    {
208
        $mockCollection = $this->getFailingCollectionMock('deleteOne');
209
        $cache = new MongoCache($mockCollection, $this->getSerializer());
210
        $this->assertFalse($cache->delete('key'));
211
    }
212
213
    /**
214
     * Verify basic behavior of clear().
215
     *
216
     * @test
217
     * @covers ::clear
218
     *
219
     * @return void
220
     */
221
    public function clear()
222
    {
223
        $this->collection->insertOne(['_id' => 'key1']);
224
        $this->collection->insertOne(['_id' => 'key2']);
225
226
        $this->assertTrue($this->cache->clear());
227
228
        $actual = $this->collection->find(
229
            [],
230
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
231
        )->toArray();
232
233
        $this->assertSame([], $actual);
234
    }
235
236
    /**
237
     * Verify behavior of clear() when mongo exception is thrown.
238
     *
239
     * @test
240
     * @covers ::clear
241
     *
242
     * @return void
243
     */
244
    public function clearMongoException()
245
    {
246
        $mockCollection = $this->getFailingCollectionMock('deleteMany');
247
        $cache = new MongoCache($mockCollection, $this->getSerializer());
248
        $this->assertFalse($cache->clear());
249
    }
250
251
    /**
252
     * Verify basic behavior of getMultiple
253
     *
254
     * @test
255
     * @covers ::getMultiple
256
     *
257
     * @return void
258
     */
259
    public function getMultiple()
260
    {
261
        $this->collection->insertOne(
262
            [
263
                '_id' => 'key1',
264
                'timestamp' => 1491782286,
265
                'timezone' => 'America/New_York',
266
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
267
            ]
268
        );
269
        $this->collection->insertOne(
270
            [
271
                '_id' => 'key3',
272
                'timestamp' => 1491807244,
273
                'timezone' => 'Pacific/Honolulu',
274
                'expires' => new UTCDateTime(strtotime('+1 day') * 1000),
275
            ]
276
        );
277
278
        $default = new \StdClass();
279
280
        $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...
281
282
        $this->assertCount(4, $dates);
283
284
        $this->assertSame('2017-04-09 23:58:06', $dates['key1']->format('Y-m-d H:i:s'));
285
        $this->assertSame($default, $dates['key2']);
286
        $this->assertSame('2017-04-10 06:54:04', $dates['key3']->format('Y-m-d H:i:s'));
287
        $this->assertSame($default, $dates['key4']);
288
    }
289
290
    /**
291
     * Verify basic behavior of setMultiple().
292
     *
293
     * @test
294
     * @covers ::setMultiple
295
     *
296
     * @return void
297
     */
298
    public function setMultple()
299
    {
300
        $dates = [
301
            'key1' => new DateTime(),
302
            'key2' => new DateTime(),
303
        ];
304
305
        $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...
306
        $expires = new UTCDateTime((time() + 86400) * 1000);
307
        $this->assertDateTimeDocument('key1', $expires, $dates['key1']);
308
        $this->assertDateTimeDocument('key2', $expires, $dates['key2']);
309
    }
310
311
    /**
312
     * Verify behavior of setMultiple() when mongo throws an exception.
313
     *
314
     * @test
315
     * @covers ::setMultiple
316
     *
317
     * @return void
318
     */
319
    public function setMultpleMongoException()
320
    {
321
        $mockCollection = $this->getFailingCollectionMock('updateOne');
322
        $cache = new MongoCache($mockCollection, $this->getSerializer());
323
        $responses = ['key1' => new DateTime(), 'key2' => new DateTime()];
324
        $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...
325
    }
326
327
    /**
328
     * Verify basic behavior of deleteMultiple().
329
     *
330
     * @test
331
     * @covers ::deleteMultiple
332
     *
333
     * @return void
334
     */
335
    public function deleteMultiple()
336
    {
337
        $this->collection->insertOne(['_id' => 'key1']);
338
        $this->collection->insertOne(['_id' => 'key2']);
339
        $this->collection->insertOne(['_id' => 'key3']);
340
341
        $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...
342
343
        $actual = $this->collection->find(
344
            [],
345
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
346
        )->toArray();
347
348
        $this->assertEquals([['_id' => 'key2']], $actual);
349
    }
350
351
    /**
352
     * Verify behavior of deleteMultiple() when mongo throws an exception.
353
     *
354
     * @test
355
     * @covers ::deleteMultiple
356
     *
357
     * @return void
358
     */
359
    public function deleteMultipleMongoException()
360
    {
361
        $mockCollection = $this->getFailingCollectionMock('deleteMany');
362
        $cache = new MongoCache($mockCollection, $this->getSerializer());
363
        $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...
364
    }
365
366
    /**
367
     * Verify basic behavior of has().
368
     *
369
     * @test
370
     * @covers ::has
371
     *
372
     * @return void
373
     */
374
    public function has()
375
    {
376
        $this->collection->insertOne(['_id' => 'key1']);
377
        $this->assertTrue($this->cache->has('key1'));
378
        $this->assertFalse($this->cache->has('key2'));
379
    }
380
381
    /**
382
     * Helper method to assert the contents of a mongo document.
383
     *
384
     * @param string      $key      The _id value to assert.
385
     * @param UTCDateTime $expires  The expected expires value.
386
     * @param DateTime    $expected The expected DateTime value.
387
     *
388
     * @return void
389
     */
390
    private function assertDateTimeDocument(string $key, UTCDateTime $expires, DateTime $expected)
391
    {
392
        $actual = $this->collection->findOne(
393
            ['_id' => $key],
394
            ['typeMap' => ['root' => 'array', 'document' => 'array', 'array' => 'array']]
395
        );
396
397
        $this->assertSame($expires->toDateTime()->getTimestamp(), $actual['expires']->toDateTime()->getTimestamp());
398
399
        $this->assertSame(
400
            [
401
                '_id' => $key,
402
                'expires' => $actual['expires'],
403
                'timestamp' => $expected->getTimestamp(),
404
                'timezone' => $expected->getTimeZone()->getName(),
405
            ],
406
            $actual
407
        );
408
    }
409
410
    private function getFailingCollectionMock(string $methodToFail) : Collection
411
    {
412
        $mock = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock();
413
        $mock->method($methodToFail)->will($this->throwException(new \Exception()));
414
        return $mock;
415
    }
416
417
    /**
418
     * Helper method to get a SerializerInterface instance.
419
     *
420
     * @return SerializerInterface
421
     */
422
    private function getSerializer() : SerializerInterface
423
    {
424
        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...
425
        {
426
            /**
427
             * @see SerializerInterface::unserialize().
428
             *
429
             * @param mixed $data The serialized data.
430
             *
431
             * @return DateTime
432
             */
433
            public function unserialize($data)
434
            {
435
                return new DateTime("@{$data['timestamp']}", timezone_open($data['timezone']));
436
            }
437
438
            /**
439
             * @see SerializerInterface::serialize().
440
             *
441
             * @param mixed $value The data to serialize.
442
             *
443
             * @return array
444
             */
445
            public function serialize($value)
446
            {
447
                return [
448
                    'timestamp' => $value->getTimestamp(),
449
                    'timezone' => $value->getTimezone()->getName(),
450
                ];
451
            }
452
        };
453
    }
454
}
455