Completed
Pull Request — master (#4)
by Timóteo
05:46
created

RepositoryTest   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 578
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 258
dl 0
loc 578
rs 10
c 0
b 0
f 0
wmc 26
1
<?php
2
3
declare(strict_types=1);
4
5
namespace TZachi\PhalconRepository\Tests\Unit;
6
7
use InvalidArgumentException;
8
use Phalcon\Mvc\Model;
9
use Phalcon\Mvc\Model\Criteria;
10
use Phalcon\Mvc\Model\Resultset\Simple as SimpleResultset;
11
use PHPUnit\Framework\MockObject\MockObject;
12
use PHPUnit\Framework\TestCase;
13
use TZachi\PhalconRepository\ModelWrapper;
14
use TZachi\PhalconRepository\Repository;
15
16
/**
17
 * @coversDefaultClass Repository
18
 */
19
final class RepositoryTest extends TestCase
20
{
21
    /**
22
     * @var ModelWrapper|MockObject
23
     */
24
    private $modelWrapper;
25
26
    /**
27
     * @var Repository
28
     */
29
    private $repository;
30
31
    /**
32
     * @before
33
     */
34
    public function setUpDependencies(): void
35
    {
36
        $this->modelWrapper = $this->createMock(ModelWrapper::class);
37
38
        $this->repository = new Repository($this->modelWrapper);
39
    }
40
41
    /**
42
     * @test
43
     * @depends whereToParametersShouldConvertValidArgs
44
     * @depends orderByToParametersShouldConvertValidArgs
45
     */
46
    public function findFirstWhereShouldGetValidParametersAndReturnModel(): void
47
    {
48
        $where    = [
49
            'field' => 1,
50
            'field2' => 'abc',
51
        ];
52
        $orderBy  = ['field', 'field2' => 'DESC'];
53
        $expected = [
54
            'conditions' => '[field] = ?0 AND [field2] = ?1',
55
            'bind' => [1, 'abc'],
56
            'order' => '[field] ASC, [field2] DESC',
57
        ];
58
59
        /**
60
         * @var Model|MockObject $model
61
         */
62
        $model = $this->createMock(Model::class);
63
        $this->modelWrapper->expects(self::once())
64
            ->method('findFirst')
65
            ->with(self::identicalTo($expected))
66
            ->willReturn($model);
67
68
        self::assertSame($model, $this->repository->findFirstWhere($where, $orderBy));
69
    }
70
71
    /**
72
     * @test
73
     * @depends whereToParametersShouldConvertValidArgs
74
     * @depends orderByToParametersShouldConvertValidArgs
75
     */
76
    public function findFirstByShouldReturnModel(): void
77
    {
78
        /**
79
         * @var Model|MockObject $model
80
         */
81
        $model = $this->createMock(Model::class);
82
        $this->modelWrapper->expects(self::once())
83
            ->method('findFirst')
84
            ->willReturn($model);
85
86
        self::assertSame($model, $this->repository->findFirstBy('field', 1));
87
    }
88
89
    /**
90
     * @test
91
     * @depends whereToParametersShouldConvertValidArgs
92
     * @depends orderByToParametersShouldConvertValidArgs
93
     */
94
    public function findFirstByShouldReturnNullWhenWrapperReturnsFalse(): void
95
    {
96
        $this->modelWrapper->expects(self::once())
97
            ->method('findFirst')
98
            ->willReturn(false);
99
100
        self::assertNull($this->repository->findFirstBy('field', 1));
101
    }
102
103
    /**
104
     * @test
105
     * @depends whereToParametersShouldConvertValidArgs
106
     */
107
    public function findFirstShouldReturnModel(): void
108
    {
109
        /**
110
         * @var Model|MockObject $model
111
         */
112
        $model = $this->createMock(Model::class);
113
        $this->modelWrapper->expects(self::once())
114
            ->method('findFirst')
115
            ->willReturn($model);
116
117
        self::assertSame($model, $this->repository->findFirst(1));
118
    }
119
120
    /**
121
     * @test
122
     * @depends whereToParametersShouldConvertValidArgs
123
     */
124
    public function findFirstShouldUseCustomId(): void
125
    {
126
        $repository = new Repository($this->modelWrapper, 'customIdField');
127
128
        /**
129
         * @var Model|MockObject $model
130
         */
131
        $model = $this->createMock(Model::class);
132
133
        $this->modelWrapper->expects(self::once())
134
            ->method('findFirst')
135
            ->with(self::identicalTo([
136
                'conditions' => '[customIdField] = ?0',
137
                'bind' => [1],
138
            ]))
139
            ->willReturn($model);
140
141
        self::assertSame($model, $repository->findFirst(1));
142
    }
143
144
    /**
145
     * @test
146
     * @depends whereToParametersShouldConvertValidArgs
147
     */
148
    public function findFirstShouldReturnNullWhenWrapperReturnsFalse(): void
149
    {
150
        $this->modelWrapper->expects(self::once())
151
            ->method('findFirst')
152
            ->willReturn(false);
153
154
        self::assertNull($this->repository->findFirst(1));
155
    }
156
157
    /**
158
     * @test
159
     * @depends whereToParametersShouldConvertValidArgs
160
     * @depends orderByToParametersShouldConvertValidArgs
161
     * @dataProvider findByValidData
162
     *
163
     * @param mixed[]  $expectedParams
164
     * @param string[] $orderBy
165
     */
166
    public function findByShouldReturnResultsetWithValidData(
167
        string $fieldName,
168
        string $fieldValue,
169
        array $expectedParams,
170
        array $orderBy,
171
        int $limit = 0,
172
        int $offset = 0
173
    ): void {
174
        /**
175
         * @var SimpleResultset|MockObject $model
176
         */
177
        $resultSet = $this->createMock(SimpleResultset::class);
178
179
        $this->modelWrapper->expects(self::once())
180
            ->method('find')
181
            ->with(self::identicalTo($expectedParams))
182
            ->willReturn($resultSet);
183
184
        self::assertSame(
185
            $resultSet,
186
            $this->repository->findBy($fieldName, $fieldValue, $orderBy, $limit, $offset)
187
        );
188
    }
189
190
    /**
191
     * @test
192
     */
193
    public function findAllShouldPassEmptyWhere(): void
194
    {
195
        /**
196
         * @var SimpleResultset|MockObject $model
197
         */
198
        $resultSet      = $this->createMock(SimpleResultset::class);
199
        $expectedParams = [
200
            'order' => '[field] ASC, [field2] ASC',
201
            'offset' => 20,
202
            'limit' => 10,
203
        ];
204
205
        $this->modelWrapper->expects(self::once())
206
            ->method('find')
207
            ->with(self::identicalTo($expectedParams))
208
            ->willReturn($resultSet);
209
210
        self::assertSame(
211
            $resultSet,
212
            $this->repository->findAll(['field', 'field2'], 10, 20)
213
        );
214
    }
215
216
    /**
217
     * @return mixed[]
218
     */
219
    public function findByValidData(): array
220
    {
221
        $testData = 'test value';
222
223
        return [
224
            'Params only' => [
225
                'fieldName' => 'field',
226
                'fieldValue' => $testData,
227
                'expectedParams' => [
228
                    'conditions' => '[field] = ?0',
229
                    'bind' => [0 => $testData],
230
                ],
231
                'orderBy' => [],
232
                'limit' => 0,
233
                'offset' => 0,
234
            ],
235
            'With order by' => [
236
                'fieldName' => 'field',
237
                'fieldValue' => $testData,
238
                'expectedParams' => [
239
                    'conditions' => '[field] = ?0',
240
                    'bind' => [0 => $testData],
241
                    'order' => '[field] ASC, [field2] DESC',
242
                ],
243
                'orderBy' => [
244
                    'field' => 'ASC',
245
                    'field2' => 'DESC',
246
                ],
247
                'limit' => 0,
248
                'offset' => 0,
249
            ],
250
            'With order by 2' => [
251
                'fieldName' => 'field',
252
                'fieldValue' => $testData,
253
                'expectedParams' => [
254
                    'conditions' => '[field] = ?0',
255
                    'bind' => [0 => $testData],
256
                    'order' => '[field] ASC, [field2] ASC',
257
                ],
258
                'orderBy' => ['field', 'field2'],
259
                'limit' => 0,
260
                'offset' => 0,
261
            ],
262
            'With limit' => [
263
                'fieldName' => 'field',
264
                'fieldValue' => $testData,
265
                'expectedParams' => [
266
                    'conditions' => '[field] = ?0',
267
                    'bind' => [0 => $testData],
268
                    'order' => '[field] ASC',
269
                    'offset' => 0,
270
                    'limit' => 10,
271
                ],
272
                'orderBy' => ['field'],
273
                'limit' => 10,
274
                'offset' => 0,
275
            ],
276
            'With offset and limit' => [
277
                'fieldName' => 'field',
278
                'fieldValue' => $testData,
279
                'expectedParams' => [
280
                    'conditions' => '[field] = ?0',
281
                    'bind' => [0 => $testData],
282
                    'order' => '[field] ASC, [field2] ASC',
283
                    'offset' => 10,
284
                    'limit' => 5,
285
                ],
286
                'orderBy' => ['field', 'field2'],
287
                'limit' => 5,
288
                'offset' => 10,
289
            ],
290
        ];
291
    }
292
293
    /**
294
     * @test
295
     */
296
    public function queryShouldReturnCriteria(): void
297
    {
298
        /**
299
         * @var Criteria|MockObject $di
300
         */
301
        $criteria = $this->createMock(Criteria::class);
302
303
        $this->modelWrapper->expects(self::once())
304
            ->method('query')
305
            ->willReturn($criteria);
306
307
        static::assertSame($criteria, $this->repository->query());
308
    }
309
310
    /**
311
     * @test
312
     */
313
    public function countShouldNotGetColumnParameterWhenColumnIsNull(): void
314
    {
315
        $expectedParams = [
316
            'conditions' => '[field] = ?0',
317
            'bind' => [1],
318
        ];
319
320
        $this->modelWrapper->expects(self::once())
321
            ->method('count')
322
            ->with(self::identicalTo($expectedParams))
323
            ->willReturn(0);
324
325
        self::assertSame(0, $this->repository->count(null, ['field' => 1]));
326
    }
327
328
    /**
329
     * @test
330
     */
331
    public function countShouldGetColumnParameterWhenColumnIsSpecified(): void
332
    {
333
        $expectedParams = ['column' => 'testColumn'];
334
335
        $this->modelWrapper->expects(self::once())
336
            ->method('count')
337
            ->with(self::identicalTo($expectedParams))
338
            ->willReturn(0);
339
340
        self::assertSame(0, $this->repository->count('testColumn'));
341
    }
342
343
    /**
344
     * @test
345
     */
346
    public function sumShouldReturnCorrectValue(): void
347
    {
348
        $this->assertColumnAggregationMethod('sum', [[null, null], ['10.4', 10.4]]);
349
    }
350
351
    /**
352
     * @test
353
     */
354
    public function averageShouldReturnCorrectValue(): void
355
    {
356
        $this->assertColumnAggregationMethod('average', [[null, null], ['31.90', 31.9]]);
357
    }
358
359
    /**
360
     * @test
361
     */
362
    public function minimumShouldReturnCorrectValue(): void
363
    {
364
        $this->assertColumnAggregationMethod('minimum', [[null, null], ['40.3', '40.3'], ['2019-01-01', '2019-01-01']]);
365
    }
366
367
    /**
368
     * @test
369
     */
370
    public function maximumShouldReturnCorrectValue(): void
371
    {
372
        $this->assertColumnAggregationMethod('maximum', [[null, null], ['40.3', '40.3'], ['2019-01-01', '2019-01-01']]);
373
    }
374
375
    /**
376
     * @param mixed[] $returnValuesData
377
     */
378
    protected function assertColumnAggregationMethod(string $methodName, array $returnValuesData): void
379
    {
380
        // Test if it gets only the column
381
        $expectedParams = ['column' => 'testColumn'];
382
        $this->modelWrapper->expects(self::once())
383
            ->method($methodName)
384
            ->with(self::identicalTo($expectedParams));
385
        $this->repository->{$methodName}('testColumn');
386
387
        $this->setUpDependencies();
388
389
        // Test if it also gets the conditions parameter
390
        $expectedParams = ['column' => 'testColumn', 'conditions' => '[field] = ?0', 'bind' => [1]];
391
        $this->modelWrapper->expects(self::once())
392
            ->method($methodName)
393
            ->with(self::identicalTo($expectedParams));
394
        $this->repository->{$methodName}('testColumn', ['field' => 1]);
395
396
        // Now test if return values match
397
        foreach ($returnValuesData as [$modelReturnValue, $repositoryReturnValue]) {
398
            $this->setUpDependencies();
399
400
            $this->modelWrapper->expects(self::atLeastOnce())->method($methodName)->willReturn($modelReturnValue);
401
            self::assertSame($repositoryReturnValue, $this->repository->{$methodName}('testColumn'));
402
        }
403
    }
404
405
    /**
406
     * @test
407
     * @dataProvider whereParamValidData
408
     *
409
     * @param mixed[] $expected
410
     * @param mixed[] $where
411
     */
412
    public function whereToParametersShouldConvertValidArgs(array $expected, array $where): void
413
    {
414
        self::assertSame($expected, $this->repository->whereToParameters($where));
415
    }
416
417
    /**
418
     * @return mixed[]
419
     */
420
    public function whereParamValidData(): array
421
    {
422
        return [
423
            'Empty where' => [
424
                'expected' => [],
425
                'where' => [],
426
            ],
427
            'Simple where' => [
428
                'expected' => [
429
                    'conditions' => '[test] IS NULL AND [test2] IN (?0, ?1, ?2) AND [test3] = ?3',
430
                    'bind' => [
431
                        0 => 'zero',
432
                        1 => 'one',
433
                        2 => 'two',
434
                        3 => 'three',
435
                    ],
436
                ],
437
                'where' => [
438
                    'test' => null,
439
                    'test2' => ['zero', 'one', 'two'],
440
                    'test3' => 'three',
441
                ],
442
            ],
443
            'Simple where with different operators' => [
444
                'expected' => [
445
                    'conditions' => '[test] IS NULL AND ([numericField] > ?0) AND ([numericField] <= ?1) '
446
                        . 'AND ([dateField] BETWEEN ?2 AND ?3)',
447
                    'bind' => [
448
                        0 => 50,
449
                        1 => 150,
450
                        2 => '2019-01-01',
451
                        3 => '2019-01-31',
452
                    ],
453
                ],
454
                'where' => [
455
                    'test' => null,
456
                    [
457
                        '@operator' => '>',
458
                        'numericField' => 50,
459
                    ],
460
                    [
461
                        '@operator' => '<=',
462
                        'numericField' => 150,
463
                    ],
464
                    [
465
                        '@operator' => 'BETWEEN',
466
                        'dateField' => ['2019-01-01', '2019-01-31'],
467
                    ],
468
                ],
469
            ],
470
            'Composite where' => [
471
                'expected' => [
472
                    'conditions' => '[test] IN (?0, ?1, ?2) '
473
                        . 'AND ([test3] = ?3 OR [test4] = ?4 OR ([test5] = ?5 AND [test6] = ?6)) '
474
                        . 'AND ([test7] = ?7 OR [test8] = ?8) AND ([numericField] BETWEEN ?9 AND ?10)',
475
                    'bind' => [
476
                        0 => 'zero',
477
                        1 => 'one',
478
                        2 => 'two',
479
                        3 => 'three',
480
                        4 => 'four',
481
                        5 => 'five',
482
                        6 => 'six',
483
                        7 => 'seven',
484
                        8 => 'eight',
485
                        9 => 1,
486
                        10 => 10,
487
                    ],
488
                ],
489
                'where' => [
490
                    'test' => ['zero', 'one', 'two'],
491
                    [
492
                        '@type' => Repository::TYPE_OR,
493
                        'test3' => 'three',
494
                        'test4' => 'four',
495
                        [
496
                            '@type' => Repository::TYPE_AND,
497
                            'test5' => 'five',
498
                            'test6' => 'six',
499
                        ],
500
                    ],
501
                    [
502
                        '@type' => Repository::TYPE_OR,
503
                        'test7' => 'seven',
504
                        'test8' => 'eight',
505
                    ],
506
                    [
507
                        '@operator' => 'BETWEEN',
508
                        'numericField' => [1, 10],
509
                    ],
510
                ],
511
            ],
512
        ];
513
    }
514
515
    /**
516
     * @test
517
     * @depends whereToParametersShouldConvertValidArgs
518
     */
519
    public function whereToParametersShouldThrowExceptionWithInvalidBetweenValue(): void
520
    {
521
        $this->expectException(InvalidArgumentException::class);
522
        $this->expectExceptionMessageRegExp('/must be an array with exactly two values/i');
523
524
        $this->repository->whereToParameters([
525
            '@operator' => 'BETWEEN',
526
            'numericField' => [1],
527
        ]);
528
    }
529
530
    /**
531
     * @test
532
     * @depends whereToParametersShouldConvertValidArgs
533
     */
534
    public function whereToParametersShouldThrowExceptionWithEmptyArrayValue(): void
535
    {
536
        $this->expectException(InvalidArgumentException::class);
537
        $this->expectExceptionMessageRegExp('/empty array value is not allowed/i');
538
539
        $this->repository->whereToParameters([
540
            'multipleFields' => [],
541
        ]);
542
    }
543
544
    /**
545
     * @test
546
     * @depends whereToParametersShouldConvertValidArgs
547
     */
548
    public function whereToParametersShouldThrowExceptionWithInvalidOperator(): void
549
    {
550
        $this->expectException(InvalidArgumentException::class);
551
        $this->expectExceptionMessageRegExp('/\* is not a valid operator/i');
552
553
        $this->repository->whereToParameters([
554
            '@operator' => '*',
555
            'field1' => 'value1',
556
        ]);
557
    }
558
559
    /**
560
     * @test
561
     * @dataProvider orderByParamValidData
562
     *
563
     * @param mixed[]  $expected
564
     * @param string[] $orderBy
565
     */
566
    public function orderByToParametersShouldConvertValidArgs(array $expected, array $orderBy): void
567
    {
568
        self::assertSame($expected, $this->repository->orderByToParameters($orderBy));
569
    }
570
571
    /**
572
     * @return mixed[]
573
     */
574
    public function orderByParamValidData(): array
575
    {
576
        return [
577
            'Empty orderBy' => [
578
                'expected' => [],
579
                'orderBy' => [],
580
            ],
581
            'Sort direction specified for every field' => [
582
                'expected' => ['order' => '[field1] ASC, [field2] DESC, [field3] ASC'],
583
                'orderBy' => ['field1' => 'ASC', 'field2' => 'desc', 'field3' => 'asc'],
584
            ],
585
            'Sort direction not specified' => [
586
                'expected' => ['order' => '[field1] ASC, [field2] ASC, [field3] ASC'],
587
                'orderBy' => ['field1', 'field2', 'field3'],
588
            ],
589
            'Mixed' => [
590
                'expected' => ['order' => '[field1] ASC, [field2] DESC, [field3] ASC, [field4] DESC, [field4] ASC'],
591
                'orderBy' => [
592
                    'field1' => 'ASC',
593
                    'field2' => 'DESC',
594
                    0 => 'field3',
595
                    'field4' => 'DESC',
596
                    1 => 'field4',
597
                ],
598
            ],
599
        ];
600
    }
601
}
602