xref: /webtrees/tests/app/ValidatorTest.php (revision 1270d2767576ed4a83917769b0ee3613e3b010bf)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2023 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees;
21
22use Aura\Router\Route;
23use Fisharebest\Webtrees\Contracts\UserInterface;
24use Fisharebest\Webtrees\Http\Exceptions\HttpBadRequestException;
25use PHPUnit\Framework\Attributes\CoversClass;
26use Psr\Http\Message\ServerRequestInterface;
27
28#[CoversClass(Validator::class)]
29class ValidatorTest extends TestCase
30{
31    public function testAttributes(): void
32    {
33        $request = $this->createMock(ServerRequestInterface::class);
34        $request
35            ->method('getAttributes')
36            ->willReturn(['param' => 'test']);
37
38        self::assertSame('test', Validator::attributes($request)->string('param'));
39    }
40
41    public function testParsedBody(): void
42    {
43        $request = $this->createMock(ServerRequestInterface::class);
44        $request
45            ->method('getParsedBody')
46            ->willReturn(['param' => 'test']);
47
48        self::assertSame('test', Validator::parsedBody($request)->string('param'));
49    }
50
51    public function testQueryParams(): void
52    {
53        $request = $this->createMock(ServerRequestInterface::class);
54        $request
55            ->method('getQueryParams')
56            ->willReturn(['param' => 'test']);
57
58        self::assertSame('test', Validator::queryParams($request)->string('param'));
59    }
60
61    public function testServerParams(): void
62    {
63        $request = $this->createMock(ServerRequestInterface::class);
64        $request
65            ->method('getServerParams')
66            ->willReturn(['param' => 'test']);
67
68        self::assertSame('test', Validator::serverParams($request)->string('param'));
69    }
70
71    public function testNonUTF8QueryParameterName(): void
72    {
73        $request = $this->createMock(ServerRequestInterface::class);
74        $request
75            ->method('getQueryParams')
76            ->willReturn(["\xFF" => 'test']);
77
78        $this->expectException(HttpBadRequestException::class);
79
80        Validator::queryParams($request);
81    }
82
83    public function testNonUTF8QueryParameterValue(): void
84    {
85        $request = $this->createMock(ServerRequestInterface::class);
86        $request
87            ->method('getQueryParams')
88            ->willReturn(['test' => "\xFF"]);
89
90        $this->expectException(HttpBadRequestException::class);
91
92        Validator::queryParams($request);
93    }
94
95    public function testRequiredArrayParameter(): void
96    {
97        $request = $this->createMock(ServerRequestInterface::class);
98        $request
99            ->method('getQueryParams')
100            ->willReturn(['param' => ['test'], 'invalid' => 'not_array']);
101
102        self::assertSame(['test'], Validator::queryParams($request)->array('param'));
103
104        $this->expectException(HttpBadRequestException::class);
105
106        Validator::queryParams($request)->array('invalid');
107    }
108
109    public function testRequiredBooleanParameter(): void
110    {
111        $request = $this->createMock(ServerRequestInterface::class);
112        $request
113            ->method('getQueryParams')
114            ->willReturn([
115                'a'    => '1',
116                'b'    => 'on',
117                'c'    => true,
118                'd' => '0',
119                'e' => '',
120                'f' => false,
121            ]);
122
123        self::assertTrue(Validator::queryParams($request)->boolean('a'));
124        self::assertTrue(Validator::queryParams($request)->boolean('b'));
125        self::assertTrue(Validator::queryParams($request)->boolean('c'));
126        self::assertFalse(Validator::queryParams($request)->boolean('d'));
127        self::assertFalse(Validator::queryParams($request)->boolean('e'));
128        self::assertFalse(Validator::queryParams($request)->boolean('f'));
129        self::assertFalse(Validator::queryParams($request)->boolean('g', false));
130
131        $this->expectException(HttpBadRequestException::class);
132
133        Validator::queryParams($request)->boolean('h');
134    }
135
136    public function testRequiredIntegerParameter(): void
137    {
138        $request = $this->createMock(ServerRequestInterface::class);
139        $request
140            ->method('getQueryParams')
141            ->willReturn([
142                'int_type_positive'    => 42,
143                'int_type_negative'    => -42,
144                'string_type_positive' => '42',
145                'string_type_negative' => '-42',
146                'invalid'              => 'not_int',
147            ]);
148
149        self::assertSame(42, Validator::queryParams($request)->integer('int_type_positive'));
150        self::assertSame(-42, Validator::queryParams($request)->integer('int_type_negative'));
151        self::assertSame(42, Validator::queryParams($request)->integer('string_type_positive'));
152        self::assertSame(-42, Validator::queryParams($request)->integer('string_type_negative'));
153
154        $this->expectException(HttpBadRequestException::class);
155
156        Validator::queryParams($request)->integer('invalid');
157    }
158
159    public function testRequiredRouteParameter(): void
160    {
161        $route = $this->createMock(Route::class);
162
163        $request = $this->createMock(ServerRequestInterface::class);
164        $request
165            ->method('getQueryParams')
166            ->willReturn([
167                'valid-route' => $route,
168                'not-route'   => '',
169            ]);
170
171        self::assertSame($route, Validator::queryParams($request)->route('valid-route'));
172
173        $this->expectException(HttpBadRequestException::class);
174
175        Validator::queryParams($request)->route('not-route');
176    }
177
178    public function testRequiredStringParameter(): void
179    {
180        $request = $this->createMock(ServerRequestInterface::class);
181        $request
182            ->method('getQueryParams')
183            ->willReturn(['param' => 'test', 'invalid' => ['not_string']]);
184
185        self::assertSame('test', Validator::queryParams($request)->string('param'));
186
187        $this->expectException(HttpBadRequestException::class);
188
189        Validator::queryParams($request)->string('invalid');
190    }
191
192    public function testRequiredTreeParameter(): void
193    {
194        $tree = $this->createMock(Tree::class);
195
196        $request = $this->createMock(ServerRequestInterface::class);
197        $request
198            ->method('getQueryParams')
199            ->willReturn([
200                'valid-tree' => $tree,
201                'not-tree'   => '',
202            ]);
203
204        self::assertSame($tree, Validator::queryParams($request)->tree('valid-tree'));
205
206        $this->expectException(HttpBadRequestException::class);
207
208        Validator::queryParams($request)->tree('no-tree');
209    }
210
211    public function testOptionalTreeParameter(): void
212    {
213        $tree = $this->createMock(Tree::class);
214
215        $request = $this->createMock(ServerRequestInterface::class);
216        $request
217            ->method('getQueryParams')
218            ->willReturn([
219                'valid-tree' => $tree,
220                'not-tree'   => '',
221            ]);
222
223        self::assertSame($tree, Validator::queryParams($request)->treeOptional('valid-tree'));
224        self::assertNull(Validator::queryParams($request)->treeOptional('missing-tree'));
225
226        $this->expectException(HttpBadRequestException::class);
227
228        Validator::queryParams($request)->treeOptional('not-tree');
229    }
230
231    public function testRequiredUserParameter(): void
232    {
233        $user = $this->createMock(UserInterface::class);
234
235        $request = $this->createMock(ServerRequestInterface::class);
236        $request
237            ->method('getQueryParams')
238            ->willReturn([
239                'valid-user' => $user,
240                'not-user'   => '',
241            ]);
242
243        self::assertSame($user, Validator::queryParams($request)->user('valid-user'));
244
245        $this->expectException(HttpBadRequestException::class);
246
247        Validator::queryParams($request)->user('not-user');
248    }
249
250    public function testIsBetweenParameter(): void
251    {
252        $request = $this->createMock(ServerRequestInterface::class);
253        $request
254            ->method('getQueryParams')
255            ->willReturn(['param' => '42', 'invalid' => '10', 'wrongtype' => 'not_integer']);
256
257        self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('param'));
258        self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('invalid', 42));
259        self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('wrongtype', 42));
260    }
261
262    public function testIsInArray(): void
263    {
264        $request = $this->createMock(ServerRequestInterface::class);
265        $request
266            ->method('getQueryParams')
267            ->willReturn(['param' => 'foo']);
268
269        self::assertSame('foo', Validator::queryParams($request)->isInArray(['foo', 'bar'])->string('param'));
270
271        $this->expectException(HttpBadRequestException::class);
272
273        Validator::queryParams($request)->isInArray(['baz'])->string('param');
274    }
275
276    public function testIsInArrayKeys(): void
277    {
278        $request = $this->createMock(ServerRequestInterface::class);
279        $request
280            ->method('getQueryParams')
281            ->willReturn(['param' => 'foo']);
282
283        self::assertSame('foo', Validator::queryParams($request)->isInArrayKeys(['foo' => 1, 'bar' => 2])->string('param'));
284
285        $this->expectException(HttpBadRequestException::class);
286
287        Validator::queryParams($request)->isInArrayKeys(['baz' => 3])->string('param');
288    }
289
290    public function testIsNotEmpty(): void
291    {
292        $request = $this->createMock(ServerRequestInterface::class);
293        $request
294            ->method('getQueryParams')
295            ->willReturn(['empty' => '', 'not-empty' => 'foo']);
296
297        self::assertSame('foo', Validator::queryParams($request)->isNotEmpty()->string('not-empty'));
298
299        $this->expectException(HttpBadRequestException::class);
300
301        Validator::queryParams($request)->isNotEmpty()->string('empty');
302    }
303
304    public function testIsTagParameter(): void
305    {
306        $request = $this->createMock(ServerRequestInterface::class);
307        $request
308            ->method('getQueryParams')
309            ->willReturn(['valid' => 'BIRT', 'invalid' => '@X1@']);
310
311        self::assertSame('BIRT', Validator::queryParams($request)->isTag()->string('valid'));
312
313        $this->expectException(HttpBadRequestException::class);
314
315        Validator::queryParams($request)->isTag()->string('invalid');
316    }
317
318    public function testIsXrefParameter(): void
319    {
320        $request = $this->createMock(ServerRequestInterface::class);
321        $request
322            ->method('getQueryParams')
323            ->willReturn(['valid' => 'X1', 'invalid' => '@X1@', 'valid-array' => ['X1'], 'invalid-array' => ['@X1@']]);
324
325        self::assertSame('X1', Validator::queryParams($request)->isXref()->string('valid'));
326        self::assertSame(['X1'], Validator::queryParams($request)->isXref()->array('valid-array'));
327        self::assertSame([], Validator::queryParams($request)->isXref()->array('invalid-array'));
328
329        $this->expectException(HttpBadRequestException::class);
330
331        Validator::queryParams($request)->isXref()->string('invalid');
332    }
333
334    public function testIsLocalUrlParameter(): void
335    {
336        $request = $this->createMock(ServerRequestInterface::class);
337        $request
338            ->method('getAttribute')
339            ->with('base_url')->willReturn('http://example.local/wt');
340        $request
341            ->method('getQueryParams')
342            ->willReturn(['param' => 'http://example.local/wt/page', 'noscheme' => '//example.local/wt/page']);
343
344        self::assertSame('http://example.local/wt/page', Validator::queryParams($request)->isLocalUrl()->string('param'));
345        self::assertSame('//example.local/wt/page', Validator::queryParams($request)->isLocalUrl()->string('noscheme'));
346    }
347
348    public function testIsLocalUrlParameterWrongScheme(): void
349    {
350        $request = $this->createMock(ServerRequestInterface::class);
351        $request
352            ->method('getAttribute')
353            ->with('base_url')
354            ->willReturn('http://example.local/wt');
355        $request
356            ->method('getQueryParams')
357            ->willReturn(['https' => 'https://example.local/wt/page']);
358
359        $this->expectException(HttpBadRequestException::class);
360
361        Validator::queryParams($request)->isLocalUrl()->string('https');
362    }
363
364    public function testIsLocalUrlParameterWrongDomain(): void
365    {
366        $request = $this->createMock(ServerRequestInterface::class);
367        $request
368            ->method('getAttribute')
369            ->with('base_url')
370            ->willReturn('http://example.local/wt');
371        $request
372            ->method('getQueryParams')
373            ->willReturn(['invalid' => 'http://example.com/wt/page']);
374
375        $this->expectException(HttpBadRequestException::class);
376
377        Validator::queryParams($request)->isLocalUrl()->string('invalid');
378    }
379
380    public function testIsLocalUrlParameterWrongType(): void
381    {
382        $request = $this->createMock(ServerRequestInterface::class);
383        $request
384            ->method('getQueryParams')
385            ->willReturn(['wrongtype' => ['42']]);
386
387        $this->expectException(HttpBadRequestException::class);
388
389        Validator::queryParams($request)->isLocalUrl()->isLocalUrl()->string('wrongtype');
390    }
391}
392