Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.
<?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); } }