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 29#[CoversClass(Validator::class)] 30class ValidatorTest extends TestCase 31{ 32 public function testAttributes(): void 33 { 34 $request = $this->createMock(ServerRequestInterface::class); 35 $request 36 ->method('getAttributes') 37 ->willReturn(['param' => 'test']); 38 39 self::assertSame('test', Validator::attributes($request)->string('param')); 40 } 41 42 public function testParsedBody(): void 43 { 44 $request = $this->createMock(ServerRequestInterface::class); 45 $request 46 ->method('getParsedBody') 47 ->willReturn(['param' => 'test']); 48 49 self::assertSame('test', Validator::parsedBody($request)->string('param')); 50 } 51 52 public function testQueryParams(): void 53 { 54 $request = $this->createMock(ServerRequestInterface::class); 55 $request 56 ->method('getQueryParams') 57 ->willReturn(['param' => 'test']); 58 59 self::assertSame('test', Validator::queryParams($request)->string('param')); 60 } 61 62 public function testServerParams(): void 63 { 64 $request = $this->createMock(ServerRequestInterface::class); 65 $request 66 ->method('getServerParams') 67 ->willReturn(['param' => 'test']); 68 69 self::assertSame('test', Validator::serverParams($request)->string('param')); 70 } 71 72 public function testNonUTF8QueryParameterName(): void 73 { 74 $request = $this->createMock(ServerRequestInterface::class); 75 $request 76 ->method('getQueryParams') 77 ->willReturn(["\xFF" => 'test']); 78 79 $this->expectException(HttpBadRequestException::class); 80 81 Validator::queryParams($request); 82 } 83 84 public function testNonUTF8QueryParameterValue(): void 85 { 86 $request = $this->createMock(ServerRequestInterface::class); 87 $request 88 ->method('getQueryParams') 89 ->willReturn(['test' => "\xFF"]); 90 91 $this->expectException(HttpBadRequestException::class); 92 93 Validator::queryParams($request); 94 } 95 96 public function testRequiredArrayParameter(): void 97 { 98 $request = $this->createMock(ServerRequestInterface::class); 99 $request 100 ->method('getQueryParams') 101 ->willReturn(['param' => ['test'], 'invalid' => 'not_array']); 102 103 104 self::assertSame(['test'], Validator::queryParams($request)->array('param')); 105 106 $this->expectException(HttpBadRequestException::class); 107 108 Validator::queryParams($request)->array('invalid'); 109 } 110 111 public function testRequiredBooleanParameter(): void 112 { 113 $request = $this->createMock(ServerRequestInterface::class); 114 $request 115 ->method('getQueryParams') 116 ->willReturn([ 117 'a' => '1', 118 'b' => 'on', 119 'c' => true, 120 'd' => '0', 121 'e' => '', 122 'f' => false, 123 ]); 124 125 self::assertSame(true, Validator::queryParams($request)->boolean('a')); 126 self::assertSame(true, Validator::queryParams($request)->boolean('b')); 127 self::assertSame(true, Validator::queryParams($request)->boolean('c')); 128 self::assertSame(false, Validator::queryParams($request)->boolean('d')); 129 self::assertSame(false, Validator::queryParams($request)->boolean('e')); 130 self::assertSame(false, Validator::queryParams($request)->boolean('f')); 131 self::assertSame(false, Validator::queryParams($request)->boolean('g', false)); 132 133 $this->expectException(HttpBadRequestException::class); 134 135 Validator::queryParams($request)->boolean('h'); 136 } 137 138 public function testRequiredIntegerParameter(): void 139 { 140 $request = $this->createMock(ServerRequestInterface::class); 141 $request 142 ->method('getQueryParams') 143 ->willReturn([ 144 'int_type_positive' => 42, 145 'int_type_negative' => -42, 146 'string_type_positive' => '42', 147 'string_type_negative' => '-42', 148 'invalid' => 'not_int', 149 ]); 150 151 self::assertSame(42, Validator::queryParams($request)->integer('int_type_positive')); 152 self::assertSame(-42, Validator::queryParams($request)->integer('int_type_negative')); 153 self::assertSame(42, Validator::queryParams($request)->integer('string_type_positive')); 154 self::assertSame(-42, Validator::queryParams($request)->integer('string_type_negative')); 155 156 $this->expectException(HttpBadRequestException::class); 157 158 Validator::queryParams($request)->integer('invalid'); 159 } 160 161 public function testRequiredRouteParameter(): void 162 { 163 $route = $this->createMock(Route::class); 164 165 $request = $this->createMock(ServerRequestInterface::class); 166 $request 167 ->method('getQueryParams') 168 ->willReturn([ 169 'valid-route' => $route, 170 'not-route' => '', 171 ]); 172 173 self::assertSame($route, Validator::queryParams($request)->route('valid-route')); 174 175 $this->expectException(HttpBadRequestException::class); 176 177 Validator::queryParams($request)->route('not-route'); 178 } 179 180 public function testRequiredStringParameter(): void 181 { 182 $request = $this->createMock(ServerRequestInterface::class); 183 $request 184 ->method('getQueryParams') 185 ->willReturn(['param' => 'test', 'invalid' => ['not_string']]); 186 187 self::assertSame('test', Validator::queryParams($request)->string('param')); 188 189 $this->expectException(HttpBadRequestException::class); 190 191 Validator::queryParams($request)->string('invalid'); 192 } 193 194 public function testRequiredTreeParameter(): void 195 { 196 $tree = $this->createMock(Tree::class); 197 198 $request = $this->createMock(ServerRequestInterface::class); 199 $request 200 ->method('getQueryParams') 201 ->willReturn([ 202 'valid-tree' => $tree, 203 'not-tree' => '', 204 ]); 205 206 self::assertSame($tree, Validator::queryParams($request)->tree('valid-tree')); 207 208 $this->expectException(HttpBadRequestException::class); 209 210 Validator::queryParams($request)->tree('no-tree'); 211 } 212 213 public function testOptionalTreeParameter(): void 214 { 215 $tree = $this->createMock(Tree::class); 216 217 $request = $this->createMock(ServerRequestInterface::class); 218 $request 219 ->method('getQueryParams') 220 ->willReturn([ 221 'valid-tree' => $tree, 222 'not-tree' => '', 223 ]); 224 225 self::assertSame($tree, Validator::queryParams($request)->treeOptional('valid-tree')); 226 self::assertSame(null, Validator::queryParams($request)->treeOptional('missing-tree')); 227 228 $this->expectException(HttpBadRequestException::class); 229 230 Validator::queryParams($request)->treeOptional('not-tree'); 231 } 232 233 public function testRequiredUserParameter(): void 234 { 235 $user = $this->createMock(UserInterface::class); 236 237 $request = $this->createMock(ServerRequestInterface::class); 238 $request 239 ->method('getQueryParams') 240 ->willReturn([ 241 'valid-user' => $user, 242 'not-user' => '', 243 ]); 244 245 self::assertSame($user, Validator::queryParams($request)->user('valid-user')); 246 247 $this->expectException(HttpBadRequestException::class); 248 249 Validator::queryParams($request)->user('not-user'); 250 } 251 252 public function testIsBetweenParameter(): void 253 { 254 $request = $this->createMock(ServerRequestInterface::class); 255 $request 256 ->method('getQueryParams') 257 ->willReturn(['param' => '42', 'invalid' => '10', 'wrongtype' => 'not_integer']); 258 259 self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('param')); 260 self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('invalid', 42)); 261 self::assertSame(42, Validator::queryParams($request)->isBetween(40, 45)->integer('wrongtype', 42)); 262 } 263 264 public function testIsInArray(): void 265 { 266 $request = $this->createMock(ServerRequestInterface::class); 267 $request 268 ->method('getQueryParams') 269 ->willReturn(['param' => 'foo']); 270 271 self::assertSame('foo', Validator::queryParams($request)->isInArray(['foo', 'bar'])->string('param')); 272 273 $this->expectException(HttpBadRequestException::class); 274 275 Validator::queryParams($request)->isInArray(['baz'])->string('param'); 276 } 277 278 public function testIsInArrayKeys(): void 279 { 280 $request = $this->createMock(ServerRequestInterface::class); 281 $request 282 ->method('getQueryParams') 283 ->willReturn(['param' => 'foo']); 284 285 self::assertSame('foo', Validator::queryParams($request)->isInArrayKeys(['foo' => 1, 'bar' => 2])->string('param')); 286 287 $this->expectException(HttpBadRequestException::class); 288 289 Validator::queryParams($request)->isInArrayKeys(['baz' => 3])->string('param'); 290 } 291 292 public function testIsNotEmpty(): void 293 { 294 $request = $this->createMock(ServerRequestInterface::class); 295 $request 296 ->method('getQueryParams') 297 ->willReturn(['empty' => '', 'not-empty' => 'foo']); 298 299 self::assertSame('foo', Validator::queryParams($request)->isNotEmpty()->string('not-empty')); 300 301 $this->expectException(HttpBadRequestException::class); 302 303 Validator::queryParams($request)->isNotEmpty()->string('empty'); 304 } 305 306 public function testIsTagParameter(): void 307 { 308 $request = $this->createMock(ServerRequestInterface::class); 309 $request 310 ->method('getQueryParams') 311 ->willReturn(['valid' => 'BIRT', 'invalid' => '@X1@']); 312 313 self::assertSame('BIRT', Validator::queryParams($request)->isTag()->string('valid')); 314 315 $this->expectException(HttpBadRequestException::class); 316 317 Validator::queryParams($request)->isTag()->string('invalid'); 318 } 319 320 public function testIsXrefParameter(): void 321 { 322 $request = $this->createMock(ServerRequestInterface::class); 323 $request 324 ->method('getQueryParams') 325 ->willReturn(['valid' => 'X1', 'invalid' => '@X1@', 'valid-array' => ['X1'], 'invalid-array' => ['@X1@']]); 326 327 self::assertSame('X1', Validator::queryParams($request)->isXref()->string('valid')); 328 self::assertSame(['X1'], Validator::queryParams($request)->isXref()->array('valid-array')); 329 self::assertSame([], Validator::queryParams($request)->isXref()->array('invalid-array')); 330 331 $this->expectException(HttpBadRequestException::class); 332 333 Validator::queryParams($request)->isXref()->string('invalid'); 334 } 335 336 public function testIsLocalUrlParameter(): void 337 { 338 $request = $this->createMock(ServerRequestInterface::class); 339 $request 340 ->method('getAttribute') 341 ->with('base_url')->willReturn('http://example.local/wt'); 342 $request 343 ->method('getQueryParams') 344 ->willReturn(['param' => 'http://example.local/wt/page', 'noscheme' => '//example.local/wt/page']); 345 346 347 self::assertSame('http://example.local/wt/page', Validator::queryParams($request)->isLocalUrl()->string('param')); 348 self::assertSame('//example.local/wt/page', Validator::queryParams($request)->isLocalUrl()->string('noscheme')); 349 } 350 351 public function testIsLocalUrlParameterWrongScheme(): void 352 { 353 $request = $this->createMock(ServerRequestInterface::class); 354 $request 355 ->method('getAttribute') 356 ->with('base_url') 357 ->willReturn('http://example.local/wt'); 358 $request 359 ->method('getQueryParams') 360 ->willReturn(['https' => 'https://example.local/wt/page']); 361 362 $this->expectException(HttpBadRequestException::class); 363 364 Validator::queryParams($request)->isLocalUrl()->string('https'); 365 } 366 367 public function testIsLocalUrlParameterWrongDomain(): void 368 { 369 $request = $this->createMock(ServerRequestInterface::class); 370 $request 371 ->method('getAttribute') 372 ->with('base_url') 373 ->willReturn('http://example.local/wt'); 374 $request 375 ->method('getQueryParams') 376 ->willReturn(['invalid' => 'http://example.com/wt/page']); 377 378 $this->expectException(HttpBadRequestException::class); 379 380 Validator::queryParams($request)->isLocalUrl()->string('invalid'); 381 } 382 383 public function testIsLocalUrlParameterWrongType(): void 384 { 385 $request = $this->createMock(ServerRequestInterface::class); 386 $request 387 ->method('getQueryParams') 388 ->willReturn(['wrongtype' => ['42']]); 389 390 $this->expectException(HttpBadRequestException::class); 391 392 Validator::queryParams($request)->isLocalUrl()->isLocalUrl()->string('wrongtype'); 393 } 394} 395