<?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();
> namespace core_availability;
/**
* 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 {
> class tree_test extends \advanced_testcase {
public function setUp(): void {
// 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 tree('frog');
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('not object', $e->getMessage());
}
try {
new tree((object)array());
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('missing ->op', $e->getMessage());
}
try {
new tree((object)array('op' => '*'));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('unknown ->op', $e->getMessage());
}
try {
new tree((object)array('op' => '|'));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('missing ->show', $e->getMessage());
}
try {
new tree((object)array('op' => '|', 'show' => 0));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('->show not bool', $e->getMessage());
}
try {
new tree((object)array('op' => '&'));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('missing ->showc', $e->getMessage());
}
try {
new tree((object)array('op' => '&', 'showc' => 0));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('->showc not array', $e->getMessage());
}
try {
new tree((object)array('op' => '&', 'showc' => array(0)));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('->showc value not bool', $e->getMessage());
}
try {
new tree((object)array('op' => '|', 'show' => true));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('missing ->c', $e->getMessage());
}
try {
new tree((object)array('op' => '|', 'show' => true,
'c' => 'side'));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('->c not array', $e->getMessage());
}
try {
new tree((object)array('op' => '|', 'show' => true,
'c' => array(3)));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('child not object', $e->getMessage());
}
try {
new tree((object)array('op' => '|', 'show' => true,
'c' => array((object)array('type' => 'doesnotexist'))));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('Unknown condition type: doesnotexist', $e->getMessage());
}
try {
new tree((object)array('op' => '|', 'show' => true,
'c' => array((object)array())));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('missing ->op', $e->getMessage());
}
try {
new tree((object)array('op' => '&',
'c' => array((object)array('op' => '&', 'c' => array())),
'showc' => array(true, true)
));
$this->fail();
< } catch (coding_exception $e) {
> } catch (\coding_exception $e) {
$this->assertStringContainsString('->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 tree(tree::get_root_json(array(
(object)array('type' => 'doesnotexist')), tree::OP_OR), 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 = tree::get_root_json(array(
tree::get_nested_json(array(), tree::OP_OR),
tree::get_nested_json(array(
tree::get_nested_json(array(), tree::OP_NOT_OR)), tree::OP_NOT_AND)),
tree::OP_AND, array(true, true));
$tree = new tree($structure);
$this->assertEquals('&(+|(),+!&(!|()))', (string)$tree);
}
/**
* Tests constructing tree using the mock plugin.
*/
public function test_construct_with_mock_plugin() {
$structure = tree::get_root_json(array(
self::mock(array('a' => true, 'm' => ''))), tree::OP_OR);
$tree = new 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 = tree::get_root_json(array(), tree::OP_OR);
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);
> $this->assertMatchesRegularExpression('~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);
> $this->assertMatchesRegularExpression('~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);
> $this->assertMatchesRegularExpression('~!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);
> $this->assertMatchesRegularExpression('~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(
tree::get_nested_json(array(
self::mock(array('a' => true, 'm' => 'no'))), tree::OP_NOT_AND));
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(
tree::get_nested_json(array(
self::mock(array('a' => false, 'm' => '1')),
self::mock(array('a' => false, 'm' => '2'))
), tree::OP_AND),
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);
> $this->assertMatchesRegularExpression('~<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 \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) {
< global $PAGE;
> global $PAGE, $OUTPUT;
$tree = new tree($structure);
$result = $tree->check_available(false, $info, true, $userid);
$information = $tree->get_result_information($info, $result);
if (!is_string($information)) {
< $renderer = $PAGE->get_renderer('core', 'availability');
< $information = $renderer->render($information);
> $renderable = new \core_availability\output\availability_info($information);
> $information = str_replace(array("\r", "\n"), '', $OUTPUT->render($renderable));
}
return array($result->is_available(), $information);
}
/**
> * Shortcut function to render the full availability information.
* Tests the is_available_for_all() function.
> *
*/
> * @param \stdClass $structure Tree structure
public function test_is_available_for_all() {
> * @param \core_availability\info $info Location info
// Empty tree is always available.
> */
$structure = tree::get_root_json(array(), tree::OP_OR);
> protected function render_full_information($structure, \core_availability\info $info) {
$tree = new tree($structure);
> global $OUTPUT;
$this->assertTrue($tree->is_available_for_all());
> $tree = new tree($structure);
> $information = $tree->get_full_information($info);
// Tree with normal item in it, not always available.
> $renderable = new \core_availability\output\availability_info($information);
$structure->c[0] = (object)array('type' => 'mock');
> $html = $OUTPUT->render($renderable);
$tree = new tree($structure);
> return str_replace(array("\r", "\n"), '', $html);
$this->assertFalse($tree->is_available_for_all());
> }
>
// OR tree with one always-available item.
> /**
$structure->c[1] = self::mock(array('all' => true));
$tree = new 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 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 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 tree($structure);
$this->assertTrue($tree->is_available_for_all());
}
/**
* Tests the get_full_information() function.
*/
public function test_get_full_information() {
global $PAGE;
< $renderer = $PAGE->get_renderer('core', 'availability');
// Setup.
$info = new \core_availability\mock_info();
// No conditions.
$structure = tree::get_root_json(array(), tree::OP_OR);
$tree = new tree($structure);
$this->assertEquals('', $tree->get_full_information($info));
// Condition (normal and NOT).
$structure->c = array(
self::mock(array('m' => 'thing')));
$tree = new tree($structure);
$this->assertEquals('SA: [FULL]thing',
$tree->get_full_information($info));
$structure->op = '!&';
$tree = new tree($structure);
$this->assertEquals('SA: ![FULL]thing',
$tree->get_full_information($info));
// Complex structure.
$structure->op = '|';
$structure->c = array(
tree::get_nested_json(array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))), tree::OP_AND),
self::mock(array('m' => 3)));
< $tree = new tree($structure);
< $this->assertRegExp('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~',
< $renderer->render($tree->get_full_information($info)));
> $this->assertMatchesRegularExpression('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~',
> $this->render_full_information($structure, $info));
// Test intro messages before list. First, OR message.
$structure->c = array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))
);
< $tree = new tree($structure);
< $this->assertRegExp('~Not available unless any of:.*<ul>~',
< $renderer->render($tree->get_full_information($info)));
> $this->assertMatchesRegularExpression('~Not available unless any of:.*<ul~',
> $this->render_full_information($structure, $info));
// Now, OR message when not shown.
$structure->show = false;
< $tree = new tree($structure);
< $this->assertRegExp('~hidden.*<ul>~',
< $renderer->render($tree->get_full_information($info)));
>
> $this->assertMatchesRegularExpression('~hidden.*<ul~',
> $this->render_full_information($structure, $info));
// AND message.
$structure->op = '&';
unset($structure->show);
$structure->showc = array(false, false);
< $tree = new tree($structure);
< $this->assertRegExp('~Not available unless:.*<ul>~',
< $renderer->render($tree->get_full_information($info)));
> $this->assertMatchesRegularExpression('~Not available unless:.*<ul~',
> $this->render_full_information($structure, $info));
// Hidden markers on items.
< $this->assertRegExp('~1.*hidden.*2.*hidden~',
< $renderer->render($tree->get_full_information($info)));
> $this->assertMatchesRegularExpression('~1.*hidden.*2.*hidden~',
> $this->render_full_information($structure, $info));
// Hidden markers on child tree and items.
$structure->c[1] = tree::get_nested_json(array(
self::mock(array('m' => '2')),
self::mock(array('m' => '3'))), tree::OP_AND);
< $tree = new tree($structure);
< $this->assertRegExp('~1.*hidden.*All of \(hidden.*2.*3~',
< $renderer->render($tree->get_full_information($info)));
> $this->assertMatchesRegularExpression('~1.*hidden.*All of \(hidden.*2.*3~',
> $this->render_full_information($structure, $info));
$structure->c[1]->op = '|';
< $tree = new tree($structure);
< $this->assertRegExp('~1.*hidden.*Any of \(hidden.*2.*3~',
< $renderer->render($tree->get_full_information($info)));
> $this->assertMatchesRegularExpression('~1.*hidden.*Any of \(hidden.*2.*3~',
> $this->render_full_information($structure, $info));
// Hidden markers on single-item display, AND and OR.
$structure->showc = array(false);
$structure->c = array(
self::mock(array('m' => '1'))
);
$tree = new tree($structure);
< $this->assertRegExp('~1.*hidden~',
> $this->assertMatchesRegularExpression('~1.*hidden~',
$tree->get_full_information($info));
unset($structure->showc);
$structure->show = false;
$structure->op = '|';
$tree = new tree($structure);
< $this->assertRegExp('~1.*hidden~',
> $this->assertMatchesRegularExpression('~1.*hidden~',
$tree->get_full_information($info));
// Hidden marker if single item is tree.
$structure->c[0] = tree::get_nested_json(array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))), tree::OP_AND);
< $tree = new tree($structure);
< $this->assertRegExp('~Not available \(hidden.*1.*2~',
< $renderer->render($tree->get_full_information($info)));
> $this->assertMatchesRegularExpression('~Not available \(hidden.*1.*2~',
> $this->render_full_information($structure, $info));
// Single item tree containing single item.
unset($structure->c[0]->c[1]);
$tree = new tree($structure);
< $this->assertRegExp('~SA.*1.*hidden~',
> $this->assertMatchesRegularExpression('~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 = tree::get_root_json(array(), tree::OP_OR);
$tree = new tree($structure);
$this->assertTrue($tree->is_empty());
// Tree with something in is not empty.
$structure = tree::get_root_json(array(self::mock(array('m' => '1'))), tree::OP_OR);
$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 = tree::get_root_json(array(), tree::OP_OR);
$tree1 = new tree($structure);
// Create second tree with complex structure.
$structure->c = array(
tree::get_nested_json(array(
self::mock(array('m' => '1')),
self::mock(array('m' => '2'))
), tree::OP_OR),
self::mock(array('m' => 3)));
$tree2 = new 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 = tree::get_root_json(array(
tree::get_nested_json(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 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 = tree::get_root_json(array(self::mock(array())));
$tree = new 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 = tree::get_root_json(array(self::mock(array('filter' => array(2, 3)))));
$tree = new 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 = tree::get_root_json(array(
self::mock(array('filter' => array(3))),
self::mock(array('filter' => array(1)))), tree::OP_OR);
$tree = new tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(1, 3), array_keys($result));
// Tree with OR condition one of which doesn't filter.
$structure = tree::get_root_json(array(
self::mock(array('filter' => array(3))),
self::mock(array())), tree::OP_OR);
$tree = new tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(1, 2, 3), array_keys($result));
// Tree with two condition that both filter (&).
$structure = tree::get_root_json(array(
self::mock(array('filter' => array(2, 3))),
self::mock(array('filter' => array(1, 2)))));
$tree = new 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 = tree::get_root_json(array(
tree::get_nested_json(array(
self::mock(array('filter' => array(1)))), tree::OP_NOT_AND)));
$tree = new tree($structure);
$result = $tree->filter_user_list($users, false, $info, $checker);
ksort($result);
$this->assertEquals(array(2, 3), array_keys($result));
}
/**
* Tests the get_json methods in tree (which are mainly for use in testing
* but might be used elsewhere).
*/
public function test_get_json() {
// Create a simple child object (fake).
$child = (object)array('type' => 'fake');
$childstr = json_encode($child);
// Minimal case.
$this->assertEquals(
(object)array('op' => '&', 'c' => array()),
tree::get_nested_json(array()));
// Children and different operator.
$this->assertEquals(
(object)array('op' => '|', 'c' => array($child, $child)),
tree::get_nested_json(array($child, $child), tree::OP_OR));
// Root empty.
$this->assertEquals('{"op":"&","c":[],"showc":[]}',
json_encode(tree::get_root_json(array(), tree::OP_AND)));
// Root with children (multi-show operator).
$this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
'],"showc":[true,true]}',
json_encode(tree::get_root_json(array($child, $child), tree::OP_AND)));
// Root with children (single-show operator).
$this->assertEquals('{"op":"|","c":[' . $childstr . ',' . $childstr .
'],"show":true}',
json_encode(tree::get_root_json(array($child, $child), tree::OP_OR)));
// Root with children (specified show boolean).
$this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
'],"showc":[false,false]}',
json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, false)));
// Root with children (specified show array).
$this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
'],"showc":[true,false]}',
json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, array(true, false))));
}
/**
* Tests the behaviour of the counter in unique_sql_parameter().
*
* There was a problem with static counters used to implement a sequence of
* parameter placeholders (MDL-53481). As always with static variables, it
* is a bit tricky to unit test the behaviour reliably as it depends on the
* actual tests executed and also their order.
*
* To minimise risk of false expected behaviour, this test method should be
* first one where {@link core_availability\tree::get_user_list_sql()} is
* used. We also use higher number of condition instances to increase the
* risk of the counter collision, should there remain a problem.
*/
public function test_unique_sql_parameter_behaviour() {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
// Create a test course with multiple groupings and groups and a student in each of them.
$course = $generator->create_course();
$user = $generator->create_user();
$studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
$generator->enrol_user($user->id, $course->id, $studentroleid);
// The total number of groupings and groups must not be greater than 61.
// There is a limit in MySQL on the max number of joined tables.
$groups = [];
for ($i = 0; $i < 25; $i++) {
$group = $generator->create_group(array('courseid' => $course->id));
groups_add_member($group, $user);
$groups[] = $group;
}
$groupings = [];
for ($i = 0; $i < 25; $i++) {
$groupings[] = $generator->create_grouping(array('courseid' => $course->id));
}
foreach ($groupings as $grouping) {
foreach ($groups as $group) {
groups_assign_grouping($grouping->id, $group->id);
}
}
$info = new \core_availability\mock_info($course);
// Make a huge tree with 'AND' of all groups and groupings conditions.
$conditions = [];
foreach ($groups as $group) {
$conditions[] = \availability_group\condition::get_json($group->id);
}
foreach ($groupings as $groupingid) {
$conditions[] = \availability_grouping\condition::get_json($grouping->id);
}
shuffle($conditions);
$tree = new tree(tree::get_root_json($conditions));
list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
// This must not throw exception.
$DB->fix_sql_params($sql, $params);
}
/**
* Tests get_user_list_sql.
*/
public function test_get_user_list_sql() {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
// Create a test course with 2 groups and users in each combination of them.
$course = $generator->create_course();
$group1 = $generator->create_group(array('courseid' => $course->id));
$group2 = $generator->create_group(array('courseid' => $course->id));
$userin1 = $generator->create_user();
$userin2 = $generator->create_user();
$userinboth = $generator->create_user();
$userinneither = $generator->create_user();
$studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
foreach (array($userin1, $userin2, $userinboth, $userinneither) as $user) {
$generator->enrol_user($user->id, $course->id, $studentroleid);
}
groups_add_member($group1, $userin1);
groups_add_member($group2, $userin2);
groups_add_member($group1, $userinboth);
groups_add_member($group2, $userinboth);
$info = new \core_availability\mock_info($course);
// Tree with single group condition.
$tree = new tree(tree::get_root_json(array(
\availability_group\condition::get_json($group1->id)
)));
list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals(array($userin1->id, $userinboth->id), $result);
// Tree with 'AND' of both group conditions.
$tree = new tree(tree::get_root_json(array(
\availability_group\condition::get_json($group1->id),
\availability_group\condition::get_json($group2->id)
)));
list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals(array($userinboth->id), $result);
// Tree with 'AND' of both group conditions.
$tree = new tree(tree::get_root_json(array(
\availability_group\condition::get_json($group1->id),
\availability_group\condition::get_json($group2->id)
), tree::OP_OR));
list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals(array($userin1->id, $userin2->id, $userinboth->id), $result);
// Check with flipped logic (NOT above level of tree).
list($sql, $params) = $tree->get_user_list_sql(true, $info, false);
$result = $DB->get_fieldset_sql($sql, $params);
sort($result);
$this->assertEquals(array($userinneither->id), $result);
// Tree with 'OR' of group conditions and a non-filtering condition.
// The non-filtering condition should mean that ALL users are included.
$tree = new tree(tree::get_root_json(array(
\availability_group\condition::get_json($group1->id),
\availability_date\condition::get_json(\availability_date\condition::DIRECTION_UNTIL, 3)
), tree::OP_OR));
list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
$this->assertEquals('', $sql);
$this->assertEquals(array(), $params);
}
/**
* 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;
}
}