Differences Between: [Versions 400 and 403] [Versions 401 and 403] [Versions 402 and 403]
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_courseformat; 18 19 use course_modinfo; 20 use moodle_exception; 21 use stdClass; 22 23 /** 24 * Tests for the stateactions class. 25 * 26 * @package core_courseformat 27 * @category test 28 * @copyright 2021 Sara Arjona (sara@moodle.com) 29 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 30 * @coversDefaultClass \core_courseformat\stateactions 31 */ 32 class stateactions_test extends \advanced_testcase { 33 /** 34 * Helper method to create an activity into a section and add it to the $sections and $activities arrays. 35 * 36 * @param int $courseid Course identifier where the activity will be added. 37 * @param string $type Activity type ('forum', 'assign', ...). 38 * @param int $section Section number where the activity will be added. 39 * @param bool $visible Whether the activity will be visible or not. 40 * @return int the activity cm id 41 */ 42 private function create_activity( 43 int $courseid, 44 string $type, 45 int $section, 46 bool $visible = true 47 ): int { 48 49 $activity = $this->getDataGenerator()->create_module( 50 $type, 51 ['course' => $courseid], 52 [ 53 'section' => $section, 54 'visible' => $visible 55 ] 56 ); 57 return $activity->cmid; 58 } 59 60 /** 61 * Helper to create a course and generate a section list. 62 * 63 * @param string $format the course format 64 * @param int $sections the number of sections 65 * @param int[] $hiddensections the section numbers to hide 66 * @return stdClass the course object 67 */ 68 private function create_course(string $format, int $sections, array $hiddensections): stdClass { 69 global $DB; 70 71 $course = $this->getDataGenerator()->create_course(['numsections' => $sections, 'format' => $format]); 72 foreach ($hiddensections as $section) { 73 set_section_visible($course->id, $section, 0); 74 } 75 76 return $course; 77 } 78 79 /** 80 * Return an array if the course references. 81 * 82 * This method is used to create alias to sections and other stuff in the dataProviders. 83 * 84 * @param stdClass $course the course object 85 * @return int[] a relation betwee all references and its element id 86 */ 87 private function course_references(stdClass $course): array { 88 global $DB; 89 90 $references = []; 91 92 $sectionrecords = $DB->get_records('course_sections', ['course' => $course->id]); 93 foreach ($sectionrecords as $id => $section) { 94 $references["section{$section->section}"] = $section->id; 95 } 96 $references['course'] = $course->id; 97 $references['invalidsection'] = -1; 98 $references['invalidcm'] = -1; 99 100 return $references; 101 } 102 103 /** 104 * Translate a references array into current ids. 105 * 106 * @param string[] $references the references list 107 * @param string[] $values the values to translate 108 * @return int[] the list of ids 109 */ 110 private function translate_references(array $references, array $values): array { 111 $result = []; 112 foreach ($values as $value) { 113 $result[] = $references[$value]; 114 } 115 return $result; 116 } 117 118 /** 119 * Generate a sorted and summarized list of an state updates message. 120 * 121 * It is important to note that the order in the update messages are not important in a real scenario 122 * because each message affects a specific part of the course state. However, for the PHPUnit test 123 * have them sorted and classified simplifies the asserts. 124 * 125 * @param stateupdates $updateobj the state updates object 126 * @return array of all data updates. 127 */ 128 private function summarize_updates(stateupdates $updateobj): array { 129 // Check state returned after executing given action. 130 $updatelist = $updateobj->jsonSerialize(); 131 132 // Initial summary structure. 133 $result = [ 134 'create' => [ 135 'course' => [], 136 'section' => [], 137 'cm' => [], 138 'count' => 0, 139 ], 140 'put' => [ 141 'course' => [], 142 'section' => [], 143 'cm' => [], 144 'count' => 0, 145 ], 146 'remove' => [ 147 'course' => [], 148 'section' => [], 149 'cm' => [], 150 'count' => 0, 151 ], 152 ]; 153 foreach ($updatelist as $update) { 154 if (!isset($result[$update->action])) { 155 $result[$update->action] = [ 156 'course' => [], 157 'section' => [], 158 'cm' => [], 159 'count' => 0, 160 ]; 161 } 162 $elementid = $update->fields->id ?? 0; 163 $result[$update->action][$update->name][$elementid] = $update->fields; 164 $result[$update->action]['count']++; 165 } 166 return $result; 167 } 168 169 /** 170 * Enrol, set and create the test user depending on the role name. 171 * 172 * @param stdClass $course the course data 173 * @param string $rolename the testing role name 174 */ 175 private function set_test_user_by_role(stdClass $course, string $rolename) { 176 if ($rolename == 'admin') { 177 $this->setAdminUser(); 178 } else { 179 $user = $this->getDataGenerator()->create_user(); 180 if ($rolename != 'unenroled') { 181 $this->getDataGenerator()->enrol_user($user->id, $course->id, $rolename); 182 } 183 $this->setUser($user); 184 } 185 } 186 187 /** 188 * Test the behaviour course_state. 189 * 190 * @dataProvider get_state_provider 191 * @covers ::course_state 192 * @covers ::section_state 193 * @covers ::cm_state 194 * 195 * @param string $format The course will be created with this course format. 196 * @param string $role The role of the user that will execute the method. 197 * @param string $method the method to call 198 * @param array $params the ids, targetsection and targetcm to use as params 199 * @param array $expectedresults List of the course module names expected after calling the method. 200 * @param bool $expectedexception If this call will raise an exception. 201 202 */ 203 public function test_get_state( 204 string $format, 205 string $role, 206 string $method, 207 array $params, 208 array $expectedresults, 209 bool $expectedexception = false 210 ): void { 211 212 $this->resetAfterTest(); 213 214 // Create a course with 3 sections, 1 of them hidden. 215 $course = $this->create_course($format, 3, [2]); 216 217 $references = $this->course_references($course); 218 219 // Create and enrol user using given role. 220 $this->set_test_user_by_role($course, $role); 221 222 // Add some activities to the course. One visible and one hidden in both sections 1 and 2. 223 $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); 224 $references["cm1"] = $this->create_activity($course->id, 'book', 1, false); 225 $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true); 226 $references["cm3"] = $this->create_activity($course->id, 'page', 2, false); 227 228 if ($expectedexception) { 229 $this->expectException(moodle_exception::class); 230 } 231 232 // Initialise stateupdates. 233 $courseformat = course_get_format($course->id); 234 $updates = new stateupdates($courseformat); 235 236 // Execute given method. 237 $actions = new stateactions(); 238 $actions->$method( 239 $updates, 240 $course, 241 $this->translate_references($references, $params['ids']), 242 $references[$params['targetsectionid']] ?? null, 243 $references[$params['targetcmid']] ?? null 244 ); 245 246 // Format results in a way we can compare easily. 247 $results = $this->summarize_updates($updates); 248 249 // The state actions does not use create or remove actions because they are designed 250 // to refresh parts of the state. 251 $this->assertEquals(0, $results['create']['count']); 252 $this->assertEquals(0, $results['remove']['count']); 253 254 // Validate we have all the expected entries. 255 $expectedtotal = count($expectedresults['course']) + count($expectedresults['section']) + count($expectedresults['cm']); 256 $this->assertEquals($expectedtotal, $results['put']['count']); 257 258 // Validate course, section and cm. 259 foreach ($expectedresults as $name => $referencekeys) { 260 foreach ($referencekeys as $referencekey) { 261 $this->assertArrayHasKey($references[$referencekey], $results['put'][$name]); 262 } 263 } 264 } 265 266 /** 267 * Data provider for data request creation tests. 268 * 269 * @return array the testing scenarios 270 */ 271 public function get_state_provider(): array { 272 return array_merge( 273 $this->course_state_provider('weeks'), 274 $this->course_state_provider('topics'), 275 $this->course_state_provider('social'), 276 $this->section_state_provider('weeks', 'admin'), 277 $this->section_state_provider('weeks', 'editingteacher'), 278 $this->section_state_provider('weeks', 'student'), 279 $this->section_state_provider('topics', 'admin'), 280 $this->section_state_provider('topics', 'editingteacher'), 281 $this->section_state_provider('topics', 'student'), 282 $this->section_state_provider('social', 'admin'), 283 $this->section_state_provider('social', 'editingteacher'), 284 $this->section_state_provider('social', 'student'), 285 $this->cm_state_provider('weeks', 'admin'), 286 $this->cm_state_provider('weeks', 'editingteacher'), 287 $this->cm_state_provider('weeks', 'student'), 288 $this->cm_state_provider('topics', 'admin'), 289 $this->cm_state_provider('topics', 'editingteacher'), 290 $this->cm_state_provider('topics', 'student'), 291 $this->cm_state_provider('social', 'admin'), 292 $this->cm_state_provider('social', 'editingteacher'), 293 $this->cm_state_provider('social', 'student'), 294 ); 295 } 296 297 /** 298 * Course state data provider. 299 * 300 * @param string $format the course format 301 * @return array the testing scenarios 302 */ 303 public function course_state_provider(string $format): array { 304 $expectedexception = ($format === 'social'); 305 return [ 306 // Tests for course_state. 307 "admin $format course_state" => [ 308 'format' => $format, 309 'role' => 'admin', 310 'method' => 'course_state', 311 'params' => [ 312 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null 313 ], 314 'expectedresults' => [ 315 'course' => ['course'], 316 'section' => ['section0', 'section1', 'section2', 'section3'], 317 'cm' => ['cm0', 'cm1', 'cm2', 'cm3'], 318 ], 319 'expectedexception' => $expectedexception, 320 ], 321 "editingteacher $format course_state" => [ 322 'format' => $format, 323 'role' => 'editingteacher', 324 'method' => 'course_state', 325 'params' => [ 326 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null 327 ], 328 'expectedresults' => [ 329 'course' => ['course'], 330 'section' => ['section0', 'section1', 'section2', 'section3'], 331 'cm' => ['cm0', 'cm1', 'cm2', 'cm3'], 332 ], 333 'expectedexception' => $expectedexception, 334 ], 335 "student $format course_state" => [ 336 'format' => $format, 337 'role' => 'student', 338 'method' => 'course_state', 339 'params' => [ 340 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null 341 ], 342 'expectedresults' => [ 343 'course' => ['course'], 344 'section' => ['section0', 'section1', 'section3'], 345 'cm' => ['cm0'], 346 ], 347 'expectedexception' => $expectedexception, 348 ], 349 ]; 350 } 351 352 /** 353 * Section state data provider. 354 * 355 * @param string $format the course format 356 * @param string $role the user role 357 * @return array the testing scenarios 358 */ 359 public function section_state_provider(string $format, string $role): array { 360 361 // Social format will raise an exception and debug messages because it does not 362 // use sections and it does not provide a renderer. 363 $expectedexception = ($format === 'social'); 364 365 // All sections and cms that the user can access to. 366 $usersections = ['section0', 'section1', 'section2', 'section3']; 367 $usercms = ['cm0', 'cm1', 'cm2', 'cm3']; 368 if ($role == 'student') { 369 $usersections = ['section0', 'section1', 'section3']; 370 $usercms = ['cm0']; 371 } 372 373 return [ 374 "$role $format section_state no section" => [ 375 'format' => $format, 376 'role' => $role, 377 'method' => 'section_state', 378 'params' => [ 379 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null 380 ], 381 'expectedresults' => [], 382 'expectedexception' => true, 383 ], 384 "$role $format section_state section 0" => [ 385 'format' => $format, 386 'role' => $role, 387 'method' => 'section_state', 388 'params' => [ 389 'ids' => ['section0'], 'targetsectionid' => null, 'targetcmid' => null 390 ], 391 'expectedresults' => [ 392 'course' => [], 393 'section' => array_intersect(['section0'], $usersections), 394 'cm' => [], 395 ], 396 'expectedexception' => $expectedexception, 397 ], 398 "$role $format section_state visible section" => [ 399 'format' => $format, 400 'role' => $role, 401 'method' => 'section_state', 402 'params' => [ 403 'ids' => ['section1'], 'targetsectionid' => null, 'targetcmid' => null 404 ], 405 'expectedresults' => [ 406 'course' => [], 407 'section' => array_intersect(['section1'], $usersections), 408 'cm' => array_intersect(['cm0', 'cm1'], $usercms), 409 ], 410 'expectedexception' => $expectedexception, 411 ], 412 "$role $format section_state hidden section" => [ 413 'format' => $format, 414 'role' => $role, 415 'method' => 'section_state', 416 'params' => [ 417 'ids' => ['section2'], 'targetsectionid' => null, 'targetcmid' => null 418 ], 419 'expectedresults' => [ 420 'course' => [], 421 'section' => array_intersect(['section2'], $usersections), 422 'cm' => array_intersect(['cm2', 'cm3'], $usercms), 423 ], 424 'expectedexception' => $expectedexception, 425 ], 426 "$role $format section_state several sections" => [ 427 'format' => $format, 428 'role' => $role, 429 'method' => 'section_state', 430 'params' => [ 431 'ids' => ['section1', 'section3'], 'targetsectionid' => null, 'targetcmid' => null 432 ], 433 'expectedresults' => [ 434 'course' => [], 435 'section' => array_intersect(['section1', 'section3'], $usersections), 436 'cm' => array_intersect(['cm0', 'cm1'], $usercms), 437 ], 438 'expectedexception' => $expectedexception, 439 ], 440 "$role $format section_state invalid section" => [ 441 'format' => $format, 442 'role' => $role, 443 'method' => 'section_state', 444 'params' => [ 445 'ids' => ['invalidsection'], 'targetsectionid' => null, 'targetcmid' => null 446 ], 447 'expectedresults' => [], 448 'expectedexception' => true, 449 ], 450 "$role $format section_state using target section" => [ 451 'format' => $format, 452 'role' => $role, 453 'method' => 'section_state', 454 'params' => [ 455 'ids' => ['section1'], 'targetsectionid' => 'section3', 'targetcmid' => null 456 ], 457 'expectedresults' => [ 458 'course' => [], 459 'section' => array_intersect(['section1', 'section3'], $usersections), 460 'cm' => array_intersect(['cm0', 'cm1'], $usercms), 461 ], 462 'expectedexception' => $expectedexception, 463 ], 464 "$role $format section_state using target targetcmid" => [ 465 'format' => $format, 466 'role' => $role, 467 'method' => 'section_state', 468 'params' => [ 469 'ids' => ['section3'], 'targetsectionid' => null, 'targetcmid' => 'cm1' 470 ], 471 'expectedresults' => [ 472 'course' => [], 473 'section' => array_intersect(['section3'], $usersections), 474 'cm' => array_intersect(['cm1'], $usercms), 475 ], 476 'expectedexception' => $expectedexception, 477 ], 478 ]; 479 } 480 481 /** 482 * Course module state data provider. 483 * 484 * @param string $format the course format 485 * @param string $role the user role 486 * @return array the testing scenarios 487 */ 488 public function cm_state_provider(string $format, string $role): array { 489 490 // All sections and cms that the user can access to. 491 $usersections = ['section0', 'section1', 'section2', 'section3']; 492 $usercms = ['cm0', 'cm1', 'cm2', 'cm3']; 493 if ($role == 'student') { 494 $usersections = ['section0', 'section1', 'section3']; 495 $usercms = ['cm0']; 496 } 497 498 return [ 499 "$role $format cm_state no cms" => [ 500 'format' => $format, 501 'role' => $role, 502 'method' => 'cm_state', 503 'params' => [ 504 'ids' => [], 'targetsectionid' => null, 'targetcmid' => null 505 ], 506 'expectedresults' => [], 507 'expectedexception' => true, 508 ], 509 "$role $format cm_state visible cm" => [ 510 'format' => $format, 511 'role' => $role, 512 'method' => 'cm_state', 513 'params' => [ 514 'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => null 515 ], 516 'expectedresults' => [ 517 'course' => [], 518 'section' => array_intersect(['section1'], $usersections), 519 'cm' => array_intersect(['cm0'], $usercms), 520 ], 521 'expectedexception' => false, 522 ], 523 "$role $format cm_state hidden cm" => [ 524 'format' => $format, 525 'role' => $role, 526 'method' => 'cm_state', 527 'params' => [ 528 'ids' => ['cm1'], 'targetsectionid' => null, 'targetcmid' => null 529 ], 530 'expectedresults' => [ 531 'course' => [], 532 'section' => array_intersect(['section1'], $usersections), 533 'cm' => array_intersect(['cm1'], $usercms), 534 ], 535 'expectedexception' => false, 536 ], 537 "$role $format cm_state several cm" => [ 538 'format' => $format, 539 'role' => $role, 540 'method' => 'cm_state', 541 'params' => [ 542 'ids' => ['cm0', 'cm2'], 'targetsectionid' => null, 'targetcmid' => null 543 ], 544 'expectedresults' => [ 545 'course' => [], 546 'section' => array_intersect(['section1', 'section2'], $usersections), 547 'cm' => array_intersect(['cm0', 'cm2'], $usercms), 548 ], 549 'expectedexception' => false, 550 ], 551 "$role $format cm_state using targetsection" => [ 552 'format' => $format, 553 'role' => $role, 554 'method' => 'cm_state', 555 'params' => [ 556 'ids' => ['cm0'], 'targetsectionid' => 'section2', 'targetcmid' => null 557 ], 558 'expectedresults' => [ 559 'course' => [], 560 'section' => array_intersect(['section1', 'section2'], $usersections), 561 'cm' => array_intersect(['cm0'], $usercms), 562 ], 563 'expectedexception' => ($format === 'social'), 564 ], 565 "$role $format cm_state using targetcm" => [ 566 'format' => $format, 567 'role' => $role, 568 'method' => 'cm_state', 569 'params' => [ 570 'ids' => ['cm0'], 'targetsectionid' => null, 'targetcmid' => 'cm3' 571 ], 572 'expectedresults' => [ 573 'course' => [], 574 'section' => array_intersect(['section1', 'section2'], $usersections), 575 'cm' => array_intersect(['cm0', 'cm3'], $usercms), 576 ], 577 'expectedexception' => false, 578 ], 579 "$role $format cm_state using an invalid cm" => [ 580 'format' => $format, 581 'role' => $role, 582 'method' => 'cm_state', 583 'params' => [ 584 'ids' => ['invalidcm'], 'targetsectionid' => null, 'targetcmid' => null 585 ], 586 'expectedresults' => [], 587 'expectedexception' => true, 588 ], 589 ]; 590 } 591 592 /** 593 * Internal method for testing a specific state action. 594 * 595 * @param string $method the method to test 596 * @param string $role the user role 597 * @param string[] $idrefs the sections or cms id references to be used as method params 598 * @param bool $expectedexception whether the call should throw an exception 599 * @param int[] $expectedtotal the expected total number of state indexed by put, remove and create 600 * @param string|null $coursefield the course field to check 601 * @param int|string|null $coursevalue the section field value 602 * @param string|null $sectionfield the section field to check 603 * @param int|string|null $sectionvalue the section field value 604 * @param string|null $cmfield the cm field to check 605 * @param int|string|null $cmvalue the cm field value 606 * @param string|null $targetsection optional target section reference 607 * @param string|null $targetcm optional target cm reference 608 * @return array an array of elements to do extra validations (course, references, results) 609 */ 610 protected function basic_state_text( 611 string $method = 'section_hide', 612 string $role = 'editingteacher', 613 array $idrefs = [], 614 bool $expectedexception = false, 615 array $expectedtotals = [], 616 ?string $coursefield = null, 617 $coursevalue = 0, 618 ?string $sectionfield = null, 619 $sectionvalue = 0, 620 ?string $cmfield = null, 621 $cmvalue = 0, 622 ?string $targetsection = null, 623 ?string $targetcm = null 624 ): array { 625 $this->resetAfterTest(); 626 627 // Create a course with 3 sections, 1 of them hidden. 628 $course = $this->create_course('topics', 3, [2]); 629 630 $references = $this->course_references($course); 631 632 $user = $this->getDataGenerator()->create_user(); 633 $this->getDataGenerator()->enrol_user($user->id, $course->id, $role); 634 $this->setUser($user); 635 636 // Add some activities to the course. One visible and one hidden in both sections 1 and 2. 637 $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); 638 $references["cm1"] = $this->create_activity($course->id, 'book', 1, false); 639 $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true); 640 $references["cm3"] = $this->create_activity($course->id, 'page', 2, false); 641 $references["cm4"] = $this->create_activity($course->id, 'forum', 2, false); 642 $references["cm5"] = $this->create_activity($course->id, 'wiki', 2, false); 643 644 if ($expectedexception) { 645 $this->expectException(moodle_exception::class); 646 } 647 648 // Initialise stateupdates. 649 $courseformat = course_get_format($course->id); 650 $updates = new stateupdates($courseformat); 651 652 // Execute the method. 653 $actions = new stateactions(); 654 $actions->$method( 655 $updates, 656 $course, 657 $this->translate_references($references, $idrefs), 658 ($targetsection) ? $references[$targetsection] : null, 659 ($targetcm) ? $references[$targetcm] : null, 660 ); 661 662 // Format results in a way we can compare easily. 663 $results = $this->summarize_updates($updates); 664 665 // Validate we have all the expected entries. 666 $this->assertEquals($expectedtotals['create'] ?? 0, $results['create']['count']); 667 $this->assertEquals($expectedtotals['remove'] ?? 0, $results['remove']['count']); 668 $this->assertEquals($expectedtotals['put'] ?? 0, $results['put']['count']); 669 670 // Validate course, section and cm. 671 if (!empty($coursefield)) { 672 foreach ($results['put']['course'] as $courseid) { 673 $this->assertEquals($coursevalue, $results['put']['course'][$courseid][$coursefield]); 674 } 675 } 676 if (!empty($sectionfield)) { 677 foreach ($results['put']['section'] as $section) { 678 $this->assertEquals($sectionvalue, $section->$sectionfield); 679 } 680 } 681 if (!empty($cmfield)) { 682 foreach ($results['put']['cm'] as $cm) { 683 $this->assertEquals($cmvalue, $cm->$cmfield); 684 } 685 } 686 return [ 687 'course' => $course, 688 'references' => $references, 689 'results' => $results, 690 ]; 691 } 692 693 /** 694 * Test for section_hide 695 * 696 * @covers ::section_hide 697 * @dataProvider basic_role_provider 698 * @param string $role the user role 699 * @param bool $expectedexception if it will expect an exception. 700 */ 701 public function test_section_hide( 702 string $role = 'editingteacher', 703 bool $expectedexception = false 704 ): void { 705 $this->basic_state_text( 706 'section_hide', 707 $role, 708 ['section1', 'section2', 'section3'], 709 $expectedexception, 710 ['put' => 9], 711 null, 712 null, 713 'visible', 714 0, 715 null, 716 null 717 ); 718 } 719 720 /** 721 * Test for section_hide 722 * 723 * @covers ::section_show 724 * @dataProvider basic_role_provider 725 * @param string $role the user role 726 * @param bool $expectedexception if it will expect an exception. 727 */ 728 public function test_section_show( 729 string $role = 'editingteacher', 730 bool $expectedexception = false 731 ): void { 732 $this->basic_state_text( 733 'section_show', 734 $role, 735 ['section1', 'section2', 'section3'], 736 $expectedexception, 737 ['put' => 9], 738 null, 739 null, 740 'visible', 741 1, 742 null, 743 null 744 ); 745 } 746 747 /** 748 * Test for cm_show 749 * 750 * @covers ::cm_show 751 * @dataProvider basic_role_provider 752 * @param string $role the user role 753 * @param bool $expectedexception if it will expect an exception. 754 */ 755 public function test_cm_show( 756 string $role = 'editingteacher', 757 bool $expectedexception = false 758 ): void { 759 $this->basic_state_text( 760 'cm_show', 761 $role, 762 ['cm0', 'cm1', 'cm2', 'cm3'], 763 $expectedexception, 764 ['put' => 4], 765 null, 766 null, 767 null, 768 null, 769 'visible', 770 1 771 ); 772 } 773 774 /** 775 * Test for cm_hide 776 * 777 * @covers ::cm_hide 778 * @dataProvider basic_role_provider 779 * @param string $role the user role 780 * @param bool $expectedexception if it will expect an exception. 781 */ 782 public function test_cm_hide( 783 string $role = 'editingteacher', 784 bool $expectedexception = false 785 ): void { 786 $this->basic_state_text( 787 'cm_hide', 788 $role, 789 ['cm0', 'cm1', 'cm2', 'cm3'], 790 $expectedexception, 791 ['put' => 4], 792 null, 793 null, 794 null, 795 null, 796 'visible', 797 0 798 ); 799 } 800 801 /** 802 * Test for cm_stealth 803 * 804 * @covers ::cm_stealth 805 * @dataProvider basic_role_provider 806 * @param string $role the user role 807 * @param bool $expectedexception if it will expect an exception. 808 */ 809 public function test_cm_stealth( 810 string $role = 'editingteacher', 811 bool $expectedexception = false 812 ): void { 813 set_config('allowstealth', 1); 814 $this->basic_state_text( 815 'cm_stealth', 816 $role, 817 ['cm0', 'cm1', 'cm2', 'cm3'], 818 $expectedexception, 819 ['put' => 4], 820 null, 821 null, 822 null, 823 null, 824 'stealth', 825 1 826 ); 827 // Disable stealth. 828 set_config('allowstealth', 0); 829 // When stealth are disabled the validation is a but more complex because they depends 830 // also on the section visibility (legacy stealth). 831 $this->basic_state_text( 832 'cm_stealth', 833 $role, 834 ['cm0', 'cm1'], 835 $expectedexception, 836 ['put' => 2], 837 null, 838 null, 839 null, 840 null, 841 'stealth', 842 0 843 ); 844 $this->basic_state_text( 845 'cm_stealth', 846 $role, 847 ['cm2', 'cm3'], 848 $expectedexception, 849 ['put' => 2], 850 null, 851 null, 852 null, 853 null, 854 'stealth', 855 1 856 ); 857 } 858 859 /** 860 * Data provider for basic role tests. 861 * 862 * @return array the testing scenarios 863 */ 864 public function basic_role_provider() { 865 return [ 866 'editingteacher' => [ 867 'role' => 'editingteacher', 868 'expectedexception' => false, 869 ], 870 'teacher' => [ 871 'role' => 'teacher', 872 'expectedexception' => true, 873 ], 874 'student' => [ 875 'role' => 'student', 876 'expectedexception' => true, 877 ], 878 'guest' => [ 879 'role' => 'guest', 880 'expectedexception' => true, 881 ], 882 ]; 883 } 884 885 /** 886 * Duplicate course module method. 887 * 888 * @covers ::cm_duplicate 889 * @dataProvider cm_duplicate_provider 890 * @param string $targetsection the target section (empty for none) 891 * @param bool $validcms if uses valid cms 892 * @param string $role the current user role name 893 * @param bool $expectedexception if the test will raise an exception 894 */ 895 public function test_cm_duplicate( 896 string $targetsection = '', 897 bool $validcms = true, 898 string $role = 'admin', 899 bool $expectedexception = false 900 ) { 901 $this->resetAfterTest(); 902 903 // Create a course with 3 sections. 904 $course = $this->create_course('topics', 3, []); 905 906 $references = $this->course_references($course); 907 908 // Create and enrol user using given role. 909 $this->set_test_user_by_role($course, $role); 910 911 // Add some activities to the course. One visible and one hidden in both sections 1 and 2. 912 $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); 913 $references["cm1"] = $this->create_activity($course->id, 'page', 2, false); 914 915 if ($expectedexception) { 916 $this->expectException(moodle_exception::class); 917 } 918 919 // Initialise stateupdates. 920 $courseformat = course_get_format($course->id); 921 $updates = new stateupdates($courseformat); 922 923 // Execute method. 924 $targetsectionid = (!empty($targetsection)) ? $references[$targetsection] : null; 925 $cmrefs = ($validcms) ? ['cm0', 'cm1'] : ['invalidcm']; 926 $actions = new stateactions(); 927 $actions->cm_duplicate( 928 $updates, 929 $course, 930 $this->translate_references($references, $cmrefs), 931 $targetsectionid, 932 ); 933 934 // Check the new elements in the course structure. 935 $originalsections = [ 936 'assign' => $references['section1'], 937 'page' => $references['section2'], 938 ]; 939 $modinfo = course_modinfo::instance($course); 940 $cms = $modinfo->get_cms(); 941 $i = 0; 942 foreach ($cms as $cmid => $cminfo) { 943 if ($cmid == $references['cm0'] || $cmid == $references['cm1']) { 944 continue; 945 } 946 $references["newcm$i"] = $cmid; 947 if ($targetsectionid) { 948 $this->assertEquals($targetsectionid, $cminfo->section); 949 } else { 950 $this->assertEquals($originalsections[$cminfo->modname], $cminfo->section); 951 } 952 $i++; 953 } 954 955 // Check the resulting updates. 956 $results = $this->summarize_updates($updates); 957 958 if ($targetsectionid) { 959 $this->assertArrayHasKey($references[$targetsection], $results['put']['section']); 960 } else { 961 $this->assertArrayHasKey($references['section1'], $results['put']['section']); 962 $this->assertArrayHasKey($references['section2'], $results['put']['section']); 963 } 964 $countcms = ($targetsection == 'section3' || $targetsection === '') ? 2 : 3; 965 $this->assertCount($countcms, $results['put']['cm']); 966 $this->assertArrayHasKey($references['newcm0'], $results['put']['cm']); 967 $this->assertArrayHasKey($references['newcm1'], $results['put']['cm']); 968 } 969 970 /** 971 * Duplicate course module data provider. 972 * 973 * @return array the testing scenarios 974 */ 975 public function cm_duplicate_provider(): array { 976 return [ 977 'valid cms without target section' => [ 978 'targetsection' => '', 979 'validcms' => true, 980 'role' => 'admin', 981 'expectedexception' => false, 982 ], 983 'valid cms targeting an empty section' => [ 984 'targetsection' => 'section3', 985 'validcms' => true, 986 'role' => 'admin', 987 'expectedexception' => false, 988 ], 989 'valid cms targeting a section with activities' => [ 990 'targetsection' => 'section2', 991 'validcms' => true, 992 'role' => 'admin', 993 'expectedexception' => false, 994 ], 995 'invalid cms without target section' => [ 996 'targetsection' => '', 997 'validcms' => false, 998 'role' => 'admin', 999 'expectedexception' => true, 1000 ], 1001 'invalid cms with target section' => [ 1002 'targetsection' => 'section3', 1003 'validcms' => false, 1004 'role' => 'admin', 1005 'expectedexception' => true, 1006 ], 1007 'student role with target section' => [ 1008 'targetsection' => 'section3', 1009 'validcms' => true, 1010 'role' => 'student', 1011 'expectedexception' => true, 1012 ], 1013 'student role without target section' => [ 1014 'targetsection' => '', 1015 'validcms' => true, 1016 'role' => 'student', 1017 'expectedexception' => true, 1018 ], 1019 'unrenolled user with target section' => [ 1020 'targetsection' => 'section3', 1021 'validcms' => true, 1022 'role' => 'unenroled', 1023 'expectedexception' => true, 1024 ], 1025 'unrenolled user without target section' => [ 1026 'targetsection' => '', 1027 'validcms' => true, 1028 'role' => 'unenroled', 1029 'expectedexception' => true, 1030 ], 1031 ]; 1032 } 1033 1034 /** 1035 * Test for cm_delete 1036 * 1037 * @covers ::cm_delete 1038 * @dataProvider basic_role_provider 1039 * @param string $role the user role 1040 * @param bool $expectedexception if it will expect an exception. 1041 */ 1042 public function test_cm_delete( 1043 string $role = 'editingteacher', 1044 bool $expectedexception = false 1045 ): void { 1046 $this->resetAfterTest(); 1047 // We want modules to be deleted for good. 1048 set_config('coursebinenable', 0, 'tool_recyclebin'); 1049 1050 $info = $this->basic_state_text( 1051 'cm_delete', 1052 $role, 1053 ['cm2', 'cm3'], 1054 $expectedexception, 1055 ['remove' => 2, 'put' => 1], 1056 ); 1057 1058 $course = $info['course']; 1059 $references = $info['references']; 1060 $results = $info['results']; 1061 $courseformat = course_get_format($course->id); 1062 1063 $this->assertArrayNotHasKey($references['cm0'], $results['remove']['cm']); 1064 $this->assertArrayNotHasKey($references['cm1'], $results['remove']['cm']); 1065 $this->assertArrayHasKey($references['cm2'], $results['remove']['cm']); 1066 $this->assertArrayHasKey($references['cm3'], $results['remove']['cm']); 1067 $this->assertArrayNotHasKey($references['cm4'], $results['remove']['cm']); 1068 $this->assertArrayNotHasKey($references['cm5'], $results['remove']['cm']); 1069 1070 // Check the new section cm list. 1071 $newcmlist = $this->translate_references($references, ['cm4', 'cm5']); 1072 $section = $results['put']['section'][$references['section2']]; 1073 $this->assertEquals($newcmlist, $section->cmlist); 1074 1075 // Check activities are deleted. 1076 $modinfo = $courseformat->get_modinfo(); 1077 $cms = $modinfo->get_cms(); 1078 $this->assertArrayHasKey($references['cm0'], $cms); 1079 $this->assertArrayHasKey($references['cm1'], $cms); 1080 $this->assertArrayNotHasKey($references['cm2'], $cms); 1081 $this->assertArrayNotHasKey($references['cm3'], $cms); 1082 $this->assertArrayHasKey($references['cm4'], $cms); 1083 $this->assertArrayHasKey($references['cm5'], $cms); 1084 } 1085 1086 /** 1087 * Test for cm_moveright 1088 * 1089 * @covers ::cm_moveright 1090 * @dataProvider basic_role_provider 1091 * @param string $role the user role 1092 * @param bool $expectedexception if it will expect an exception. 1093 */ 1094 public function test_cm_moveright( 1095 string $role = 'editingteacher', 1096 bool $expectedexception = false 1097 ): void { 1098 $this->basic_state_text( 1099 'cm_moveright', 1100 $role, 1101 ['cm0', 'cm1', 'cm2', 'cm3'], 1102 $expectedexception, 1103 ['put' => 4], 1104 null, 1105 null, 1106 null, 1107 null, 1108 'indent', 1109 1 1110 ); 1111 } 1112 1113 /** 1114 * Test for cm_moveleft 1115 * 1116 * @covers ::cm_moveleft 1117 * @dataProvider basic_role_provider 1118 * @param string $role the user role 1119 * @param bool $expectedexception if it will expect an exception. 1120 */ 1121 public function test_cm_moveleft( 1122 string $role = 'editingteacher', 1123 bool $expectedexception = false 1124 ): void { 1125 $this->basic_state_text( 1126 'cm_moveleft', 1127 $role, 1128 ['cm0', 'cm1', 'cm2', 'cm3'], 1129 $expectedexception, 1130 ['put' => 4], 1131 null, 1132 null, 1133 null, 1134 null, 1135 'indent', 1136 0 1137 ); 1138 } 1139 1140 /** 1141 * Test for cm_nogroups 1142 * 1143 * @covers ::cm_nogroups 1144 * @dataProvider basic_role_provider 1145 * @param string $role the user role 1146 * @param bool $expectedexception if it will expect an exception. 1147 */ 1148 public function test_cm_nogroups( 1149 string $role = 'editingteacher', 1150 bool $expectedexception = false 1151 ): void { 1152 $this->basic_state_text( 1153 'cm_nogroups', 1154 $role, 1155 ['cm0', 'cm1', 'cm2', 'cm3'], 1156 $expectedexception, 1157 ['put' => 4], 1158 null, 1159 null, 1160 null, 1161 null, 1162 'groupmode', 1163 NOGROUPS 1164 ); 1165 } 1166 1167 /** 1168 * Test for cm_visiblegroups 1169 * 1170 * @covers ::cm_visiblegroups 1171 * @dataProvider basic_role_provider 1172 * @param string $role the user role 1173 * @param bool $expectedexception if it will expect an exception. 1174 */ 1175 public function test_cm_visiblegroups( 1176 string $role = 'editingteacher', 1177 bool $expectedexception = false 1178 ): void { 1179 $this->basic_state_text( 1180 'cm_visiblegroups', 1181 $role, 1182 ['cm0', 'cm1', 'cm2', 'cm3'], 1183 $expectedexception, 1184 ['put' => 4], 1185 null, 1186 null, 1187 null, 1188 null, 1189 'groupmode', 1190 VISIBLEGROUPS 1191 ); 1192 } 1193 1194 /** 1195 * Test for cm_separategroups 1196 * 1197 * @covers ::cm_separategroups 1198 * @dataProvider basic_role_provider 1199 * @param string $role the user role 1200 * @param bool $expectedexception if it will expect an exception. 1201 */ 1202 public function test_cm_separategroups( 1203 string $role = 'editingteacher', 1204 bool $expectedexception = false 1205 ): void { 1206 $this->basic_state_text( 1207 'cm_separategroups', 1208 $role, 1209 ['cm0', 'cm1', 'cm2', 'cm3'], 1210 $expectedexception, 1211 ['put' => 4], 1212 null, 1213 null, 1214 null, 1215 null, 1216 'groupmode', 1217 SEPARATEGROUPS 1218 ); 1219 } 1220 1221 /** 1222 * Test for section_move_after 1223 * 1224 * @covers ::section_move_after 1225 * @dataProvider section_move_after_provider 1226 * @param string[] $sectiontomove the sections to move 1227 * @param string $targetsection the target section reference 1228 * @param string[] $finalorder the final sections order 1229 * @param string[] $updatedcms the list of cms in the state updates 1230 * @param int $totalputs the total amount of put updates 1231 */ 1232 public function test_section_move_after( 1233 array $sectiontomove, 1234 string $targetsection, 1235 array $finalorder, 1236 array $updatedcms, 1237 int $totalputs 1238 ): void { 1239 $this->resetAfterTest(); 1240 1241 $course = $this->create_course('topics', 8, []); 1242 1243 $references = $this->course_references($course); 1244 1245 // Add some activities to the course. One visible and one hidden in both sections 1 and 2. 1246 $references["cm0"] = $this->create_activity($course->id, 'assign', 1, true); 1247 $references["cm1"] = $this->create_activity($course->id, 'book', 1, false); 1248 $references["cm2"] = $this->create_activity($course->id, 'glossary', 2, true); 1249 $references["cm3"] = $this->create_activity($course->id, 'page', 2, false); 1250 $references["cm4"] = $this->create_activity($course->id, 'forum', 3, false); 1251 $references["cm5"] = $this->create_activity($course->id, 'wiki', 3, false); 1252 1253 $user = $this->getDataGenerator()->create_user(); 1254 $this->getDataGenerator()->enrol_user($user->id, $course->id, 'editingteacher'); 1255 $this->setUser($user); 1256 1257 // Initialise stateupdates. 1258 $courseformat = course_get_format($course->id); 1259 $updates = new stateupdates($courseformat); 1260 1261 // Execute the method. 1262 $actions = new stateactions(); 1263 $actions->section_move_after( 1264 $updates, 1265 $course, 1266 $this->translate_references($references, $sectiontomove), 1267 $references[$targetsection] 1268 ); 1269 1270 // Format results in a way we can compare easily. 1271 $results = $this->summarize_updates($updates); 1272 1273 // Validate we have all the expected entries. 1274 $this->assertEquals(0, $results['create']['count']); 1275 $this->assertEquals(0, $results['remove']['count']); 1276 // Moving a section puts: 1277 // - The course state. 1278 // - All sections state. 1279 // - The cm states related to the moved and target sections. 1280 $this->assertEquals($totalputs, $results['put']['count']); 1281 1282 // Course state should contain the sorted list of sections (section zero + 8 sections). 1283 $finalsectionids = $this->translate_references($references, $finalorder); 1284 $coursestate = reset($results['put']['course']); 1285 $this->assertEquals($finalsectionids, $coursestate->sectionlist); 1286 // All sections should be present in the update. 1287 $this->assertCount(9, $results['put']['section']); 1288 // Only cms from the affected sections should be updated. 1289 $cmids = $this->translate_references($references, $updatedcms); 1290 $cms = $results['put']['cm']; 1291 foreach ($cmids as $cmid) { 1292 $this->assertArrayHasKey($cmid, $cms); 1293 } 1294 } 1295 1296 /** 1297 * Provider for test_section_move_after. 1298 * 1299 * @return array the testing scenarios 1300 */ 1301 public function section_move_after_provider(): array { 1302 return [ 1303 'Move sections down' => [ 1304 'sectiontomove' => ['section2', 'section4'], 1305 'targetsection' => 'section7', 1306 'finalorder' => [ 1307 'section0', 1308 'section1', 1309 'section3', 1310 'section5', 1311 'section6', 1312 'section7', 1313 'section2', 1314 'section4', 1315 'section8', 1316 ], 1317 'updatedcms' => ['cm2', 'cm3'], 1318 'totalputs' => 12, 1319 ], 1320 'Move sections up' => [ 1321 'sectiontomove' => ['section3', 'section5'], 1322 'targetsection' => 'section1', 1323 'finalorder' => [ 1324 'section0', 1325 'section1', 1326 'section3', 1327 'section5', 1328 'section2', 1329 'section4', 1330 'section6', 1331 'section7', 1332 'section8', 1333 ], 1334 'updatedcms' => ['cm0', 'cm1', 'cm4', 'cm5'], 1335 'totalputs' => 14, 1336 ], 1337 'Move sections in the middle' => [ 1338 'sectiontomove' => ['section2', 'section5'], 1339 'targetsection' => 'section3', 1340 'finalorder' => [ 1341 'section0', 1342 'section1', 1343 'section3', 1344 'section2', 1345 'section5', 1346 'section4', 1347 'section6', 1348 'section7', 1349 'section8', 1350 ], 1351 'updatedcms' => ['cm2', 'cm3', 'cm4', 'cm5'], 1352 'totalputs' => 14, 1353 ], 1354 'Move sections on top' => [ 1355 'sectiontomove' => ['section3', 'section5'], 1356 'targetsection' => 'section0', 1357 'finalorder' => [ 1358 'section0', 1359 'section3', 1360 'section5', 1361 'section1', 1362 'section2', 1363 'section4', 1364 'section6', 1365 'section7', 1366 'section8', 1367 ], 1368 'updatedcms' => ['cm4', 'cm5'], 1369 'totalputs' => 12, 1370 ], 1371 'Move sections on bottom' => [ 1372 'sectiontomove' => ['section3', 'section5'], 1373 'targetsection' => 'section8', 1374 'finalorder' => [ 1375 'section0', 1376 'section1', 1377 'section2', 1378 'section4', 1379 'section6', 1380 'section7', 1381 'section8', 1382 'section3', 1383 'section5', 1384 ], 1385 'updatedcms' => ['cm4', 'cm5'], 1386 'totalputs' => 12, 1387 ], 1388 ]; 1389 } 1390 1391 /** 1392 * Test for section_move_after capability checks. 1393 * 1394 * @covers ::section_move_after 1395 * @dataProvider basic_role_provider 1396 * @param string $role the user role 1397 * @param bool $expectedexception if it will expect an exception. 1398 */ 1399 public function test_section_move_after_capabilities( 1400 string $role = 'editingteacher', 1401 bool $expectedexception = false 1402 ): void { 1403 $this->resetAfterTest(); 1404 // We want modules to be deleted for good. 1405 set_config('coursebinenable', 0, 'tool_recyclebin'); 1406 1407 $info = $this->basic_state_text( 1408 'section_move_after', 1409 $role, 1410 ['section2'], 1411 $expectedexception, 1412 ['put' => 9], 1413 null, 1414 0, 1415 null, 1416 0, 1417 null, 1418 0, 1419 'section0' 1420 ); 1421 } 1422 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body