Passed
Push — main ( d1c8bb...2cd0ba )
by Daniel
12:40
created

InvalidParameterFilterTest::testIsAFilter()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace DanBettles\Defence\Tests\Filter;
6
7
use Psr\Log\NullLogger;
8
use Psr\Log\LogLevel;
9
use DanBettles\Defence\Filter\InvalidParameterFilter;
10
use DanBettles\Defence\Filter\AbstractFilter;
11
use DanBettles\Defence\Envelope;
12
use DanBettles\Defence\Tests\AbstractTestCase;
13
use InvalidArgumentException;
14
use Symfony\Component\HttpFoundation\Request;
15
16
use function strtoupper;
17
18
use const false;
19
use const null;
20
use const true;
21
22
/**
23
 * @phpstan-import-type Selector from InvalidParameterFilter
24
 * @phpstan-import-type AugmentedFilterOptions from InvalidParameterFilter
25
 */
26
class InvalidParameterFilterTest extends AbstractTestCase
27
{
28
    public function testIsAFilter(): void
29
    {
30
        $this->assertSubclassOf(AbstractFilter::class, InvalidParameterFilter::class);
31
    }
32
33
    /** @return array<mixed[]> */
34
    public function providesConstructorArguments(): array
35
    {
36
        return [[
37
            'selector' => ['blog_id', 'post_id'],
38
            'validator' => '/^\d*$/',
39
        ], [
40
            'selector' => '/_id$/',
41
            'validator' => '/^\d*$/',
42
        ]];
43
    }
44
45
    /**
46
     * @dataProvider providesConstructorArguments
47
     * @phpstan-param Selector $selector
48
     */
49
    public function testConstructor(
50
        $selector,
51
        string $validator
52
    ): void {
53
        $filter = new InvalidParameterFilter($selector, $validator);
54
55
        $this->assertSame($selector, $filter->getSelector());
56
        $this->assertSame($validator, $filter->getValidator());
57
58
        $this->assertSame([
59
            'log_level' => LogLevel::WARNING,
60
            'type' => null,
61
        ], $filter->getOptions());
62
    }
63
64
    public function testConstructorAcceptsOptions(): void
65
    {
66
        $filter = new InvalidParameterFilter([], '//', [
67
            'foo' => 'bar',
68
        ]);
69
70
        $this->assertSame([
71
            'log_level' => LogLevel::WARNING,
72
            'type' => null,
73
            'foo' => 'bar',
74
        ], $filter->getOptions());
75
    }
76
77
    /** @return array<mixed[]> */
78
    public function providesInvalidTypeNames(): array
79
    {
80
        $invalidTypeNames = [
81
            'bool',
82
            'boolean',
83
            'int',
84
            'integer',
85
            'float',
86
            'double',
87
            'object',
88
            'resource',
89
            'resource (closed)',
90
            'null',
91
            'unknown type',
92
        ];
93
94
        $argLists = [];
95
96
        foreach ($invalidTypeNames as $invalidTypeName) {
97
            $argLists[] = [
98
                $invalidTypeName,
99
            ];
100
101
            $argLists[] = [
102
                strtoupper($invalidTypeName),
103
            ];
104
        }
105
106
        return $argLists;
107
    }
108
109
    /** @dataProvider providesInvalidTypeNames */
110
    public function testConstructorThrowsAnExceptionIfTheValueOfTheTypeOptionIsInvalid(string $invalidTypeName): void
111
    {
112
        $this->expectException(InvalidArgumentException::class);
113
        $this->expectExceptionMessage('Option `type` is not one of: `NULL`, `"string"`, `"array"`');
114
115
        new InvalidParameterFilter(['ignored'], 'ignored', [
116
            'type' => $invalidTypeName,
117
        ]);
118
    }
119
120
    /** @return array<mixed[]> */
121
    public function providesInvalidSelectors(): array
122
    {
123
        return [[
124
            true,
125
        ], [
126
            123,
127
        ], [
128
            1.23,
129
        ], [
130
            null,
131
        ]];
132
    }
133
134
    /**
135
     * @dataProvider providesInvalidSelectors
136
     * @param mixed $invalidSelector
137
     */
138
    public function testConstructorThrowsAnExceptionIfTheSelectorIsInvalid($invalidSelector): void
139
    {
140
        $this->expectException(InvalidArgumentException::class);
141
        $this->expectExceptionMessage('The selector is invalid.');
142
143
        /** @phpstan-ignore-next-line */
144
        new InvalidParameterFilter($invalidSelector, '//');
145
    }
146
147
    /** @return array<mixed[]> */
148
    public function providesRequestsContainingInvalidParameters(): array
149
    {
150
        $requestFactory = $this->getRequestFactory();
151
152
        $argLists = [[
153
            'requestIsSuspicious' => false,
154
            'request' => $requestFactory->createGet([]),
155
            'selector' => ['id'],  //Specific parameters.
156
            'validator' => '/^\d*$/',
157
            'options' => [],
158
        ], [
159
            'requestIsSuspicious' => false,
160
            'request' => $requestFactory->createPost([]),
161
            'selector' => ['id'],  //Specific parameters.
162
            'validator' => '/^\d*$/',
163
            'options' => [],
164
        ], [
165
            'requestIsSuspicious' => false,
166
            'request' => $requestFactory->createGet(['id' => '123']),
167
            'selector' => ['id'],  //Specific parameters.
168
            'validator' => '/^\d*$/',
169
            'options' => [],
170
        ], [
171
            'requestIsSuspicious' => false,
172
            'request' => $requestFactory->createPost(['id' => '123']),
173
            'selector' => ['id'],  //Specific parameters.
174
            'validator' => '/^\d*$/',
175
            'options' => [],
176
        ], [
177
            'requestIsSuspicious' => false,
178
            'request' => $requestFactory->createGet(['id' => '']),
179
            'selector' => ['id'],  //Specific parameters.
180
            'validator' => '/^\d*$/',
181
            'options' => [],
182
        ], [
183
            'requestIsSuspicious' => false,
184
            'request' => $requestFactory->createPost(['id' => '']),
185
            'selector' => ['id'],  //Specific parameters.
186
            'validator' => '/^\d*$/',
187
            'options' => [],
188
        ], [
189
            'requestIsSuspicious' => false,
190
            'request' => $requestFactory->createGet([]),
191
            'selector' => '/_id$/',  //All parameters with names matching a regex.
192
            'validator' => '/^\d*$/',
193
            'options' => [],
194
        ], [
195
            'requestIsSuspicious' => false,
196
            'request' => $requestFactory->createPost([]),
197
            'selector' => '/_id$/',  //All parameters with names matching a regex.
198
            'validator' => '/^\d*$/',
199
            'options' => [],
200
        ], [
201
            'requestIsSuspicious' => false,
202
            'request' => $requestFactory->createGet(['valid_id' => '123']),
203
            'selector' => '/_id$/',  //All parameters with names matching a regex.
204
            'validator' => '/^\d*$/',
205
            'options' => [],
206
        ], [
207
            'requestIsSuspicious' => false,
208
            'request' => $requestFactory->createPost(['valid_id' => '123']),
209
            'selector' => '/_id$/',  //All parameters with names matching a regex.
210
            'validator' => '/^\d*$/',
211
            'options' => [],
212
        ], [
213
            'requestIsSuspicious' => false,
214
            'request' => $requestFactory->createGet(['id' => ['123', '456']]),  // As in `?id[]=123&id[]=456`
215
            'selector' => ['id'],  //Specific parameters.
216
            'validator' => '/^\d*$/',
217
            'options' => [],
218
        ], [
219
            'requestIsSuspicious' => false,
220
            'request' => $requestFactory->createPost(['id' => ['123', '456']]),  // As in `?id[]=123&id[]=456`.
221
            'selector' => ['id'],  //Specific parameters.
222
            'validator' => '/^\d*$/',
223
            'options' => [],
224
        ], [
225
            'requestIsSuspicious' => false,
226
            'request' => $requestFactory->createGet(['valid_id' => ['123', '456']]),  // As in `?id[]=123&id[]=456`.
227
            'selector' => '/_id$/',  //All parameters with names matching a regex.
228
            'validator' => '/^\d*$/',
229
            'options' => [],
230
        ], [
231
            'requestIsSuspicious' => false,
232
            'request' => $requestFactory->createPost(['valid_id' => ['123', '456']]),  // As in `?id[]=123&id[]=456`.
233
            'selector' => '/_id$/',  //All parameters with names matching a regex.
234
            'validator' => '/^\d*$/',
235
            'options' => [],
236
        ], [
237
            'requestIsSuspicious' => true,
238
            'request' => $requestFactory->createGet(['id' => "71094'A=0"]),
239
            'selector' => ['id'],  //Specific parameters.
240
            'validator' => '/^\d*$/',
241
            'options' => [],
242
        ], [
243
            'requestIsSuspicious' => true,
244
            'request' => $requestFactory->createPost(['id' => "71094'A=0"]),
245
            'selector' => ['id'],  //Specific parameters.
246
            'validator' => '/^\d*$/',
247
            'options' => [],
248
        ], [
249
            'requestIsSuspicious' => true,
250
            'request' => $requestFactory->createGet(['invalid_id' => "71094'A=0"]),
251
            'selector' => '/_id$/',  //All parameters with names matching a regex.
252
            'validator' => '/^\d*$/',
253
            'options' => [],
254
        ], [
255
            'requestIsSuspicious' => true,
256
            'request' => $requestFactory->createPost(['invalid_id' => "71094'A=0"]),
257
            'selector' => '/_id$/',  //All parameters with names matching a regex.
258
            'validator' => '/^\d*$/',
259
            'options' => [],
260
        ], [
261
            'requestIsSuspicious' => true,
262
            'request' => $requestFactory->createGet(['id' => ['123', "71094'A=0"]]),  // As in `?id[]=123&id[]=71094%27A%3D0`.
263
            'selector' => ['id'],  //Specific parameters.
264
            'validator' => '/^\d*$/',
265
            'options' => [],
266
        ], [
267
            'requestIsSuspicious' => true,
268
            'request' => $requestFactory->createPost(['id' => ['123', "71094'A=0"]]),  // As in `?id[]=123&id[]=71094%27A%3D0`.
269
            'selector' => ['id'],  //Specific parameters.
270
            'validator' => '/^\d*$/',
271
            'options' => [],
272
        ], [
273
            'requestIsSuspicious' => true,
274
            'request' => $requestFactory->createGet(['invalid_id' => ['123', "71094'A=0"]]),  // As in `?id[]=123&id[]=71094%27A%3D0`.
275
            'selector' => '/_id$/',  //All parameters with names matching a regex.
276
            'validator' => '/^\d*$/',
277
            'options' => [],
278
        ], [
279
            'requestIsSuspicious' => true,
280
            'request' => $requestFactory->createPost(['invalid_id' => ['123', "71094'A=0"]]),  // As in `?id[]=123&id[]=71094%27A%3D0`.
281
            'selector' => '/_id$/',  //All parameters with names matching a regex.
282
            'validator' => '/^\d*$/',
283
            'options' => [],
284
        ], [
285
            'requestIsSuspicious' => true,
286
            'request' => $requestFactory->createGet(['array_of_valid_ids' => ['123']]),  // As in `?array_of_valid_ids[]=123`.
287
            'selector' => ['array_of_valid_ids'],
288
            'validator' => '/^\d*$/',
289
            'options' => ['type' => 'string'],
290
        ], [
291
            'requestIsSuspicious' => true,
292
            'request' => $requestFactory->createPost(['array_of_valid_ids' => ['123']]),  // As in `?array_of_valid_ids[]=123`.
293
            'selector' => ['array_of_valid_ids'],
294
            'validator' => '/^\d*$/',
295
            'options' => ['type' => 'string'],
296
        ]];
297
298
        $getRequest = $requestFactory->createGet();
299
        $getRequest->query->set('1564736436', '');  // The query string was `?1564736436=`
300
301
        $argLists[] = [
302
            'requestIsSuspicious' => false,
303
            'request' => $getRequest,
304
            'selector' => '/_id$/',  // All parameters with names matching a regex
305
            'validator' => '/^\d*$/',
306
            'options' => [],
307
        ];
308
309
        $postRequest = $requestFactory->createPost();
310
        $postRequest->request->set('1564736436', '');  // The query string was `?1564736436=`
311
312
        $argLists[] = [
313
            'requestIsSuspicious' => false,
314
            'request' => $postRequest,
315
            'selector' => '/_id$/',  // All parameters with names matching a regex
316
            'validator' => '/^\d*$/',
317
            'options' => [],
318
        ];
319
320
        return $argLists;
321
    }
322
323
    /**
324
     * @dataProvider providesRequestsContainingInvalidParameters
325
     * @phpstan-param Selector $selector
326
     * @phpstan-param AugmentedFilterOptions $options
327
     */
328
    public function testInvokeReturnsTrueIfTheValueOfAParameterIsInvalid(
329
        bool $requestIsSuspicious,
330
        Request $request,
331
        $selector,
332
        string $validator,
333
        array $options
334
    ): void {
335
        $envelope = new Envelope($request, new NullLogger());
336
        $filter = new InvalidParameterFilter($selector, $validator, $options);
337
338
        $this->assertSame($requestIsSuspicious, $filter($envelope));
339
    }
340
341
    /** @return array<mixed[]> */
342
    public function providesLogMessages(): array
343
    {
344
        $requestFactory = $this->getRequestFactory();
345
346
        return [
347
            [
348
                'expectedMessage' => 'The value of `query.foo_id` failed validation using the regex `/^\d*$/`.',
349
                'selector' => ['foo_id'],
350
                'validator' => '/^\d*$/',
351
                'options' => [],
352
                'request' => $requestFactory->createGet(['foo_id' => 'bar']),
353
            ],
354
            [
355
                'expectedMessage' => 'The value of `request.foo_id` failed validation using the regex `/^\d*$/`.',
356
                'selector' => ['foo_id'],
357
                'validator' => '/^\d*$/',
358
                'options' => [],
359
                'request' => $requestFactory->createPost(['foo_id' => 'bar']),
360
            ],
361
            [
362
                'expectedMessage' => 'The value of `query.foo_id` failed validation using the regex `/^\d*$/`.',
363
                'selector' => '/_id$/',
364
                'validator' => '/^\d*$/',
365
                'options' => [],
366
                'request' => $requestFactory->createGet(['foo_id' => 'bar']),
367
            ],
368
            [
369
                'expectedMessage' => 'The value of `request.foo_id` failed validation using the regex `/^\d*$/`.',
370
                'selector' => '/_id$/',
371
                'validator' => '/^\d*$/',
372
                'options' => [],
373
                'request' => $requestFactory->createPost(['foo_id' => 'bar']),
374
            ],
375
            [
376
                'expectedMessage' => 'The value of `query.bar` failed validation using the regex `/^[a-z]+$/`.',
377
                'selector' => ['bar'],
378
                'validator' => '/^[a-z]+$/',  //A different regex.
379
                'options' => [],
380
                'request' => $requestFactory->createGet(['bar' => 'BAZ']),
381
            ],
382
            [
383
                'expectedMessage' => 'The value of `request.bar` failed validation using the regex `/^[a-z]+$/`.',
384
                'selector' => ['bar'],
385
                'validator' => '/^[a-z]+$/',  //A different regex.
386
                'options' => [],
387
                'request' => $requestFactory->createPost(['bar' => 'BAZ']),
388
            ],
389
            [
390
                'expectedMessage' => 'The value of `query.foo` is not of type `string`',
391
                'selector' => ['foo'],
392
                'validator' => 'ignored',
393
                'options' => ['type' => 'string'],
394
                'request' => $requestFactory->createGet(['foo' => ['bar', 'baz']]),
395
            ],
396
            [
397
                'expectedMessage' => 'The value of `request.foo` is not of type `string`',
398
                'selector' => ['foo'],
399
                'validator' => 'ignored',
400
                'options' => ['type' => 'string'],
401
                'request' => $requestFactory->createPost(['foo' => ['bar', 'baz']]),
402
            ],
403
            [
404
                'expectedMessage' => 'The value of `query.foo` is not of type `array`',
405
                'selector' => ['foo'],
406
                'validator' => 'ignored',
407
                'options' => ['type' => 'array'],
408
                'request' => $requestFactory->createGet(['foo' => 'bar']),
409
            ],
410
            [
411
                'expectedMessage' => 'The value of `request.foo` is not of type `array`',
412
                'selector' => ['foo'],
413
                'validator' => 'ignored',
414
                'options' => ['type' => 'array'],
415
                'request' => $requestFactory->createPost(['foo' => 'bar']),
416
            ],
417
        ];
418
    }
419
420
    /**
421
     * @dataProvider providesLogMessages
422
     * @phpstan-param Selector $selector
423
     * @phpstan-param AugmentedFilterOptions $options
424
     */
425
    public function testInvokeWillAddALogEntryIfTheRequestIsSuspicious(
426
        string $expectedMessage,
427
        $selector,
428
        string $validator,
429
        array $options,
430
        Request $request
431
    ): void {
432
        $envelope = new Envelope($request, new NullLogger());
433
434
        $filterMock = $this
435
            ->getMockBuilder(InvalidParameterFilter::class)
436
            ->setConstructorArgs([
437
                $selector,
438
                $validator,
439
                $options,
440
            ])
441
            ->onlyMethods(['envelopeAddLogEntry'])
442
            ->getMock()
443
        ;
444
445
        $filterMock
446
            ->expects($this->once())
447
            ->method('envelopeAddLogEntry')
448
            ->with($envelope, $expectedMessage)
449
        ;
450
451
        $this->assertTrue($filterMock($envelope));
452
    }
453
454
    /** @return array<mixed[]> */
455
    public function providesTrustfulRequests(): array
456
    {
457
        $requestFactory = $this->getRequestFactory();
458
459
        return [[
460
            'selector' => ['foo_id'],
461
            'validator' => '/^\d*$/',
462
            'request' => $requestFactory->createGet(['foo_id' => '123']),
463
        ], [
464
            'selector' => ['foo_id'],
465
            'validator' => '/^\d*$/',
466
            'request' => $requestFactory->createPost(['foo_id' => '123']),
467
        ], [
468
            'selector' => '/_id$/',
469
            'validator' => '/^\d*$/',
470
            'request' => $requestFactory->createGet(['foo_id' => '123']),
471
        ], [
472
            'selector' => '/_id$/',
473
            'validator' => '/^\d*$/',
474
            'request' => $requestFactory->createPost(['foo_id' => '123']),
475
        ]];
476
    }
477
478
    /**
479
     * @dataProvider providesTrustfulRequests
480
     * @phpstan-param Selector $selector
481
     */
482
    public function testInvokeWillNotAddALogEntryIfTheRequestIsNotSuspicious(
483
        $selector,
484
        string $validator,
485
        Request $request
486
    ): void {
487
        $envelope = new Envelope($request, new NullLogger());
488
489
        $filterMock = $this
490
            ->getMockBuilder(InvalidParameterFilter::class)
491
            ->setConstructorArgs([
492
                $selector,
493
                $validator,
494
            ])
495
            ->onlyMethods(['envelopeAddLogEntry'])
496
            ->getMock()
497
        ;
498
499
        $filterMock
500
            ->expects($this->never())
501
            ->method('envelopeAddLogEntry')
502
        ;
503
504
        $this->assertFalse($filterMock($envelope));
505
    }
506
}
507