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