See Release Notes
Long Term Support Release
Differences Between: [Versions 39 and 311] [Versions 39 and 400] [Versions 39 and 401] [Versions 39 and 402] [Versions 39 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Unit tests for core_table\local\filter\filterset. 19 * 20 * @package core_table 21 * @category test 22 * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 24 */ 25 26 declare(strict_types=1); 27 28 namespace core_table\local\filter; 29 30 use InvalidArgumentException; 31 use UnexpectedValueException; 32 use advanced_testcase; 33 use moodle_exception; 34 35 /** 36 * Unit tests for core_table\local\filter\filterset. 37 * 38 * @package core_table 39 * @category test 40 * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk> 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class filterset_test extends advanced_testcase { 44 /** 45 * Ensure that it is possibly to set the join type. 46 */ 47 public function test_set_join_type(): void { 48 $filterset = $this->get_mocked_filterset(); 49 50 // Initial set with the default type should just work. 51 // The setter should be chainable. 52 $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_DEFAULT)); 53 $this->assertEquals(filterset::JOINTYPE_DEFAULT, $filterset->get_join_type()); 54 55 // It should be possible to update the join type later. 56 $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_NONE)); 57 $this->assertEquals(filterset::JOINTYPE_NONE, $filterset->get_join_type()); 58 59 $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_ANY)); 60 $this->assertEquals(filterset::JOINTYPE_ANY, $filterset->get_join_type()); 61 62 $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_ALL)); 63 $this->assertEquals(filterset::JOINTYPE_ALL, $filterset->get_join_type()); 64 } 65 66 /** 67 * Ensure that it is not possible to provide a value out of bounds when setting the join type. 68 */ 69 public function test_set_join_type_invalid_low(): void { 70 $filterset = $this->get_mocked_filterset(); 71 72 // Valid join types are current 0, 1, or 2. 73 // A value too low should be rejected. 74 $this->expectException(InvalidArgumentException::class); 75 $this->expectExceptionMessage("Invalid join type specified"); 76 $filterset->set_join_type(-1); 77 } 78 79 /** 80 * Ensure that it is not possible to provide a value out of bounds when setting the join type. 81 */ 82 public function test_set_join_type_invalid_high(): void { 83 $filterset = $this->get_mocked_filterset(); 84 85 // Valid join types are current 0, 1, or 2. 86 // A value too low should be rejected. 87 $this->expectException(InvalidArgumentException::class); 88 $this->expectExceptionMessage("Invalid join type specified"); 89 $filterset->set_join_type(4); 90 } 91 92 /** 93 * Ensure that adding filter values works as expected. 94 */ 95 public function test_add_filter_value(): void { 96 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 97 $filterset->method('get_optional_filters') 98 ->will($this->returnValue([ 99 'name' => filter::class, 100 'species' => filter::class, 101 ])); 102 103 // Initially an empty list. 104 $this->assertEmpty($filterset->get_filters()); 105 106 // Test data. 107 $speciesfilter = new filter('species', null, ['canine']); 108 $namefilter = new filter('name', null, ['rosie']); 109 110 // Add a filter to the list. 111 $filterset->add_filter($speciesfilter); 112 $this->assertSame([ 113 $speciesfilter, 114 ], array_values($filterset->get_filters())); 115 116 // Adding a second value should add that value. 117 // The values should sorted. 118 $filterset->add_filter($namefilter); 119 $this->assertSame([ 120 $namefilter, 121 $speciesfilter, 122 ], array_values($filterset->get_filters())); 123 124 // Adding an existing filter again should be ignored. 125 $filterset->add_filter($speciesfilter); 126 $this->assertSame([ 127 $namefilter, 128 $speciesfilter, 129 ], array_values($filterset->get_filters())); 130 } 131 132 /** 133 * Ensure that it is possible to add a filter of a validated filter type. 134 */ 135 public function test_add_filter_validated_type(): void { 136 $namefilter = $this->getMockBuilder(filter::class) 137 ->setConstructorArgs(['name']) 138 ->setMethods(null) 139 ->getMock(); 140 $namefilter->add_filter_value('rosie'); 141 142 // Mock the get_optional_filters function. 143 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 144 $filterset->method('get_optional_filters') 145 ->will($this->returnValue([ 146 'name' => get_class($namefilter), 147 ])); 148 149 // Add a filter to the list. 150 // This is the 'name' filter. 151 $filterset->add_filter($namefilter); 152 153 $this->assertNull($filterset->check_validity()); 154 } 155 156 /** 157 * Ensure that it is not possible to add a type which is not expected. 158 */ 159 public function test_add_filter_unexpected_key(): void { 160 // Mock the get_optional_filters function. 161 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 162 $filterset->method('get_optional_filters') 163 ->will($this->returnValue([])); 164 165 // Add a filter to the list. 166 // This is the 'name' filter. 167 $namefilter = new filter('name'); 168 169 $this->expectException(InvalidArgumentException::class); 170 $this->expectExceptionMessage("The filter 'name' was not recognised."); 171 $filterset->add_filter($namefilter); 172 } 173 174 /** 175 * Ensure that it is not possible to add a validated type where the type is incorrect. 176 */ 177 public function test_add_filter_validated_type_incorrect(): void { 178 $filtername = "name"; 179 $otherfilter = $this->createMock(filter::class); 180 181 // Mock the get_optional_filters function. 182 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 183 $filterset->method('get_optional_filters') 184 ->will($this->returnValue([ 185 $filtername => get_class($otherfilter), 186 ])); 187 188 // Add a filter to the list. 189 // This is the 'name' filter. 190 $namefilter = $this->getMockBuilder(filter::class) 191 ->setMethods(null) 192 ->setConstructorArgs([$filtername]) 193 ->getMock(); 194 195 $actualtype = get_class($namefilter); 196 $requiredtype = get_class($otherfilter); 197 $this->expectException(InvalidArgumentException::class); 198 $this->expectExceptionMessage( 199 "The filter '{$filtername}' was incorrectly specified as a {$actualtype}. It must be a {$requiredtype}." 200 ); 201 $filterset->add_filter($namefilter); 202 } 203 204 /** 205 * Ensure that a filter can be added from parameters provided to a web service. 206 */ 207 public function test_add_filter_from_params(): void { 208 $filtername = "name"; 209 $otherfilter = $this->getMockBuilder(filter::class) 210 ->setMethods(null) 211 ->setConstructorArgs([$filtername]) 212 ->getMock(); 213 214 // Mock the get_optional_filters function. 215 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 216 $filterset->method('get_optional_filters') 217 ->will($this->returnValue([ 218 $filtername => get_class($otherfilter), 219 ])); 220 221 $result = $filterset->add_filter_from_params($filtername, filter::JOINTYPE_DEFAULT, ['kevin']); 222 223 // The function is chainable. 224 $this->assertEquals($filterset, $result); 225 226 // Get the filter back. 227 $filter = $filterset->get_filter($filtername); 228 $this->assertEquals($filtername, $filter->get_name()); 229 $this->assertEquals(filter::JOINTYPE_DEFAULT, $filter->get_join_type()); 230 $this->assertEquals(['kevin'], $filter->get_filter_values()); 231 } 232 233 /** 234 * Ensure that an unknown filter is not added. 235 */ 236 public function test_add_filter_from_params_unable_to_autoload(): void { 237 // Mock the get_optional_filters function. 238 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 239 $filterset->method('get_optional_filters') 240 ->will($this->returnValue([ 241 'name' => '\\moodle\\this\\is\\a\\fake\\class\\name', 242 ])); 243 244 $this->expectException(InvalidArgumentException::class); 245 $this->expectExceptionMessage( 246 "The filter class '\\moodle\\this\\is\\a\\fake\\class\\name' for filter 'name' could not be found." 247 ); 248 $filterset->add_filter_from_params('name', filter::JOINTYPE_DEFAULT, ['kevin']); 249 } 250 251 /** 252 * Ensure that an unknown filter is not added. 253 */ 254 public function test_add_filter_from_params_invalid(): void { 255 $filtername = "name"; 256 $otherfilter = $this->getMockBuilder(filter::class) 257 ->setMethods(null) 258 ->setConstructorArgs([$filtername]) 259 ->getMock(); 260 261 // Mock the get_optional_filters function. 262 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 263 $filterset->method('get_optional_filters') 264 ->will($this->returnValue([ 265 $filtername => get_class($otherfilter), 266 ])); 267 268 $this->expectException(InvalidArgumentException::class); 269 $this->expectExceptionMessage("The filter 'unknownfilter' was not recognised."); 270 $filterset->add_filter_from_params('unknownfilter', filter::JOINTYPE_DEFAULT, ['kevin']); 271 } 272 273 /** 274 * Ensure that adding a different filter with a different object throws an Exception. 275 */ 276 public function test_duplicate_filter_value(): void { 277 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 278 $filterset->method('get_optional_filters') 279 ->will($this->returnValue([ 280 'name' => filter::class, 281 'species' => filter::class, 282 ])); 283 284 // Add a filter to the list. 285 // This is the 'name' filter. 286 $namefilter = new filter('name', null, ['rosie']); 287 $filterset->add_filter($namefilter); 288 289 // Add another filter to the list. 290 // This one has been incorrectly called the 'name' filter when it should be 'species'. 291 $this->expectException(UnexpectedValueException::Class); 292 $this->expectExceptionMessage("A filter of type 'name' has already been added. Check that you have the correct filter."); 293 294 $speciesfilter = new filter('name', null, ['canine']); 295 $filterset->add_filter($speciesfilter); 296 } 297 298 /** 299 * Ensure that validating a filterset correctly compares filter types. 300 */ 301 public function test_check_validity_optional_filters_not_specified(): void { 302 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 303 $filterset->method('get_optional_filters') 304 ->will($this->returnValue([ 305 'name' => filter::class, 306 'species' => filter::class, 307 ])); 308 309 $this->assertNull($filterset->check_validity()); 310 } 311 312 /** 313 * Ensure that validating a filterset correctly requires required filters. 314 */ 315 public function test_check_validity_required_filter(): void { 316 $filterset = $this->get_mocked_filterset(['get_required_filters']); 317 $filterset->expects($this->any()) 318 ->method('get_required_filters') 319 ->willReturn([ 320 'name' => filter::class 321 ]); 322 323 // Add a filter to the list. 324 // This is the 'name' filter. 325 $filterset->add_filter(new filter('name')); 326 327 $this->assertNull($filterset->check_validity()); 328 } 329 330 /** 331 * Ensure that validating a filterset excepts correctly when a required fieldset is missing. 332 */ 333 public function test_check_validity_filter_missing_required(): void { 334 $filterset = $this->get_mocked_filterset(['get_required_filters']); 335 $filterset->expects($this->any()) 336 ->method('get_required_filters') 337 ->willReturn([ 338 'name' => filter::class, 339 'species' => filter::class, 340 ]); 341 342 $this->expectException(moodle_exception::Class); 343 $this->expectExceptionMessage("One or more required filters were missing (name, species)"); 344 $filterset->check_validity(); 345 } 346 347 /** 348 * Ensure that getting the filters returns a sorted list of filters. 349 */ 350 public function test_get_filters(): void { 351 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 352 $filterset->method('get_optional_filters') 353 ->will($this->returnValue([ 354 // Filters are not defined lexically. 355 'd' => filter::class, 356 'b' => filter::class, 357 'a' => filter::class, 358 'c' => filter::class, 359 ])); 360 361 // Filters are added in a different non-lexical order. 362 $c = new filter('c'); 363 $filterset->add_filter($c); 364 365 $b = new filter('b'); 366 $filterset->add_filter($b); 367 368 $d = new filter('d'); 369 $filterset->add_filter($d); 370 371 $a = new filter('a'); 372 $filterset->add_filter($a); 373 374 // But they are returned lexically sorted. 375 $this->assertEquals([ 376 'a' => $a, 377 'b' => $b, 378 'c' => $c, 379 'd' => $d, 380 ], $filterset->get_filters()); 381 } 382 383 /** 384 * Ensure that getting a singlel filter returns the correct filter. 385 */ 386 public function test_get_filter(): void { 387 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 388 $filterset->method('get_optional_filters') 389 ->will($this->returnValue([ 390 // Filters are not defined lexically. 391 'd' => filter::class, 392 'b' => filter::class, 393 'a' => filter::class, 394 'c' => filter::class, 395 ])); 396 397 // Filters are added in a different non-lexical order. 398 $c = new filter('c'); 399 $filterset->add_filter($c); 400 401 $b = new filter('b'); 402 $filterset->add_filter($b); 403 404 $d = new filter('d'); 405 $filterset->add_filter($d); 406 407 $a = new filter('a'); 408 $filterset->add_filter($a); 409 410 // Filters can be individually retrieved in any order. 411 $this->assertEquals($d, $filterset->get_filter('d')); 412 $this->assertEquals($a, $filterset->get_filter('a')); 413 $this->assertEquals($b, $filterset->get_filter('b')); 414 $this->assertEquals($c, $filterset->get_filter('c')); 415 } 416 417 /** 418 * Ensure that it is not possible to retrieve an unknown filter. 419 */ 420 public function test_get_filter_unknown(): void { 421 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 422 $filterset->method('get_optional_filters') 423 ->will($this->returnValue([ 424 'a' => filter::class, 425 ])); 426 427 $a = new filter('a'); 428 $filterset->add_filter($a); 429 430 $this->expectException(UnexpectedValueException::Class); 431 $this->expectExceptionMessage("The filter specified (d) is invalid."); 432 $filterset->get_filter('d'); 433 } 434 435 /** 436 * Ensure that it is not possible to retrieve a valid filter before it is created. 437 */ 438 public function test_get_filter_not_yet_added(): void { 439 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 440 $filterset->method('get_optional_filters') 441 ->will($this->returnValue([ 442 'a' => filter::class, 443 ])); 444 445 $this->expectException(UnexpectedValueException::Class); 446 $this->expectExceptionMessage("The filter specified (a) has not been created."); 447 $filterset->get_filter('a'); 448 } 449 450 /** 451 * Ensure that the get_all_filtertypes function correctly returns the combined filterset. 452 */ 453 public function test_get_all_filtertypes(): void { 454 $otherfilter = $this->createMock(filter::class); 455 456 $filterset = $this->get_mocked_filterset([ 457 'get_optional_filters', 458 'get_required_filters', 459 ]); 460 $filterset->method('get_optional_filters') 461 ->will($this->returnValue([ 462 'a' => filter::class, 463 'c' => get_class($otherfilter), 464 ])); 465 $filterset->method('get_required_filters') 466 ->will($this->returnValue([ 467 'b' => get_class($otherfilter), 468 'd' => filter::class, 469 ])); 470 471 $this->assertEquals([ 472 'a' => filter::class, 473 'b' => get_class($otherfilter), 474 'c' => get_class($otherfilter), 475 'd' => filter::class, 476 ], $filterset->get_all_filtertypes()); 477 } 478 479 /** 480 * Ensure that the get_all_filtertypes function correctly returns the combined filterset. 481 */ 482 public function test_get_all_filtertypes_conflict(): void { 483 $otherfilter = $this->createMock(filter::class); 484 485 $filterset = $this->get_mocked_filterset([ 486 'get_optional_filters', 487 'get_required_filters', 488 ]); 489 $filterset->method('get_optional_filters') 490 ->will($this->returnValue([ 491 'a' => filter::class, 492 'b' => get_class($otherfilter), 493 'd' => filter::class, 494 ])); 495 $filterset->method('get_required_filters') 496 ->will($this->returnValue([ 497 'b' => get_class($otherfilter), 498 'c' => filter::class, 499 'd' => filter::class, 500 ])); 501 502 $this->expectException(InvalidArgumentException::Class); 503 $this->expectExceptionMessage("Some filter types are both required, and optional: b, d"); 504 $filterset->get_all_filtertypes(); 505 506 } 507 508 /** 509 * Ensure that the has_filter function works as expected. 510 */ 511 public function test_has_filter(): void { 512 $filterset = $this->get_mocked_filterset(['get_optional_filters']); 513 $filterset->method('get_optional_filters') 514 ->will($this->returnValue([ 515 // Define filters 'a', and 'b'. 516 'a' => filter::class, 517 'b' => filter::class, 518 ])); 519 520 // Only add filter 'a'. 521 $a = new filter('a'); 522 $filterset->add_filter($a); 523 524 // Filter 'a' should exist. 525 $this->assertTrue($filterset->has_filter('a')); 526 527 // Filter 'b' is defined, but has not been added. 528 $this->assertFalse($filterset->has_filter('b')); 529 530 // Filter 'c' is not defined. 531 // No need to throw any kind of exception - this is an existence check. 532 $this->assertFalse($filterset->has_filter('c')); 533 } 534 535 /** 536 * Get a mocked copy of the filterset, mocking the specified methods. 537 * 538 * @param array $mockedmethods anonymous array containing the list of mocked methods 539 * @return filterset Mock of the filterset 540 */ 541 protected function get_mocked_filterset(array $mockedmethods = null): filterset { 542 if (empty($mockedmethods)) { 543 $mockedmethods = null; 544 } 545 546 return $this->getMockForAbstractClass(filterset::class, [], '', true, true, true, $mockedmethods); 547 } 548 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body