See Release Notes
Long Term Support Release
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Unit tests for core_table\local\filter\filterset. * * @package core_table * @category test * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU Public License */ declare(strict_types=1); namespace core_table\local\filter; use InvalidArgumentException; use UnexpectedValueException; use advanced_testcase; use moodle_exception; /** * Unit tests for core_table\local\filter\filterset. * * @package core_table * @category test * @copyright 2020 Andrew Nicols <andrew@nicols.co.uk> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class filterset_test extends advanced_testcase { /** * Ensure that it is possibly to set the join type. */ public function test_set_join_type(): void { $filterset = $this->get_mocked_filterset(); // Initial set with the default type should just work. // The setter should be chainable. $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_DEFAULT)); $this->assertEquals(filterset::JOINTYPE_DEFAULT, $filterset->get_join_type()); // It should be possible to update the join type later. $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_NONE)); $this->assertEquals(filterset::JOINTYPE_NONE, $filterset->get_join_type()); $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_ANY)); $this->assertEquals(filterset::JOINTYPE_ANY, $filterset->get_join_type()); $this->assertEquals($filterset, $filterset->set_join_type(filterset::JOINTYPE_ALL)); $this->assertEquals(filterset::JOINTYPE_ALL, $filterset->get_join_type()); } /** * Ensure that it is not possible to provide a value out of bounds when setting the join type. */ public function test_set_join_type_invalid_low(): void { $filterset = $this->get_mocked_filterset(); // Valid join types are current 0, 1, or 2. // A value too low should be rejected. $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Invalid join type specified"); $filterset->set_join_type(-1); } /** * Ensure that it is not possible to provide a value out of bounds when setting the join type. */ public function test_set_join_type_invalid_high(): void { $filterset = $this->get_mocked_filterset(); // Valid join types are current 0, 1, or 2. // A value too low should be rejected. $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("Invalid join type specified"); $filterset->set_join_type(4); } /** * Ensure that adding filter values works as expected. */ public function test_add_filter_value(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'name' => filter::class, 'species' => filter::class, ])); // Initially an empty list. $this->assertEmpty($filterset->get_filters()); // Test data. $speciesfilter = new filter('species', null, ['canine']); $namefilter = new filter('name', null, ['rosie']); // Add a filter to the list. $filterset->add_filter($speciesfilter); $this->assertSame([ $speciesfilter, ], array_values($filterset->get_filters())); // Adding a second value should add that value. // The values should sorted. $filterset->add_filter($namefilter); $this->assertSame([ $namefilter, $speciesfilter, ], array_values($filterset->get_filters())); // Adding an existing filter again should be ignored. $filterset->add_filter($speciesfilter); $this->assertSame([ $namefilter, $speciesfilter, ], array_values($filterset->get_filters())); } /** * Ensure that it is possible to add a filter of a validated filter type. */ public function test_add_filter_validated_type(): void { $namefilter = $this->getMockBuilder(filter::class) ->setConstructorArgs(['name'])< ->setMethods(null)> ->onlyMethods([])->getMock(); $namefilter->add_filter_value('rosie'); // Mock the get_optional_filters function. $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'name' => get_class($namefilter), ])); // Add a filter to the list. // This is the 'name' filter. $filterset->add_filter($namefilter); $this->assertNull($filterset->check_validity()); } /** * Ensure that it is not possible to add a type which is not expected. */ public function test_add_filter_unexpected_key(): void { // Mock the get_optional_filters function. $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([])); // Add a filter to the list. // This is the 'name' filter. $namefilter = new filter('name'); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("The filter 'name' was not recognised."); $filterset->add_filter($namefilter); } /** * Ensure that it is not possible to add a validated type where the type is incorrect. */ public function test_add_filter_validated_type_incorrect(): void { $filtername = "name"; $otherfilter = $this->createMock(filter::class); // Mock the get_optional_filters function. $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ $filtername => get_class($otherfilter), ])); // Add a filter to the list. // This is the 'name' filter. $namefilter = $this->getMockBuilder(filter::class)< ->setMethods(null)> ->onlyMethods([])->setConstructorArgs([$filtername]) ->getMock(); $actualtype = get_class($namefilter); $requiredtype = get_class($otherfilter); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( "The filter '{$filtername}' was incorrectly specified as a {$actualtype}. It must be a {$requiredtype}." ); $filterset->add_filter($namefilter); } /** * Ensure that a filter can be added from parameters provided to a web service. */ public function test_add_filter_from_params(): void { $filtername = "name"; $otherfilter = $this->getMockBuilder(filter::class)< ->setMethods(null)> ->onlyMethods([])->setConstructorArgs([$filtername]) ->getMock(); // Mock the get_optional_filters function. $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ $filtername => get_class($otherfilter), ])); $result = $filterset->add_filter_from_params($filtername, filter::JOINTYPE_DEFAULT, ['kevin']); // The function is chainable. $this->assertEquals($filterset, $result); // Get the filter back. $filter = $filterset->get_filter($filtername); $this->assertEquals($filtername, $filter->get_name()); $this->assertEquals(filter::JOINTYPE_DEFAULT, $filter->get_join_type()); $this->assertEquals(['kevin'], $filter->get_filter_values()); } /** * Ensure that an unknown filter is not added. */ public function test_add_filter_from_params_unable_to_autoload(): void { // Mock the get_optional_filters function. $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'name' => '\\moodle\\this\\is\\a\\fake\\class\\name', ])); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage( "The filter class '\\moodle\\this\\is\\a\\fake\\class\\name' for filter 'name' could not be found." ); $filterset->add_filter_from_params('name', filter::JOINTYPE_DEFAULT, ['kevin']); } /** * Ensure that an unknown filter is not added. */ public function test_add_filter_from_params_invalid(): void { $filtername = "name"; $otherfilter = $this->getMockBuilder(filter::class)< ->setMethods(null)> ->onlyMethods([])->setConstructorArgs([$filtername]) ->getMock(); // Mock the get_optional_filters function. $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ $filtername => get_class($otherfilter), ])); $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage("The filter 'unknownfilter' was not recognised."); $filterset->add_filter_from_params('unknownfilter', filter::JOINTYPE_DEFAULT, ['kevin']); } /** * Ensure that adding a different filter with a different object throws an Exception. */ public function test_duplicate_filter_value(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'name' => filter::class, 'species' => filter::class, ])); // Add a filter to the list. // This is the 'name' filter. $namefilter = new filter('name', null, ['rosie']); $filterset->add_filter($namefilter); // Add another filter to the list. // This one has been incorrectly called the 'name' filter when it should be 'species'. $this->expectException(UnexpectedValueException::Class); $this->expectExceptionMessage("A filter of type 'name' has already been added. Check that you have the correct filter."); $speciesfilter = new filter('name', null, ['canine']); $filterset->add_filter($speciesfilter); } /** * Ensure that validating a filterset correctly compares filter types. */ public function test_check_validity_optional_filters_not_specified(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'name' => filter::class, 'species' => filter::class, ])); $this->assertNull($filterset->check_validity()); } /** * Ensure that validating a filterset correctly requires required filters. */ public function test_check_validity_required_filter(): void { $filterset = $this->get_mocked_filterset(['get_required_filters']); $filterset->expects($this->any()) ->method('get_required_filters') ->willReturn([ 'name' => filter::class ]); // Add a filter to the list. // This is the 'name' filter. $filterset->add_filter(new filter('name')); $this->assertNull($filterset->check_validity()); } /** * Ensure that validating a filterset excepts correctly when a required fieldset is missing. */ public function test_check_validity_filter_missing_required(): void { $filterset = $this->get_mocked_filterset(['get_required_filters']); $filterset->expects($this->any()) ->method('get_required_filters') ->willReturn([ 'name' => filter::class, 'species' => filter::class, ]); $this->expectException(moodle_exception::Class); $this->expectExceptionMessage("One or more required filters were missing (name, species)"); $filterset->check_validity(); } /** * Ensure that getting the filters returns a sorted list of filters. */ public function test_get_filters(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ // Filters are not defined lexically. 'd' => filter::class, 'b' => filter::class, 'a' => filter::class, 'c' => filter::class, ])); // Filters are added in a different non-lexical order. $c = new filter('c'); $filterset->add_filter($c); $b = new filter('b'); $filterset->add_filter($b); $d = new filter('d'); $filterset->add_filter($d); $a = new filter('a'); $filterset->add_filter($a); // But they are returned lexically sorted. $this->assertEquals([ 'a' => $a, 'b' => $b, 'c' => $c, 'd' => $d, ], $filterset->get_filters()); } /** * Ensure that getting a singlel filter returns the correct filter. */ public function test_get_filter(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ // Filters are not defined lexically. 'd' => filter::class, 'b' => filter::class, 'a' => filter::class, 'c' => filter::class, ])); // Filters are added in a different non-lexical order. $c = new filter('c'); $filterset->add_filter($c); $b = new filter('b'); $filterset->add_filter($b); $d = new filter('d'); $filterset->add_filter($d); $a = new filter('a'); $filterset->add_filter($a); // Filters can be individually retrieved in any order. $this->assertEquals($d, $filterset->get_filter('d')); $this->assertEquals($a, $filterset->get_filter('a')); $this->assertEquals($b, $filterset->get_filter('b')); $this->assertEquals($c, $filterset->get_filter('c')); } /** * Ensure that it is not possible to retrieve an unknown filter. */ public function test_get_filter_unknown(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'a' => filter::class, ])); $a = new filter('a'); $filterset->add_filter($a); $this->expectException(UnexpectedValueException::Class); $this->expectExceptionMessage("The filter specified (d) is invalid."); $filterset->get_filter('d'); } /** * Ensure that it is not possible to retrieve a valid filter before it is created. */ public function test_get_filter_not_yet_added(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'a' => filter::class, ])); $this->expectException(UnexpectedValueException::Class); $this->expectExceptionMessage("The filter specified (a) has not been created."); $filterset->get_filter('a'); } /** * Ensure that the get_all_filtertypes function correctly returns the combined filterset. */ public function test_get_all_filtertypes(): void { $otherfilter = $this->createMock(filter::class); $filterset = $this->get_mocked_filterset([ 'get_optional_filters', 'get_required_filters', ]); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'a' => filter::class, 'c' => get_class($otherfilter), ])); $filterset->method('get_required_filters') ->will($this->returnValue([ 'b' => get_class($otherfilter), 'd' => filter::class, ])); $this->assertEquals([ 'a' => filter::class, 'b' => get_class($otherfilter), 'c' => get_class($otherfilter), 'd' => filter::class, ], $filterset->get_all_filtertypes()); } /** * Ensure that the get_all_filtertypes function correctly returns the combined filterset. */ public function test_get_all_filtertypes_conflict(): void { $otherfilter = $this->createMock(filter::class); $filterset = $this->get_mocked_filterset([ 'get_optional_filters', 'get_required_filters', ]); $filterset->method('get_optional_filters') ->will($this->returnValue([ 'a' => filter::class, 'b' => get_class($otherfilter), 'd' => filter::class, ])); $filterset->method('get_required_filters') ->will($this->returnValue([ 'b' => get_class($otherfilter), 'c' => filter::class, 'd' => filter::class, ])); $this->expectException(InvalidArgumentException::Class); $this->expectExceptionMessage("Some filter types are both required, and optional: b, d"); $filterset->get_all_filtertypes(); } /** * Ensure that the has_filter function works as expected. */ public function test_has_filter(): void { $filterset = $this->get_mocked_filterset(['get_optional_filters']); $filterset->method('get_optional_filters') ->will($this->returnValue([ // Define filters 'a', and 'b'. 'a' => filter::class, 'b' => filter::class, ])); // Only add filter 'a'. $a = new filter('a'); $filterset->add_filter($a); // Filter 'a' should exist. $this->assertTrue($filterset->has_filter('a')); // Filter 'b' is defined, but has not been added. $this->assertFalse($filterset->has_filter('b')); // Filter 'c' is not defined. // No need to throw any kind of exception - this is an existence check. $this->assertFalse($filterset->has_filter('c')); } /** * Get a mocked copy of the filterset, mocking the specified methods. * * @param array $mockedmethods anonymous array containing the list of mocked methods * @return filterset Mock of the filterset */< protected function get_mocked_filterset(array $mockedmethods = null): filterset { < if (empty($mockedmethods)) { < $mockedmethods = null; < }> protected function get_mocked_filterset(array $mockedmethods = []): filterset {return $this->getMockForAbstractClass(filterset::class, [], '', true, true, true, $mockedmethods); } }