Files
moodle/availability/tests/tree_test.php
T

659 lines
26 KiB
PHP

<?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 the condition tree class and related logic.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
use core_availability\capability_checker;
use \core_availability\tree;
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for the condition tree class and related logic.
*
* @package core_availability
* @copyright 2014 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class tree_testcase extends \advanced_testcase {
public function setUp() {
// Load the mock classes so they can be used.
require_once(__DIR__ . '/fixtures/mock_condition.php');
require_once(__DIR__ . '/fixtures/mock_info.php');
}
/**
* Tests constructing a tree with errors.
*/
public function test_construct_errors() {
try {
new \core_availability\tree('frog');
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('not object', $e->getMessage());
}
try {
new \core_availability\tree((object)array());
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('missing ->op', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '*'));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('unknown ->op', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '|'));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('missing ->show', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '|', 'show' => 0));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('->show not bool', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '&'));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('missing ->showc', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '&', 'showc' => 0));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('->showc not array', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '&', 'showc' => array(0)));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('->showc value not bool', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '|', 'show' => true));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('missing ->c', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '|', 'show' => true,
'c' => 'side'));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('->c not array', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '|', 'show' => true,
'c' => array(3)));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('child not object', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '|', 'show' => true,
'c' => array((object)array('type' => 'doesnotexist'))));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('Unknown condition type: doesnotexist', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '|', 'show' => true,
'c' => array((object)array())));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('missing ->op', $e->getMessage());
}
try {
new \core_availability\tree((object)array('op' => '&',
'c' => array((object)array('op' => '&', 'c' => array())),
'showc' => array(true, true)
));
$this->fail();
} catch (coding_exception $e) {
$this->assertContains('->c, ->showc mismatch', $e->getMessage());
}
}
/**
* Tests constructing a tree with plugin that does not exist (ignored).
*/
public function test_construct_ignore_missing_plugin() {
// Construct a tree with & combination of one condition that doesn't exist.
$tree = new \core_availability\tree(self::tree(array(
(object)array('type' => 'doesnotexist'))), true);
// Expected result is an empty tree with | condition, shown.
$this->assertEquals('+|()', (string)$tree);
}
/**
* Tests constructing a tree with subtrees using all available operators.
*/
public function test_construct_just_trees() {
$structure = self::tree(array(
self::tree(array()),
self::tree(array(
self::tree(array(), '!|')), '!&')),
'&', null, array(true, true));
$tree = new \core_availability\tree($structure);
$this->assertEquals('&(+|(),+!&(!|()))', (string)$tree);
}
/**
* Tests constructing tree using the mock plugin.
*/
public function test_construct_with_mock_plugin() {
$structure = self::tree(array(
self::mock(array('a' => true, 'm' => ''))));
$tree = new \core_availability\tree($structure);
$this->assertEquals('+|({mock:y,})', (string)$tree);
}
/**
* Tests the check_available and get_result_information functions.
*/
public function test_check_available() {
global $USER;
// Setup.
$this->resetAfterTest();
$info = new \core_availability\mock_info();
$this->setAdminUser();
$information = '';
// No conditions.
$structure = self::tree(array());
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertTrue($available);
// One condition set to yes.
$structure->c = array(
self::mock(array('a' => true)));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertTrue($available);
// One condition set to no.
$structure->c = array(
self::mock(array('a' => false, 'm' => 'no')));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertEquals('SA: no', $information);
// Two conditions, OR, resolving as true.
$structure->c = array(
self::mock(array('a' => false, 'm' => 'no')),
self::mock(array('a' => true)));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertTrue($available);
$this->assertEquals('', $information);
// Two conditions, OR, resolving as false.
$structure->c = array(
self::mock(array('a' => false, 'm' => 'no')),
self::mock(array('a' => false, 'm' => 'way')));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertRegExp('~any of.*no.*way~', $information);
// Two conditions, OR, resolving as false, no display.
$structure->show = false;
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertEquals('', $information);
// Two conditions, AND, resolving as true.
$structure->op = '&';
unset($structure->show);
$structure->showc = array(true, true);
$structure->c = array(
self::mock(array('a' => true)),
self::mock(array('a' => true)));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertTrue($available);
// Two conditions, AND, one false.
$structure->c = array(
self::mock(array('a' => false, 'm' => 'wom')),
self::mock(array('a' => true, 'm' => '')));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertEquals('SA: wom', $information);
// Two conditions, AND, both false.
$structure->c = array(
self::mock(array('a' => false, 'm' => 'wom')),
self::mock(array('a' => false, 'm' => 'bat')));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertRegExp('~wom.*bat~', $information);
// Two conditions, AND, both false, show turned off for one. When
// show is turned off, that means if you don't have that condition
// you don't get to see anything at all.
$structure->showc[0] = false;
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertEquals('', $information);
$structure->showc[0] = true;
// Two conditions, NOT OR, both false.
$structure->op = '!|';
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertTrue($available);
// Two conditions, NOT OR, one true.
$structure->c[0]->a = true;
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertEquals('SA: !wom', $information);
// Two conditions, NOT OR, both true.
$structure->c[1]->a = true;
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertRegExp('~!wom.*!bat~', $information);
// Two conditions, NOT AND, both true.
$structure->op = '!&';
unset($structure->showc);
$structure->show = true;
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertRegExp('~any of.*!wom.*!bat~', $information);
// Two conditions, NOT AND, one true.
$structure->c[1]->a = false;
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertTrue($available);
// Nested NOT conditions; true.
$structure->c = array(
self::tree(array(
self::mock(array('a' => true, 'm' => 'no'))), '!&'));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertTrue($available);
// Nested NOT conditions; false (note no ! in message).
$structure->c[0]->c[0]->a = false;
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertEquals('SA: no', $information);
// Nested condition groups, message test.
$structure->op = '|';
$structure->c = array(
self::tree(array(
self::mock(array('a' => false, 'm' => '1')),
self::mock(array('a' => false, 'm' => '2'))
), '&', null, array(true, true)),
self::mock(array('a' => false, 'm' => 3)));
list ($available, $information) = $this->get_available_results(
$structure, $info, $USER->id);
$this->assertFalse($available);
$this->assertRegExp('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~', $information);
}
/**
* Shortcut function to check availability and also get information.
*
* @param stdClass $structure Tree structure
* @param \core_availability\info $info Location info
* @param int $userid User id
*/
protected function get_available_results($structure, \core_availability\info $info, $userid) {
$tree = new \core_availability\tree($structure);
$result = $tree->check_available(false, $info, true, $userid);
return array($result->is_available(), $tree->get_result_information($info, $result));
}
/**
* Tests the is_available_for_all() function.
*/
public function test_is_available_for_all() {
// Empty tree is always available.
$structure = self::tree(array());
$tree = new \core_availability\tree($structure);
$this->assertTrue($tree->is_available_for_all());
// Tree with normal item in it, not always available.
$structure->c[0] = (object)array('type' => 'mock');
$tree = new \core_availability\tree($structure);
$this->assertFalse($tree->is_available_for_all());
// OR tree with one always-available item.
$structure->c[1] = self::mock(array('all' => true));
$tree = new \core_availability\tree($structure);
$this->assertTrue($tree->is_available_for_all());
// AND tree with one always-available and one not.
$structure->op = '&';
$structure->showc = array(true, true);
unset($structure->show);
$tree = new \core_availability\tree($structure);
$this->assertFalse($tree->is_available_for_all());
// Test NOT conditions (items not always-available).
$structure->op = '!&';
$structure->show = true;
unset($structure->showc);
$tree = new \core_availability\tree($structure);
$this->assertFalse($tree->is_available_for_all());
// Test again with one item always-available for NOT mode.
$structure->c[1]->allnot = true;
$tree = new \core_availability\tree($structure);
$this->assertTrue($tree->is_available_for_all());
}
/**
* Tests the get_full_information() function.
*/
public function test_get_full_information() {
// Setup.
$info = new \core_availability\mock_info();
// No conditions.
$structure = self::tree(array());
$tree = new \core_availability\tree($structure);
$this->assertEquals('', $tree->get_full_information($info));
// Condition (normal and NOT).
$structure->c = array(
self::mock(array('m' => 'thing')));
$tree = new \core_availability\tree($structure);
$this->assertEquals('SA: [FULL]thing',
$tree->get_full_information($info));
$structure->op = '!&';
$tree = new \core_availability\tree($structure);
$this->assertEquals('SA: ![FULL]thing',
$tree->get_full_information($info));
// Complex structure.
$structure->op = '|';
$structure->c = array(
self::tree(array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))), '&'),
self::mock(array('m' => 3)));
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~',
$tree->get_full_information($info));
// Test intro messages before list. First, OR message.
$structure->c = array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))
);
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~Not available unless any of:.*<ul>~',
$tree->get_full_information($info));
// Now, OR message when not shown.
$structure->show = false;
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~hidden.*<ul>~',
$tree->get_full_information($info));
// AND message.
$structure->op = '&';
unset($structure->show);
$structure->showc = array(false, false);
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~Not available unless:.*<ul>~',
$tree->get_full_information($info));
// Hidden markers on items.
$this->assertRegExp('~1.*hidden.*2.*hidden~',
$tree->get_full_information($info));
// Hidden markers on child tree and items.
$structure->c[1] = self::tree(array(
self::mock(array('m' => '2')),
self::mock(array('m' => '3'))), '&');
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~1.*hidden.*All of \(hidden.*2.*3~',
$tree->get_full_information($info));
$structure->c[1]->op = '|';
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~1.*hidden.*Any of \(hidden.*2.*3~',
$tree->get_full_information($info));
// Hidden markers on single-item display, AND and OR.
$structure->showc = array(false);
$structure->c = array(
self::mock(array('m' => '1'))
);
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~1.*hidden~',
$tree->get_full_information($info));
unset($structure->showc);
$structure->show = false;
$structure->op = '|';
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~1.*hidden~',
$tree->get_full_information($info));
// Hidden marker if single item is tree.
$structure->c[0] = self::tree(array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))), '&', null);
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~Not available \(hidden.*1.*2~',
$tree->get_full_information($info));
// Single item tree containing single item.
unset($structure->c[0]->c[1]);
$tree = new \core_availability\tree($structure);
$this->assertRegExp('~SA.*1.*hidden~',
$tree->get_full_information($info));
}
/**
* Tests the is_empty() function.
*/
public function test_is_empty() {
// Tree with nothing in should be empty.
$structure = self::tree(array());
$tree = new tree($structure);
$this->assertTrue($tree->is_empty());
// Tree with something in is not empty.
$structure = self::tree(array(self::mock(array('m' => '1'))));
$tree = new tree($structure);
$this->assertFalse($tree->is_empty());
}
/**
* Tests the get_all_children() function.
*/
public function test_get_all_children() {
// Create a tree with nothing in.
$structure = self::tree(array());
$tree1 = new \core_availability\tree($structure);
// Create second tree with complex structure.
$structure->c = array(
self::tree(array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))
)),
self::mock(array('m' => 3)));
$tree2 = new \core_availability\tree($structure);
// Check list of conditions from both trees.
$this->assertEquals(array(), $tree1->get_all_children('core_availability\condition'));
$result = $tree2->get_all_children('core_availability\condition');
$this->assertEquals(3, count($result));
$this->assertEquals('{mock:n,1}', (string)$result[0]);
$this->assertEquals('{mock:n,2}', (string)$result[1]);
$this->assertEquals('{mock:n,3}', (string)$result[2]);
// Check specific type, should give same results.
$result2 = $tree2->get_all_children('availability_mock\condition');
$this->assertEquals($result, $result2);
}
/**
* Tests the update_dependency_id() function.
*/
public function test_update_dependency_id() {
// Create tree with structure of 3 mocks.
$structure = self::tree(array(
self::tree(array(
self::mock(array('table' => 'frogs', 'id' => 9)),
self::mock(array('table' => 'zombies', 'id' => 9))
)),
self::mock(array('table' => 'frogs', 'id' => 9))));
// Get 'before' value.
$tree = new \core_availability\tree($structure);
$before = $tree->save();
// Try replacing a table or id that isn't used.
$this->assertFalse($tree->update_dependency_id('toads', 9, 13));
$this->assertFalse($tree->update_dependency_id('frogs', 7, 8));
$this->assertEquals($before, $tree->save());
// Replace the zombies one.
$this->assertTrue($tree->update_dependency_id('zombies', 9, 666));
$after = $tree->save();
$this->assertEquals(666, $after->c[0]->c[1]->id);
// And the frogs one.
$this->assertTrue($tree->update_dependency_id('frogs', 9, 3));
$after = $tree->save();
$this->assertEquals(3, $after->c[0]->c[0]->id);
$this->assertEquals(3, $after->c[1]->id);
}
/**
* Tests the filter_users function.
*/
public function test_filter_users() {
$info = new \core_availability\mock_info();
$checker = new capability_checker($info->get_context());
// Don't need to create real users in database, just use these ids.
$users = array(1 => null, 2 => null, 3 => null);
// Test basic tree with one condition that doesn't filter.
$structure = self::tree(array(self::mock(array())));
$tree = new \core_availability\tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(1, 2, 3), array_keys($result));
// Now a tree with one condition that filters.
$structure = self::tree(array(self::mock(array('filter' => array(2, 3)))));
$tree = new \core_availability\tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(2, 3), array_keys($result));
// Tree with two conditions that both filter (|).
$structure = self::tree(array(
self::mock(array('filter' => array(3))),
self::mock(array('filter' => array(1)))));
$tree = new \core_availability\tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(1, 3), array_keys($result));
// Tree with two condition that both filter (&).
$structure = self::tree(array(
self::mock(array('filter' => array(2, 3))),
self::mock(array('filter' => array(1, 2)))), '&', false,
array(true, true));
$tree = new \core_availability\tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(2), array_keys($result));
// Tree with child tree with NOT condition.
$structure = self::tree(array(
self::tree(
self::mock(array('filter' => array(1))), '!&', null)));
$tree = new \core_availability\tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(2, 3), array_keys($result));
}
/**
* Utility function to build the PHP structure representing a tree.
*
* @param array|tree_node $children Children or single child
* @param string $op Operator
* @param bool|null $show True/false show param or null if none
* @param array|null $showc showc param or null if none
* @return \stdClass Structure object
*/
protected static function tree($children, $op = '|', $show = true, $showc = null) {
$structure = new stdClass();
$structure->op = $op;
if (!is_array($children)) {
$children = array($children);
}
$structure->c = $children;
if ($showc !== null) {
$structure->showc = $showc;
}
if ($show !== null) {
$structure->show = $show;
}
return $structure;
}
/**
* Utility function to build the PHP structure representing a mock condition.
*
* @param array $params Mock parameters
* @return \stdClass Structure object
*/
protected static function mock(array $params) {
$params['type'] = 'mock';
return (object)$params;
}
}