Automatically generated installer lang files
[moodle.git] / availability / tests / tree_test.php
blob1b131a9a96eae26bb7864357652f82ceab2cb24f
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.
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/>.
17 /**
18 * Unit tests for the condition tree class and related logic.
20 * @package core_availability
21 * @copyright 2014 The Open University
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
25 use core_availability\capability_checker;
26 use \core_availability\tree;
28 defined('MOODLE_INTERNAL') || die();
30 /**
31 * Unit tests for the condition tree class and related logic.
33 * @package core_availability
34 * @copyright 2014 The Open University
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class tree_testcase extends \advanced_testcase {
38 public function setUp() {
39 // Load the mock classes so they can be used.
40 require_once(__DIR__ . '/fixtures/mock_condition.php');
41 require_once(__DIR__ . '/fixtures/mock_info.php');
44 /**
45 * Tests constructing a tree with errors.
47 public function test_construct_errors() {
48 try {
49 new tree('frog');
50 $this->fail();
51 } catch (coding_exception $e) {
52 $this->assertContains('not object', $e->getMessage());
54 try {
55 new tree((object)array());
56 $this->fail();
57 } catch (coding_exception $e) {
58 $this->assertContains('missing ->op', $e->getMessage());
60 try {
61 new tree((object)array('op' => '*'));
62 $this->fail();
63 } catch (coding_exception $e) {
64 $this->assertContains('unknown ->op', $e->getMessage());
66 try {
67 new tree((object)array('op' => '|'));
68 $this->fail();
69 } catch (coding_exception $e) {
70 $this->assertContains('missing ->show', $e->getMessage());
72 try {
73 new tree((object)array('op' => '|', 'show' => 0));
74 $this->fail();
75 } catch (coding_exception $e) {
76 $this->assertContains('->show not bool', $e->getMessage());
78 try {
79 new tree((object)array('op' => '&'));
80 $this->fail();
81 } catch (coding_exception $e) {
82 $this->assertContains('missing ->showc', $e->getMessage());
84 try {
85 new tree((object)array('op' => '&', 'showc' => 0));
86 $this->fail();
87 } catch (coding_exception $e) {
88 $this->assertContains('->showc not array', $e->getMessage());
90 try {
91 new tree((object)array('op' => '&', 'showc' => array(0)));
92 $this->fail();
93 } catch (coding_exception $e) {
94 $this->assertContains('->showc value not bool', $e->getMessage());
96 try {
97 new tree((object)array('op' => '|', 'show' => true));
98 $this->fail();
99 } catch (coding_exception $e) {
100 $this->assertContains('missing ->c', $e->getMessage());
102 try {
103 new tree((object)array('op' => '|', 'show' => true,
104 'c' => 'side'));
105 $this->fail();
106 } catch (coding_exception $e) {
107 $this->assertContains('->c not array', $e->getMessage());
109 try {
110 new tree((object)array('op' => '|', 'show' => true,
111 'c' => array(3)));
112 $this->fail();
113 } catch (coding_exception $e) {
114 $this->assertContains('child not object', $e->getMessage());
116 try {
117 new tree((object)array('op' => '|', 'show' => true,
118 'c' => array((object)array('type' => 'doesnotexist'))));
119 $this->fail();
120 } catch (coding_exception $e) {
121 $this->assertContains('Unknown condition type: doesnotexist', $e->getMessage());
123 try {
124 new tree((object)array('op' => '|', 'show' => true,
125 'c' => array((object)array())));
126 $this->fail();
127 } catch (coding_exception $e) {
128 $this->assertContains('missing ->op', $e->getMessage());
130 try {
131 new tree((object)array('op' => '&',
132 'c' => array((object)array('op' => '&', 'c' => array())),
133 'showc' => array(true, true)
135 $this->fail();
136 } catch (coding_exception $e) {
137 $this->assertContains('->c, ->showc mismatch', $e->getMessage());
142 * Tests constructing a tree with plugin that does not exist (ignored).
144 public function test_construct_ignore_missing_plugin() {
145 // Construct a tree with & combination of one condition that doesn't exist.
146 $tree = new tree(tree::get_root_json(array(
147 (object)array('type' => 'doesnotexist')), tree::OP_OR), true);
148 // Expected result is an empty tree with | condition, shown.
149 $this->assertEquals('+|()', (string)$tree);
153 * Tests constructing a tree with subtrees using all available operators.
155 public function test_construct_just_trees() {
156 $structure = tree::get_root_json(array(
157 tree::get_nested_json(array(), tree::OP_OR),
158 tree::get_nested_json(array(
159 tree::get_nested_json(array(), tree::OP_NOT_OR)), tree::OP_NOT_AND)),
160 tree::OP_AND, array(true, true));
161 $tree = new tree($structure);
162 $this->assertEquals('&(+|(),+!&(!|()))', (string)$tree);
166 * Tests constructing tree using the mock plugin.
168 public function test_construct_with_mock_plugin() {
169 $structure = tree::get_root_json(array(
170 self::mock(array('a' => true, 'm' => ''))), tree::OP_OR);
171 $tree = new tree($structure);
172 $this->assertEquals('+|({mock:y,})', (string)$tree);
176 * Tests the check_available and get_result_information functions.
178 public function test_check_available() {
179 global $USER;
181 // Setup.
182 $this->resetAfterTest();
183 $info = new \core_availability\mock_info();
184 $this->setAdminUser();
185 $information = '';
187 // No conditions.
188 $structure = tree::get_root_json(array(), tree::OP_OR);
189 list ($available, $information) = $this->get_available_results(
190 $structure, $info, $USER->id);
191 $this->assertTrue($available);
193 // One condition set to yes.
194 $structure->c = array(
195 self::mock(array('a' => true)));
196 list ($available, $information) = $this->get_available_results(
197 $structure, $info, $USER->id);
198 $this->assertTrue($available);
200 // One condition set to no.
201 $structure->c = array(
202 self::mock(array('a' => false, 'm' => 'no')));
203 list ($available, $information) = $this->get_available_results(
204 $structure, $info, $USER->id);
205 $this->assertFalse($available);
206 $this->assertEquals('SA: no', $information);
208 // Two conditions, OR, resolving as true.
209 $structure->c = array(
210 self::mock(array('a' => false, 'm' => 'no')),
211 self::mock(array('a' => true)));
212 list ($available, $information) = $this->get_available_results(
213 $structure, $info, $USER->id);
214 $this->assertTrue($available);
215 $this->assertEquals('', $information);
217 // Two conditions, OR, resolving as false.
218 $structure->c = array(
219 self::mock(array('a' => false, 'm' => 'no')),
220 self::mock(array('a' => false, 'm' => 'way')));
221 list ($available, $information) = $this->get_available_results(
222 $structure, $info, $USER->id);
223 $this->assertFalse($available);
224 $this->assertRegExp('~any of.*no.*way~', $information);
226 // Two conditions, OR, resolving as false, no display.
227 $structure->show = false;
228 list ($available, $information) = $this->get_available_results(
229 $structure, $info, $USER->id);
230 $this->assertFalse($available);
231 $this->assertEquals('', $information);
233 // Two conditions, AND, resolving as true.
234 $structure->op = '&';
235 unset($structure->show);
236 $structure->showc = array(true, true);
237 $structure->c = array(
238 self::mock(array('a' => true)),
239 self::mock(array('a' => true)));
240 list ($available, $information) = $this->get_available_results(
241 $structure, $info, $USER->id);
242 $this->assertTrue($available);
244 // Two conditions, AND, one false.
245 $structure->c = array(
246 self::mock(array('a' => false, 'm' => 'wom')),
247 self::mock(array('a' => true, 'm' => '')));
248 list ($available, $information) = $this->get_available_results(
249 $structure, $info, $USER->id);
250 $this->assertFalse($available);
251 $this->assertEquals('SA: wom', $information);
253 // Two conditions, AND, both false.
254 $structure->c = array(
255 self::mock(array('a' => false, 'm' => 'wom')),
256 self::mock(array('a' => false, 'm' => 'bat')));
257 list ($available, $information) = $this->get_available_results(
258 $structure, $info, $USER->id);
259 $this->assertFalse($available);
260 $this->assertRegExp('~wom.*bat~', $information);
262 // Two conditions, AND, both false, show turned off for one. When
263 // show is turned off, that means if you don't have that condition
264 // you don't get to see anything at all.
265 $structure->showc[0] = false;
266 list ($available, $information) = $this->get_available_results(
267 $structure, $info, $USER->id);
268 $this->assertFalse($available);
269 $this->assertEquals('', $information);
270 $structure->showc[0] = true;
272 // Two conditions, NOT OR, both false.
273 $structure->op = '!|';
274 list ($available, $information) = $this->get_available_results(
275 $structure, $info, $USER->id);
276 $this->assertTrue($available);
278 // Two conditions, NOT OR, one true.
279 $structure->c[0]->a = true;
280 list ($available, $information) = $this->get_available_results(
281 $structure, $info, $USER->id);
282 $this->assertFalse($available);
283 $this->assertEquals('SA: !wom', $information);
285 // Two conditions, NOT OR, both true.
286 $structure->c[1]->a = true;
287 list ($available, $information) = $this->get_available_results(
288 $structure, $info, $USER->id);
289 $this->assertFalse($available);
290 $this->assertRegExp('~!wom.*!bat~', $information);
292 // Two conditions, NOT AND, both true.
293 $structure->op = '!&';
294 unset($structure->showc);
295 $structure->show = true;
296 list ($available, $information) = $this->get_available_results(
297 $structure, $info, $USER->id);
298 $this->assertFalse($available);
299 $this->assertRegExp('~any of.*!wom.*!bat~', $information);
301 // Two conditions, NOT AND, one true.
302 $structure->c[1]->a = false;
303 list ($available, $information) = $this->get_available_results(
304 $structure, $info, $USER->id);
305 $this->assertTrue($available);
307 // Nested NOT conditions; true.
308 $structure->c = array(
309 tree::get_nested_json(array(
310 self::mock(array('a' => true, 'm' => 'no'))), tree::OP_NOT_AND));
311 list ($available, $information) = $this->get_available_results(
312 $structure, $info, $USER->id);
313 $this->assertTrue($available);
315 // Nested NOT conditions; false (note no ! in message).
316 $structure->c[0]->c[0]->a = false;
317 list ($available, $information) = $this->get_available_results(
318 $structure, $info, $USER->id);
319 $this->assertFalse($available);
320 $this->assertEquals('SA: no', $information);
322 // Nested condition groups, message test.
323 $structure->op = '|';
324 $structure->c = array(
325 tree::get_nested_json(array(
326 self::mock(array('a' => false, 'm' => '1')),
327 self::mock(array('a' => false, 'm' => '2'))
328 ), tree::OP_AND),
329 self::mock(array('a' => false, 'm' => 3)));
330 list ($available, $information) = $this->get_available_results(
331 $structure, $info, $USER->id);
332 $this->assertFalse($available);
333 $this->assertRegExp('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~', $information);
337 * Shortcut function to check availability and also get information.
339 * @param stdClass $structure Tree structure
340 * @param \core_availability\info $info Location info
341 * @param int $userid User id
343 protected function get_available_results($structure, \core_availability\info $info, $userid) {
344 global $PAGE;
345 $tree = new tree($structure);
346 $result = $tree->check_available(false, $info, true, $userid);
347 $information = $tree->get_result_information($info, $result);
348 if (!is_string($information)) {
349 $renderer = $PAGE->get_renderer('core', 'availability');
350 $information = $renderer->render($information);
352 return array($result->is_available(), $information);
356 * Tests the is_available_for_all() function.
358 public function test_is_available_for_all() {
359 // Empty tree is always available.
360 $structure = tree::get_root_json(array(), tree::OP_OR);
361 $tree = new tree($structure);
362 $this->assertTrue($tree->is_available_for_all());
364 // Tree with normal item in it, not always available.
365 $structure->c[0] = (object)array('type' => 'mock');
366 $tree = new tree($structure);
367 $this->assertFalse($tree->is_available_for_all());
369 // OR tree with one always-available item.
370 $structure->c[1] = self::mock(array('all' => true));
371 $tree = new tree($structure);
372 $this->assertTrue($tree->is_available_for_all());
374 // AND tree with one always-available and one not.
375 $structure->op = '&';
376 $structure->showc = array(true, true);
377 unset($structure->show);
378 $tree = new tree($structure);
379 $this->assertFalse($tree->is_available_for_all());
381 // Test NOT conditions (items not always-available).
382 $structure->op = '!&';
383 $structure->show = true;
384 unset($structure->showc);
385 $tree = new tree($structure);
386 $this->assertFalse($tree->is_available_for_all());
388 // Test again with one item always-available for NOT mode.
389 $structure->c[1]->allnot = true;
390 $tree = new tree($structure);
391 $this->assertTrue($tree->is_available_for_all());
395 * Tests the get_full_information() function.
397 public function test_get_full_information() {
398 global $PAGE;
399 $renderer = $PAGE->get_renderer('core', 'availability');
400 // Setup.
401 $info = new \core_availability\mock_info();
403 // No conditions.
404 $structure = tree::get_root_json(array(), tree::OP_OR);
405 $tree = new tree($structure);
406 $this->assertEquals('', $tree->get_full_information($info));
408 // Condition (normal and NOT).
409 $structure->c = array(
410 self::mock(array('m' => 'thing')));
411 $tree = new tree($structure);
412 $this->assertEquals('SA: [FULL]thing',
413 $tree->get_full_information($info));
414 $structure->op = '!&';
415 $tree = new tree($structure);
416 $this->assertEquals('SA: ![FULL]thing',
417 $tree->get_full_information($info));
419 // Complex structure.
420 $structure->op = '|';
421 $structure->c = array(
422 tree::get_nested_json(array(
423 self::mock(array('m' => '1')),
424 self::mock(array('m' => '2'))), tree::OP_AND),
425 self::mock(array('m' => 3)));
426 $tree = new tree($structure);
427 $this->assertRegExp('~<ul.*<ul.*<li.*1.*<li.*2.*</ul>.*<li.*3~',
428 $renderer->render($tree->get_full_information($info)));
430 // Test intro messages before list. First, OR message.
431 $structure->c = array(
432 self::mock(array('m' => '1')),
433 self::mock(array('m' => '2'))
435 $tree = new tree($structure);
436 $this->assertRegExp('~Not available unless any of:.*<ul>~',
437 $renderer->render($tree->get_full_information($info)));
439 // Now, OR message when not shown.
440 $structure->show = false;
441 $tree = new tree($structure);
442 $this->assertRegExp('~hidden.*<ul>~',
443 $renderer->render($tree->get_full_information($info)));
445 // AND message.
446 $structure->op = '&';
447 unset($structure->show);
448 $structure->showc = array(false, false);
449 $tree = new tree($structure);
450 $this->assertRegExp('~Not available unless:.*<ul>~',
451 $renderer->render($tree->get_full_information($info)));
453 // Hidden markers on items.
454 $this->assertRegExp('~1.*hidden.*2.*hidden~',
455 $renderer->render($tree->get_full_information($info)));
457 // Hidden markers on child tree and items.
458 $structure->c[1] = tree::get_nested_json(array(
459 self::mock(array('m' => '2')),
460 self::mock(array('m' => '3'))), tree::OP_AND);
461 $tree = new tree($structure);
462 $this->assertRegExp('~1.*hidden.*All of \(hidden.*2.*3~',
463 $renderer->render($tree->get_full_information($info)));
464 $structure->c[1]->op = '|';
465 $tree = new tree($structure);
466 $this->assertRegExp('~1.*hidden.*Any of \(hidden.*2.*3~',
467 $renderer->render($tree->get_full_information($info)));
469 // Hidden markers on single-item display, AND and OR.
470 $structure->showc = array(false);
471 $structure->c = array(
472 self::mock(array('m' => '1'))
474 $tree = new tree($structure);
475 $this->assertRegExp('~1.*hidden~',
476 $tree->get_full_information($info));
478 unset($structure->showc);
479 $structure->show = false;
480 $structure->op = '|';
481 $tree = new tree($structure);
482 $this->assertRegExp('~1.*hidden~',
483 $tree->get_full_information($info));
485 // Hidden marker if single item is tree.
486 $structure->c[0] = tree::get_nested_json(array(
487 self::mock(array('m' => '1')),
488 self::mock(array('m' => '2'))), tree::OP_AND);
489 $tree = new tree($structure);
490 $this->assertRegExp('~Not available \(hidden.*1.*2~',
491 $renderer->render($tree->get_full_information($info)));
493 // Single item tree containing single item.
494 unset($structure->c[0]->c[1]);
495 $tree = new tree($structure);
496 $this->assertRegExp('~SA.*1.*hidden~',
497 $tree->get_full_information($info));
501 * Tests the is_empty() function.
503 public function test_is_empty() {
504 // Tree with nothing in should be empty.
505 $structure = tree::get_root_json(array(), tree::OP_OR);
506 $tree = new tree($structure);
507 $this->assertTrue($tree->is_empty());
509 // Tree with something in is not empty.
510 $structure = tree::get_root_json(array(self::mock(array('m' => '1'))), tree::OP_OR);
511 $tree = new tree($structure);
512 $this->assertFalse($tree->is_empty());
516 * Tests the get_all_children() function.
518 public function test_get_all_children() {
519 // Create a tree with nothing in.
520 $structure = tree::get_root_json(array(), tree::OP_OR);
521 $tree1 = new tree($structure);
523 // Create second tree with complex structure.
524 $structure->c = array(
525 tree::get_nested_json(array(
526 self::mock(array('m' => '1')),
527 self::mock(array('m' => '2'))
528 ), tree::OP_OR),
529 self::mock(array('m' => 3)));
530 $tree2 = new tree($structure);
532 // Check list of conditions from both trees.
533 $this->assertEquals(array(), $tree1->get_all_children('core_availability\condition'));
534 $result = $tree2->get_all_children('core_availability\condition');
535 $this->assertEquals(3, count($result));
536 $this->assertEquals('{mock:n,1}', (string)$result[0]);
537 $this->assertEquals('{mock:n,2}', (string)$result[1]);
538 $this->assertEquals('{mock:n,3}', (string)$result[2]);
540 // Check specific type, should give same results.
541 $result2 = $tree2->get_all_children('availability_mock\condition');
542 $this->assertEquals($result, $result2);
546 * Tests the update_dependency_id() function.
548 public function test_update_dependency_id() {
549 // Create tree with structure of 3 mocks.
550 $structure = tree::get_root_json(array(
551 tree::get_nested_json(array(
552 self::mock(array('table' => 'frogs', 'id' => 9)),
553 self::mock(array('table' => 'zombies', 'id' => 9))
555 self::mock(array('table' => 'frogs', 'id' => 9))));
557 // Get 'before' value.
558 $tree = new tree($structure);
559 $before = $tree->save();
561 // Try replacing a table or id that isn't used.
562 $this->assertFalse($tree->update_dependency_id('toads', 9, 13));
563 $this->assertFalse($tree->update_dependency_id('frogs', 7, 8));
564 $this->assertEquals($before, $tree->save());
566 // Replace the zombies one.
567 $this->assertTrue($tree->update_dependency_id('zombies', 9, 666));
568 $after = $tree->save();
569 $this->assertEquals(666, $after->c[0]->c[1]->id);
571 // And the frogs one.
572 $this->assertTrue($tree->update_dependency_id('frogs', 9, 3));
573 $after = $tree->save();
574 $this->assertEquals(3, $after->c[0]->c[0]->id);
575 $this->assertEquals(3, $after->c[1]->id);
579 * Tests the filter_users function.
581 public function test_filter_users() {
582 $info = new \core_availability\mock_info();
583 $checker = new capability_checker($info->get_context());
585 // Don't need to create real users in database, just use these ids.
586 $users = array(1 => null, 2 => null, 3 => null);
588 // Test basic tree with one condition that doesn't filter.
589 $structure = tree::get_root_json(array(self::mock(array())));
590 $tree = new tree($structure);
591 $result = $tree->filter_user_list($users, false, $info, $checker);
592 ksort($result);
593 $this->assertEquals(array(1, 2, 3), array_keys($result));
595 // Now a tree with one condition that filters.
596 $structure = tree::get_root_json(array(self::mock(array('filter' => array(2, 3)))));
597 $tree = new tree($structure);
598 $result = $tree->filter_user_list($users, false, $info, $checker);
599 ksort($result);
600 $this->assertEquals(array(2, 3), array_keys($result));
602 // Tree with two conditions that both filter (|).
603 $structure = tree::get_root_json(array(
604 self::mock(array('filter' => array(3))),
605 self::mock(array('filter' => array(1)))), tree::OP_OR);
606 $tree = new tree($structure);
607 $result = $tree->filter_user_list($users, false, $info, $checker);
608 ksort($result);
609 $this->assertEquals(array(1, 3), array_keys($result));
611 // Tree with OR condition one of which doesn't filter.
612 $structure = tree::get_root_json(array(
613 self::mock(array('filter' => array(3))),
614 self::mock(array())), tree::OP_OR);
615 $tree = new tree($structure);
616 $result = $tree->filter_user_list($users, false, $info, $checker);
617 ksort($result);
618 $this->assertEquals(array(1, 2, 3), array_keys($result));
620 // Tree with two condition that both filter (&).
621 $structure = tree::get_root_json(array(
622 self::mock(array('filter' => array(2, 3))),
623 self::mock(array('filter' => array(1, 2)))));
624 $tree = new tree($structure);
625 $result = $tree->filter_user_list($users, false, $info, $checker);
626 ksort($result);
627 $this->assertEquals(array(2), array_keys($result));
629 // Tree with child tree with NOT condition.
630 $structure = tree::get_root_json(array(
631 tree::get_nested_json(array(
632 self::mock(array('filter' => array(1)))), tree::OP_NOT_AND)));
633 $tree = new tree($structure);
634 $result = $tree->filter_user_list($users, false, $info, $checker);
635 ksort($result);
636 $this->assertEquals(array(2, 3), array_keys($result));
640 * Tests the get_json methods in tree (which are mainly for use in testing
641 * but might be used elsewhere).
643 public function test_get_json() {
644 // Create a simple child object (fake).
645 $child = (object)array('type' => 'fake');
646 $childstr = json_encode($child);
648 // Minimal case.
649 $this->assertEquals(
650 (object)array('op' => '&', 'c' => array()),
651 tree::get_nested_json(array()));
652 // Children and different operator.
653 $this->assertEquals(
654 (object)array('op' => '|', 'c' => array($child, $child)),
655 tree::get_nested_json(array($child, $child), tree::OP_OR));
657 // Root empty.
658 $this->assertEquals('{"op":"&","c":[],"showc":[]}',
659 json_encode(tree::get_root_json(array(), tree::OP_AND)));
660 // Root with children (multi-show operator).
661 $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
662 '],"showc":[true,true]}',
663 json_encode(tree::get_root_json(array($child, $child), tree::OP_AND)));
664 // Root with children (single-show operator).
665 $this->assertEquals('{"op":"|","c":[' . $childstr . ',' . $childstr .
666 '],"show":true}',
667 json_encode(tree::get_root_json(array($child, $child), tree::OP_OR)));
668 // Root with children (specified show boolean).
669 $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
670 '],"showc":[false,false]}',
671 json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, false)));
672 // Root with children (specified show array).
673 $this->assertEquals('{"op":"&","c":[' . $childstr . ',' . $childstr .
674 '],"showc":[true,false]}',
675 json_encode(tree::get_root_json(array($child, $child), tree::OP_AND, array(true, false))));
679 * Tests the behaviour of the counter in unique_sql_parameter().
681 * There was a problem with static counters used to implement a sequence of
682 * parameter placeholders (MDL-53481). As always with static variables, it
683 * is a bit tricky to unit test the behaviour reliably as it depends on the
684 * actual tests executed and also their order.
686 * To minimise risk of false expected behaviour, this test method should be
687 * first one where {@link core_availability\tree::get_user_list_sql()} is
688 * used. We also use higher number of condition instances to increase the
689 * risk of the counter collision, should there remain a problem.
691 public function test_unique_sql_parameter_behaviour() {
692 global $DB;
693 $this->resetAfterTest();
694 $generator = $this->getDataGenerator();
696 // Create a test course with multiple groupings and groups and a student in each of them.
697 $course = $generator->create_course();
698 $user = $generator->create_user();
699 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
700 $generator->enrol_user($user->id, $course->id, $studentroleid);
701 // The total number of groupings and groups must not be greater than 61.
702 // There is a limit in MySQL on the max number of joined tables.
703 $groups = [];
704 for ($i = 0; $i < 25; $i++) {
705 $group = $generator->create_group(array('courseid' => $course->id));
706 groups_add_member($group, $user);
707 $groups[] = $group;
709 $groupings = [];
710 for ($i = 0; $i < 25; $i++) {
711 $groupings[] = $generator->create_grouping(array('courseid' => $course->id));
713 foreach ($groupings as $grouping) {
714 foreach ($groups as $group) {
715 groups_assign_grouping($grouping->id, $group->id);
718 $info = new \core_availability\mock_info($course);
720 // Make a huge tree with 'AND' of all groups and groupings conditions.
721 $conditions = [];
722 foreach ($groups as $group) {
723 $conditions[] = \availability_group\condition::get_json($group->id);
725 foreach ($groupings as $groupingid) {
726 $conditions[] = \availability_grouping\condition::get_json($grouping->id);
728 shuffle($conditions);
729 $tree = new tree(tree::get_root_json($conditions));
730 list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
731 // This must not throw exception.
732 $DB->fix_sql_params($sql, $params);
736 * Tests get_user_list_sql.
738 public function test_get_user_list_sql() {
739 global $DB;
740 $this->resetAfterTest();
741 $generator = $this->getDataGenerator();
743 // Create a test course with 2 groups and users in each combination of them.
744 $course = $generator->create_course();
745 $group1 = $generator->create_group(array('courseid' => $course->id));
746 $group2 = $generator->create_group(array('courseid' => $course->id));
747 $userin1 = $generator->create_user();
748 $userin2 = $generator->create_user();
749 $userinboth = $generator->create_user();
750 $userinneither = $generator->create_user();
751 $studentroleid = $DB->get_field('role', 'id', array('shortname' => 'student'));
752 foreach (array($userin1, $userin2, $userinboth, $userinneither) as $user) {
753 $generator->enrol_user($user->id, $course->id, $studentroleid);
755 groups_add_member($group1, $userin1);
756 groups_add_member($group2, $userin2);
757 groups_add_member($group1, $userinboth);
758 groups_add_member($group2, $userinboth);
759 $info = new \core_availability\mock_info($course);
761 // Tree with single group condition.
762 $tree = new tree(tree::get_root_json(array(
763 \availability_group\condition::get_json($group1->id)
764 )));
765 list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
766 $result = $DB->get_fieldset_sql($sql, $params);
767 sort($result);
768 $this->assertEquals(array($userin1->id, $userinboth->id), $result);
770 // Tree with 'AND' of both group conditions.
771 $tree = new tree(tree::get_root_json(array(
772 \availability_group\condition::get_json($group1->id),
773 \availability_group\condition::get_json($group2->id)
774 )));
775 list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
776 $result = $DB->get_fieldset_sql($sql, $params);
777 sort($result);
778 $this->assertEquals(array($userinboth->id), $result);
780 // Tree with 'AND' of both group conditions.
781 $tree = new tree(tree::get_root_json(array(
782 \availability_group\condition::get_json($group1->id),
783 \availability_group\condition::get_json($group2->id)
784 ), tree::OP_OR));
785 list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
786 $result = $DB->get_fieldset_sql($sql, $params);
787 sort($result);
788 $this->assertEquals(array($userin1->id, $userin2->id, $userinboth->id), $result);
790 // Check with flipped logic (NOT above level of tree).
791 list($sql, $params) = $tree->get_user_list_sql(true, $info, false);
792 $result = $DB->get_fieldset_sql($sql, $params);
793 sort($result);
794 $this->assertEquals(array($userinneither->id), $result);
796 // Tree with 'OR' of group conditions and a non-filtering condition.
797 // The non-filtering condition should mean that ALL users are included.
798 $tree = new tree(tree::get_root_json(array(
799 \availability_group\condition::get_json($group1->id),
800 \availability_date\condition::get_json(\availability_date\condition::DIRECTION_UNTIL, 3)
801 ), tree::OP_OR));
802 list($sql, $params) = $tree->get_user_list_sql(false, $info, false);
803 $this->assertEquals('', $sql);
804 $this->assertEquals(array(), $params);
808 * Utility function to build the PHP structure representing a mock condition.
810 * @param array $params Mock parameters
811 * @return \stdClass Structure object
813 protected static function mock(array $params) {
814 $params['type'] = 'mock';
815 return (object)$params;