Completed
Pull Request — master (#76)
by Chad
01:25
created

FiltererTest::ofArrayRequiredFail()   A

Complexity

Conditions 2
Paths 3

Size

Total Lines 10
Code Lines 7

Duplication

Lines 10
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 10
loc 10
rs 9.4285
cc 2
eloc 7
nc 3
nop 0
1
<?php
2
3
namespace TraderInteractive;
4
5
use Exception;
6
use InvalidArgumentException;
7
use PHPUnit\Framework\TestCase;
8
use StdClass;
9
10
/**
11
 * @coversDefaultClass \TraderInteractive\Filterer
12
 */
13
final class FiltererTest extends TestCase
14
{
15
    public function setUp()
16
    {
17
        Filterer::setFilterAliases(Filterer::DEFAULT_FILTER_ALIASES);
18
    }
19
20
    /**
21
     * @test
22
     * @covers ::filter
23
     */
24
    public function requiredPass()
25
    {
26
        $result = Filterer::filter(['fieldOne' => ['required' => false]], []);
27
        $this->assertSame([true, [], null, []], $result);
28
    }
29
30
    /**
31
     * @test
32
     * @covers ::filter
33
     */
34
    public function requiredFail()
35
    {
36
        $result = Filterer::filter(['fieldOne' => ['required' => true]], []);
37
        $this->assertSame([false, null, "Field 'fieldOne' was required and not present", []], $result);
38
    }
39
40
    /**
41
     * @test
42
     * @covers ::filter
43
     */
44
    public function requiredWithADefaultWithoutInput()
45
    {
46
        $result = Filterer::filter(['fieldOne' => ['required' => true, 'default' => 'theDefault']], []);
47
        $this->assertSame([true, ['fieldOne' => 'theDefault'], null, []], $result);
48
    }
49
50
    /**
51
     * @test
52
     * @covers ::filter
53
     */
54
    public function requiredWithANullDefaultWithoutInput()
55
    {
56
        $result = Filterer::filter(['fieldOne' => ['required' => true, 'default' => null]], []);
57
        $this->assertSame([true, ['fieldOne' => null], null, []], $result);
58
    }
59
60
    /**
61
     * @test
62
     * @covers ::filter
63
     */
64 View Code Duplication
    public function requiredWithADefaultWithInput()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
65
    {
66
        $result = Filterer::filter(
67
            ['fieldOne' => ['required' => true, 'default' => 'theDefault']],
68
            ['fieldOne' => 'notTheDefault']
69
        );
70
        $this->assertSame([true, ['fieldOne' => 'notTheDefault'], null, []], $result);
71
    }
72
73
    /**
74
     * @test
75
     * @covers ::filter
76
     */
77
    public function notRequiredWithADefaultWithoutInput()
78
    {
79
        $result = Filterer::filter(['fieldOne' => ['default' => 'theDefault']], []);
80
        $this->assertSame([true, ['fieldOne' => 'theDefault'], null, []], $result);
81
    }
82
83
    /**
84
     * @test
85
     * @covers ::filter
86
     */
87
    public function notRequiredWithADefaultWithInput()
88
    {
89
        $result = Filterer::filter(['fieldOne' => ['default' => 'theDefault']], ['fieldOne' => 'notTheDefault']);
90
        $this->assertSame([true, ['fieldOne' => 'notTheDefault'], null, []], $result);
91
    }
92
93
    /**
94
     * @test
95
     * @covers ::filter
96
     */
97
    public function requiredDefaultPass()
98
    {
99
        $result = Filterer::filter(['fieldOne' => []], []);
100
        $this->assertSame([true, [], null, []], $result);
101
    }
102
103
    /**
104
     * @test
105
     * @covers ::filter
106
     */
107
    public function requiredDefaultFail()
108
    {
109
        $result = Filterer::filter(['fieldOne' => []], [], ['defaultRequired' => true]);
110
        $this->assertSame([false, null, "Field 'fieldOne' was required and not present", []], $result);
111
    }
112
113
    /**
114
     * @test
115
     * @covers ::filter
116
     */
117
    public function filterPass()
118
    {
119
        $result = Filterer::filter(['fieldOne' => [['floatval']]], ['fieldOne' => '3.14']);
120
        $this->assertSame([true, ['fieldOne' => 3.14], null, []], $result);
121
    }
122
123
    /**
124
     * @test
125
     * @covers ::filter
126
     */
127
    public function filterDefaultShortNamePass()
128
    {
129
        $result = Filterer::filter(['fieldOne' => [['float']]], ['fieldOne' => '3.14']);
130
        $this->assertSame([true, ['fieldOne' => 3.14], null, []], $result);
131
    }
132
133
    /**
134
     * @test
135
     * @covers ::filter
136
     * @covers ::setFilterAliases
137
     */
138
    public function filterCustomShortNamePass()
139
    {
140
        Filterer::setFilterAliases(['fval' => 'floatval']);
141
        $result = Filterer::filter(['fieldOne' => [['fval']]], ['fieldOne' => '3.14']);
142
        $this->assertSame([true, ['fieldOne' => 3.14], null, []], $result);
143
    }
144
145
    /**
146
     * @test
147
     * @covers ::filter
148
     * @covers ::setFilterAliases
149
     * @covers ::getFilterAliases
150
     */
151
    public function filterGetSetKnownFilters()
152
    {
153
        $knownFilters = ['lower' => 'strtolower', 'upper' => 'strtoupper'];
154
        Filterer::setFilterAliases($knownFilters);
155
        $this->assertSame($knownFilters, Filterer::getFilterAliases());
156
    }
157
158
    /**
159
     * @test
160
     * @covers ::filter
161
     */
162
    public function filterFail()
163
    {
164
        $result = Filterer::filter(
165
            ['fieldOne' => [['\TraderInteractive\FiltererTest::failingFilter']]],
166
            ['fieldOne' => 'valueOne']
167
        );
168
        $this->assertSame(
169
            [false, null, "Field 'fieldOne' with value 'valueOne' failed filtering, message 'i failed'", []],
170
            $result
171
        );
172
    }
173
174
    /**
175
     * @test
176
     * @covers ::filter
177
     */
178
    public function chainPass()
179
    {
180
        $result = Filterer::filter(['fieldOne' => [['trim', 'a'], ['floatval']]], ['fieldOne' => 'a3.14']);
181
        $this->assertSame([true, ['fieldOne' => 3.14], null, []], $result);
182
    }
183
184
    /**
185
     * @test
186
     * @covers ::filter
187
     */
188 View Code Duplication
    public function chainFail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
    {
190
        $result = Filterer::filter(
191
            ['fieldOne' => [['trim'], ['\TraderInteractive\FiltererTest::failingFilter']]],
192
            ['fieldOne' => 'the value']
193
        );
194
        $this->assertSame(
195
            [false, null, "Field 'fieldOne' with value 'the value' failed filtering, message 'i failed'", []],
196
            $result
197
        );
198
    }
199
200
    /**
201
     * @test
202
     * @covers ::filter
203
     */
204
    public function multiInputPass()
205
    {
206
        $result = Filterer::filter(
207
            ['fieldOne' => [['trim']], 'fieldTwo' => [['strtoupper']]],
208
            ['fieldOne' => ' value', 'fieldTwo' => 'bob']
209
        );
210
        $this->assertSame([true, ['fieldOne' => 'value', 'fieldTwo' => 'BOB'], null, []], $result);
211
    }
212
213
    /**
214
     * @test
215
     * @covers ::filter
216
     */
217
    public function multiInputFail()
218
    {
219
        $result = Filterer::filter(
220
            [
221
                'fieldOne' => [['\TraderInteractive\FiltererTest::failingFilter']],
222
                'fieldTwo' => [['\TraderInteractive\FiltererTest::failingFilter']],
223
            ],
224
            ['fieldOne' => 'value one', 'fieldTwo' => 'value two']
225
        );
226
        $expectedMessage = "Field 'fieldOne' with value 'value one' failed filtering, message 'i failed'\n";
227
        $expectedMessage .= "Field 'fieldTwo' with value 'value two' failed filtering, message 'i failed'";
228
        $this->assertSame([false, null, $expectedMessage, []], $result);
229
    }
230
231
    /**
232
     * @test
233
     * @covers ::filter
234
     */
235
    public function emptyFilter()
236
    {
237
        $result = Filterer::filter(['fieldOne' => [[]]], ['fieldOne' => 0]);
238
        $this->assertSame([true, ['fieldOne' => 0], null, []], $result);
239
    }
240
241
    /**
242
     * @test
243
     * @covers ::filter
244
     */
245
    public function unknownsAllowed()
246
    {
247
        $result = Filterer::filter([], ['fieldTwo' => 0], ['allowUnknowns' => true]);
248
        $this->assertSame([true, [], null, ['fieldTwo' => 0]], $result);
249
    }
250
251
    /**
252
     * @test
253
     * @covers ::filter
254
     */
255
    public function unknownsNotAllowed()
256
    {
257
        $result = Filterer::filter([], ['fieldTwo' => 0]);
258
        $this->assertSame([false, null, "Field 'fieldTwo' with value '0' is unknown", ['fieldTwo' => 0]], $result);
259
    }
260
261
    /**
262
     * @test
263
     * @covers ::filter
264
     */
265
    public function objectFilter()
266
    {
267
        $result = Filterer::filter(['fieldOne' => [[[$this, 'passingFilter']]]], ['fieldOne' => 'foo']);
268
        $this->assertSame([true, ['fieldOne' => 'fooboo'], null, []], $result);
269
    }
270
271
    /**
272
     * @test
273
     * @covers ::filter
274
     * @expectedException Exception
275
     * @expectedExceptionMessage Function 'boo' for field 'foo' is not callable
276
     */
277
    public function notCallable()
278
    {
279
        Filterer::filter(['foo' => [['boo']]], ['foo' => 0]);
280
    }
281
282
    /**
283
     * @test
284
     * @covers ::filter
285
     * @expectedException InvalidArgumentException
286
     * @expectedExceptionMessage 'allowUnknowns' option was not a bool
287
     */
288
    public function allowUnknownsNotBool()
289
    {
290
        Filterer::filter([], [], ['allowUnknowns' => 1]);
291
    }
292
293
    /**
294
     * @test
295
     * @covers ::filter
296
     * @expectedException InvalidArgumentException
297
     * @expectedExceptionMessage 'defaultRequired' option was not a bool
298
     */
299
    public function defaultRequiredNotBool()
300
    {
301
        Filterer::filter([], [], ['defaultRequired' => 1]);
302
    }
303
304
    /**
305
     * @test
306
     * @covers ::filter
307
     * @expectedException InvalidArgumentException
308
     * @expectedExceptionMessage filters for field 'boo' was not a array
309
     */
310
    public function filtersNotArrayInLeftOverSpec()
311
    {
312
        Filterer::filter(['boo' => 1], []);
313
    }
314
315
    /**
316
     * @test
317
     * @covers ::filter
318
     * @expectedException InvalidArgumentException
319
     * @expectedExceptionMessage filters for field 'boo' was not a array
320
     */
321
    public function filtersNotArrayWithInput()
322
    {
323
        Filterer::filter(['boo' => 1], ['boo' => 'notUnderTest']);
324
    }
325
326
    /**
327
     * @test
328
     * @covers ::filter
329
     * @expectedException InvalidArgumentException
330
     * @expectedExceptionMessage filter for field 'boo' was not a array
331
     */
332
    public function filterNotArray()
333
    {
334
        Filterer::filter(['boo' => [1]], ['boo' => 'notUnderTest']);
335
    }
336
337
    /**
338
     * @test
339
     * @covers ::filter
340
     * @expectedException InvalidArgumentException
341
     * @expectedExceptionMessage 'required' for field 'boo' was not a bool
342
     */
343
    public function requiredNotBool()
344
    {
345
        Filterer::filter(['boo' => ['required' => 1]], []);
346
    }
347
348
    /**
349
     * @test
350
     * @covers ::registerAlias
351
     * @expectedException InvalidArgumentException
352
     * @expectedExceptionMessage $alias was not a string or int
353
     */
354
    public function registerAliasAliasNotString()
355
    {
356
        Filterer::registerAlias(true, 'strtolower');
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a string|integer.

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...
357
    }
358
359
    /**
360
     * @test
361
     * @covers ::registerAlias
362
     * @expectedException Exception
363
     * @expectedExceptionMessage Alias 'upper' exists
364
     */
365
    public function registerExistingAliasOverwriteFalse()
366
    {
367
        Filterer::setFilterAliases([]);
368
        Filterer::registerAlias('upper', 'strtoupper');
369
        Filterer::registerAlias('upper', 'strtoupper', false);
370
    }
371
372
    /**
373
     * @test
374
     * @covers ::registerAlias
375
     */
376
    public function registerExistingAliasOverwriteTrue()
377
    {
378
        Filterer::setFilterAliases(['upper' => 'strtoupper', 'lower' => 'strtolower']);
379
        Filterer::registerAlias('upper', 'ucfirst', true);
380
        $this->assertSame(['upper' => 'ucfirst', 'lower' => 'strtolower'], Filterer::getFilterAliases());
381
    }
382
383
    public static function failingFilter()
384
    {
385
        throw new Exception('i failed');
386
    }
387
388
    public static function passingFilter($value)
389
    {
390
        return $value . 'boo';
391
    }
392
393
    /**
394
     * Verify custom errors can be added to filter spec.
395
     *
396
     * @test
397
     * @covers ::filter
398
     *
399
     * @return void
400
     */
401 View Code Duplication
    public function filterWithCustomError()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
402
    {
403
        $result = Filterer::filter(
404
            [
405
                'fieldOne' => [
406
                    ['\TraderInteractive\FiltererTest::failingFilter'],
407
                    'error' => 'My custom error message'
408
                ],
409
            ],
410
            ['fieldOne' => 'valueOne']
411
        );
412
        $this->assertSame(
413
            [false, null, 'My custom error message', []],
414
            $result
415
        );
416
    }
417
418
    /**
419
     * Verify behavior of filter() when 'error' is not a string value.
420
     *
421
     * @test
422
     * @covers ::filter
423
     * @expectedException InvalidArgumentException
424
     * @expectedExceptionMessage error for field 'fieldOne' was not a non-empty string
425
     *
426
     * @return void
427
     */
428 View Code Duplication
    public function filterWithNonStringError()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
429
    {
430
        Filterer::filter(
431
            ['fieldOne' => [['strtoupper'], 'error' => new StdClass()]],
432
            ['fieldOne' => 'valueOne']
433
        );
434
    }
435
436
    /**
437
     * Verify behavior of filter() when 'error' is an empty string.
438
     *
439
     * @test
440
     * @covers ::filter
441
     * @expectedException InvalidArgumentException
442
     * @expectedExceptionMessage error for field 'fieldOne' was not a non-empty string
443
     *
444
     * @return void
445
     */
446 View Code Duplication
    public function filterWithEmptyStringError()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
447
    {
448
        Filterer::filter(
449
            ['fieldOne' => [['strtoupper'], 'error' => "\n   \t"]],
450
            ['fieldOne' => 'valueOne']
451
        );
452
    }
453
454
    /**
455
     * @test
456
     * @covers ::ofScalars
457
     */
458
    public function ofScalars()
459
    {
460
        $this->assertSame([1, 2], Filterer::ofScalars(['1', '2'], [['uint']]));
461
    }
462
463
    /**
464
     * @test
465
     * @covers ::ofScalars
466
     */
467
    public function ofScalarsChained()
468
    {
469
        $this->assertSame([3.3, 5.5], Filterer::ofScalars(['a3.3', 'a5.5'], [['trim', 'a'], ['floatval']]));
470
    }
471
472
    /**
473
     * @test
474
     * @covers ::ofScalars
475
     */
476
    public function ofScalarsWithMeaninglessKeys()
477
    {
478
        $this->assertSame(['key1' => 1, 'key2' => 2], Filterer::ofScalars(['key1' => '1', 'key2' => '2'], [['uint']]));
479
    }
480
481
    /**
482
     * @test
483
     * @covers ::ofScalars
484
     */
485 View Code Duplication
    public function ofScalarsFail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
486
    {
487
        try {
488
            Filterer::ofScalars(['1', [], new \StdClass], [['string']]);
489
            $this->fail();
490
        } catch (Exception $e) {
491
            $expected = <<<TXT
492
Field '1' with value 'array (
493
)' failed filtering, message 'Value 'array (
494
)' is not a string'
495
Field '2' with value 'stdClass::__set_state(array(
496
))' failed filtering, message 'Value 'stdClass::__set_state(array(
497
))' is not a string'
498
TXT;
499
            $this->assertSame($expected, $e->getMessage());
500
        }
501
    }
502
503
    /**
504
     * @test
505
     * @covers ::ofArrays
506
     */
507
    public function ofArrays()
508
    {
509
        $expected = [['key' => 1], ['key' => 2]];
510
        $this->assertSame($expected, Filterer::ofArrays([['key' => '1'], ['key' => '2']], ['key' => [['uint']]]));
511
    }
512
513
    /**
514
     * @test
515
     * @covers ::ofArrays
516
     */
517
    public function ofArraysChained()
518
    {
519
        $expected = [['key' => 3.3], ['key' => 5.5]];
520
        $spec = ['key' => [['trim', 'a'], ['floatval']]];
521
        $this->assertSame($expected, Filterer::ofArrays([['key' => 'a3.3'], ['key' => 'a5.5']], $spec));
522
    }
523
524
    /**
525
     * @test
526
     * @covers ::ofArrays
527
     */
528 View Code Duplication
    public function ofArraysRequiredAndUnknown()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
529
    {
530
        try {
531
            Filterer::ofArrays([['key' => '1'], ['key2' => '2']], ['key' => ['required' => true, ['uint']]]);
532
            $this->fail();
533
        } catch (Exception $e) {
534
            $expected = "Field 'key' was required and not present\nField 'key2' with value '2' is unknown";
535
            $this->assertSame($expected, $e->getMessage());
536
        }
537
    }
538
539
    /**
540
     * @test
541
     * @covers ::ofArrays
542
     */
543
    public function ofArraysFail()
544
    {
545
        try {
546
            Filterer::ofArrays(
547
                [['key' => new \StdClass], ['key' => []], ['key' => null], 'key'],
548
                ['key' => [['string']]]
549
            );
550
            $this->fail();
551
        } catch (Exception $e) {
552
            $expected = <<<TXT
553
Field 'key' with value 'stdClass::__set_state(array(
554
))' failed filtering, message 'Value 'stdClass::__set_state(array(
555
))' is not a string'
556
Field 'key' with value 'array (
557
)' failed filtering, message 'Value 'array (
558
)' is not a string'
559
Field 'key' with value 'NULL' failed filtering, message 'Value failed filtering, \$allowNull is set to false'
560
Value at position '3' was not an array
561
TXT;
562
            $this->assertSame($expected, $e->getMessage());
563
        }
564
    }
565
566
    /**
567
     * @test
568
     * @covers ::ofArray
569
     */
570
    public function ofArray()
571
    {
572
        $expected = ['key1' => 1, 'key2' => 2];
573
        $spec = ['key1' => [['uint']], 'key2' => [['uint']]];
574
        $this->assertSame($expected, Filterer::ofArray(['key1' => '1', 'key2' => '2'], $spec));
575
    }
576
577
    /**
578
     * @test
579
     * @covers ::ofArray
580
     */
581
    public function ofArrayChained()
582
    {
583
        $expected = ['key' => 3.3];
584
        $spec = ['key' => [['trim', 'a'], ['floatval']]];
585
        $this->assertSame($expected, Filterer::ofArray(['key' => 'a3.3'], $spec));
586
    }
587
588
    /**
589
     * @test
590
     * @covers ::ofArray
591
     */
592
    public function ofArrayRequiredSuccess()
593
    {
594
        $expected = ['key2' => 2];
595
        $spec = ['key1' => [['uint']], 'key2' => ['required' => true, ['uint']]];
596
        $this->assertSame($expected, Filterer::ofArray(['key2' => '2'], $spec));
597
    }
598
599
    /**
600
     * @test
601
     * @covers ::ofArray
602
     */
603 View Code Duplication
    public function ofArrayRequiredFail()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
604
    {
605
        try {
606
            Filterer::ofArray(['key1' => '1'], ['key1' => [['uint']], 'key2' => ['required' => true, ['uint']]]);
607
            $this->fail();
608
        } catch (Exception $e) {
609
            $expected = "Field 'key2' was required and not present";
610
            $this->assertSame($expected, $e->getMessage());
611
        }
612
    }
613
614
    /**
615
     * @test
616
     * @covers ::ofArray
617
     */
618 View Code Duplication
    public function ofArrayUnknown()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
619
    {
620
        try {
621
            Filterer::ofArray(['key' => '1'], ['key2' => [['uint']]]);
622
            $this->fail();
623
        } catch (Exception $e) {
624
            $expected = "Field 'key' with value '1' is unknown";
625
            $this->assertSame($expected, $e->getMessage());
626
        }
627
    }
628
}
629