See Release Notes
Long Term Support Release
Differences Between: [Versions 311 and 401]
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 declare(strict_types = 1); 18 19 namespace mod_scorm; 20 21 use advanced_testcase; 22 use cm_info; 23 use coding_exception; 24 use mod_scorm\completion\custom_completion; 25 use moodle_exception; 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 global $CFG; 30 require_once($CFG->libdir . '/completionlib.php'); 31 require_once($CFG->dirroot.'/mod/scorm/locallib.php'); 32 33 /** 34 * Class for unit testing mod_scorm/custom_completion. 35 * 36 * @package mod_scorm 37 * @copyright 2021 Michael Hawkins <michaelh@moodle.com> 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class custom_completion_test extends advanced_testcase { 41 42 /** 43 * Data provider for get_state(). 44 * 45 * @return array[] 46 */ 47 public function get_state_provider(): array { 48 49 // Prepare various reusable user scorm track data used to mock various completion states/requirements. 50 $completionincomplete = (object) [ 51 'id' => 1, 52 'scoid' => 1, 53 'element' => 'cmi.completion_status', 54 'value' => 'incomplete', 55 ]; 56 57 $completionpassed = (object) [ 58 'id' => 1, 59 'scoid' => 1, 60 'element' => 'cmi.completion_status', 61 'value' => 'passed', 62 ]; 63 64 $completioncompleted = (object) [ 65 'id' => 1, 66 'scoid' => 2, 67 'element' => 'cmi.success_status', 68 'value' => 'completed', 69 ]; 70 71 $completionscorefail = (object) [ 72 'id' => 1, 73 'scoid' => 1, 74 'element' => 'cmi.score.raw', 75 'value' => '20', 76 ]; 77 78 $completionscorepass = (object) [ 79 'id' => 1, 80 'scoid' => 1, 81 'element' => 'cmi.score.raw', 82 'value' => '100', 83 ]; 84 85 return [ 86 'Undefined completion requirement' => [ 87 'somenonexistentrule', COMPLETION_ENABLED, [$completionincomplete], 0, null, coding_exception::class 88 ], 89 'Completion status requirement not available' => [ 90 'completionstatusrequired', COMPLETION_DISABLED, [$completionincomplete], 0, null, moodle_exception::class 91 ], 92 'Completion status Passed required, user has no completion status recorded' => [ 93 'completionstatusrequired', 2, [], 0, COMPLETION_INCOMPLETE, null 94 ], 95 'Completion status Passed required, user has not passed, can make another attempt' => [ 96 'completionstatusrequired', 2, [$completionincomplete], 0, COMPLETION_INCOMPLETE, null 97 ], 98 'Completion status Passed required, user has passed' => [ 99 'completionstatusrequired', 2, [$completionpassed], 0, COMPLETION_COMPLETE, null 100 ], 101 'Completion status Completed required, user has not completed, can make another attempt' => [ 102 'completionstatusrequired', 4, [$completionincomplete], 2, COMPLETION_INCOMPLETE, null 103 ], 104 'Completion status Completed required, user has completed' => [ 105 'completionstatusrequired', 4, [$completioncompleted], 1, COMPLETION_COMPLETE, null 106 ], 107 'Completion status Passed or Completed required, user has only completed, can make another attempt' => [ 108 'completionstatusrequired', 6, [$completioncompleted], 0, COMPLETION_COMPLETE, null 109 ], 110 'Completion status Passed or Completed required, user has completed and passed' => [ 111 'completionstatusrequired', 6, [$completionpassed, $completioncompleted], 0, COMPLETION_COMPLETE, null 112 ], 113 'Completion status Passed or Completed required, user has not passed or completed, but has another attempt' => [ 114 'completionstatusrequired', 6, [$completionincomplete], 2, COMPLETION_INCOMPLETE, null 115 ], 116 'Completion status Passed or Completed required, user has used all attempts, but not passed or completed' => [ 117 'completionstatusrequired', 6, [$completionincomplete], 1, COMPLETION_COMPLETE_FAIL, null 118 ], 119 'Completion status Passed required, user has used all attempts and completed, but not passed' => [ 120 'completionstatusrequired', 2, [$completioncompleted], 1, COMPLETION_COMPLETE_FAIL, null 121 ], 122 'Completion status Completed required, user has used all attempts, but not completed' => [ 123 'completionstatusrequired', 4, [$completionincomplete], 1, COMPLETION_COMPLETE_FAIL, null 124 ], 125 'Completion status Passed or Completed required, user has used all attempts, but not passed' => [ 126 'completionstatusrequired', 6, [$completionincomplete, $completioncompleted], 2, COMPLETION_COMPLETE, null 127 ], 128 'Completion score required, user has no score' => [ 129 'completionscorerequired', 80, [], 0, COMPLETION_INCOMPLETE, null 130 ], 131 'Completion score required, user score does not meet requirement, can make another attempt' => [ 132 'completionscorerequired', 80, [$completionscorefail], 0, COMPLETION_INCOMPLETE, null 133 ], 134 'Completion score required, user has used all attempts, but not reached the score' => [ 135 'completionscorerequired', 80, [$completionscorefail], 1, COMPLETION_COMPLETE_FAIL, null 136 ], 137 'Completion score required, user score meets requirement' => [ 138 'completionscorerequired', 80, [$completionscorepass], 0, COMPLETION_COMPLETE, null 139 ], 140 'Completion of all scos required, user has not completed, can make another attempt' => [ 141 'completionstatusallscos', 1, [$completionincomplete, $completioncompleted], 3, COMPLETION_INCOMPLETE, null 142 ], 143 'Completion of all scos required, user has completed' => [ 144 'completionstatusallscos', 1, [$completionpassed, $completioncompleted], 2, COMPLETION_COMPLETE, null 145 ], 146 'Completion of all scos required, user has used all attempts, but not completed all scos' => [ 147 'completionstatusallscos', 1, [$completionincomplete, $completioncompleted], 2, COMPLETION_COMPLETE_FAIL, null 148 ], 149 ]; 150 } 151 152 /** 153 * Test for get_state(). 154 * 155 * @dataProvider get_state_provider 156 * @param string $rule The custom completion condition. 157 * @param int $rulevalue The custom completion rule value. 158 * @param array $uservalue The relevant record database mock data recorded against the user for the rule. 159 * @param int $maxattempts The number of attempts the activity allows (0 = unlimited). 160 * @param int|null $status Expected completion status for the rule. 161 * @param string|null $exception Expected exception. 162 */ 163 public function test_get_state(string $rule, int $rulevalue, array $uservalue, int $maxattempts, ?int $status, 164 ?string $exception) { 165 global $DB; 166 167 if (!is_null($exception)) { 168 $this->expectException($exception); 169 } 170 171 // Custom completion rule data for cm_info::customdata. 172 $customdataval = [ 173 'customcompletionrules' => [ 174 $rule => $rulevalue 175 ] 176 ]; 177 178 // Build a mock cm_info instance. 179 $mockcminfo = $this->getMockBuilder(cm_info::class) 180 ->disableOriginalConstructor() 181 ->onlyMethods(['__get']) 182 ->getMock(); 183 184 // Mock the return of the magic getter method when fetching the cm_info object's 185 // customdata and instance values. 186 $mockcminfo->expects($this->any()) 187 ->method('__get') 188 ->will($this->returnValueMap([ 189 ['customdata', $customdataval], 190 ['instance', 1], 191 ])); 192 193 // Mock the DB call fetching user's SCORM track data. 194 $DB = $this->createMock(get_class($DB)); 195 $DB->expects($this->atMost(1)) 196 ->method('get_records_sql') 197 ->willReturn($uservalue); 198 199 // For completed all scos tests, mock the DB call that fetches the sco IDs. 200 if ($rule === 'completionstatusallscos') { 201 $returnscos = []; 202 203 foreach ($uservalue as $data) { 204 $returnscos[$data->scoid] = (object) ['id' => $data->scoid]; 205 } 206 207 $DB->expects($this->atMost(1)) 208 ->method('get_records') 209 ->willReturn($returnscos); 210 } 211 212 // Anything not complete will check if attempts have been exhausted, mock the DB calls for that check. 213 if ($status != COMPLETION_COMPLETE) { 214 $mockscorm = (object) [ 215 'id' => 1, 216 'version' => SCORM_13, 217 'grademethod' => GRADESCOES, 218 'maxattempt' => $maxattempts, 219 ]; 220 221 $DB->expects($this->atMost(1)) 222 ->method('get_record') 223 ->willReturn($mockscorm); 224 225 $DB->expects($this->atMost(1)) 226 ->method('count_records_sql') 227 ->willReturn(count($uservalue)); 228 } 229 230 $customcompletion = new custom_completion($mockcminfo, 2); 231 232 $this->assertEquals($status, $customcompletion->get_state($rule)); 233 } 234 235 /** 236 * Test for get_defined_custom_rules(). 237 */ 238 public function test_get_defined_custom_rules() { 239 $expectedrules = [ 240 'completionstatusrequired', 241 'completionscorerequired', 242 'completionstatusallscos', 243 ]; 244 245 $definedrules = custom_completion::get_defined_custom_rules(); 246 $this->assertCount(3, $definedrules); 247 248 foreach ($definedrules as $definedrule) { 249 $this->assertContains($definedrule, $expectedrules); 250 } 251 } 252 253 /** 254 * Test for get_defined_custom_rule_descriptions(). 255 */ 256 public function test_get_custom_rule_descriptions() { 257 // Get defined custom rules. 258 $rules = custom_completion::get_defined_custom_rules(); 259 260 // Build a mock cm_info instance. 261 $mockcminfo = $this->getMockBuilder(cm_info::class) 262 ->disableOriginalConstructor() 263 ->onlyMethods(['__get']) 264 ->getMock(); 265 266 // Instantiate a custom_completion object using the mocked cm_info. 267 $customcompletion = new custom_completion($mockcminfo, 1); 268 269 // Get custom rule descriptions. 270 $ruledescriptions = $customcompletion->get_custom_rule_descriptions(); 271 272 // Confirm that defined rules and rule descriptions are consistent with each other. 273 $this->assertEquals(count($rules), count($ruledescriptions)); 274 foreach ($rules as $rule) { 275 $this->assertArrayHasKey($rule, $ruledescriptions); 276 } 277 } 278 279 /** 280 * Test for is_defined(). 281 */ 282 public function test_is_defined() { 283 // Build a mock cm_info instance. 284 $mockcminfo = $this->getMockBuilder(cm_info::class) 285 ->disableOriginalConstructor() 286 ->getMock(); 287 288 $customcompletion = new custom_completion($mockcminfo, 1); 289 290 // All rules are defined. 291 $this->assertTrue($customcompletion->is_defined('completionstatusrequired')); 292 $this->assertTrue($customcompletion->is_defined('completionscorerequired')); 293 $this->assertTrue($customcompletion->is_defined('completionstatusallscos')); 294 295 // Undefined rule is not found. 296 $this->assertFalse($customcompletion->is_defined('somerandomrule')); 297 } 298 299 /** 300 * Data provider for test_get_available_custom_rules(). 301 * 302 * @return array[] 303 */ 304 public function get_available_custom_rules_provider(): array { 305 return [ 306 'Completion status enabled only' => [ 307 [ 308 'completionstatusrequired' => 4, 309 'completionscorerequired' => COMPLETION_DISABLED, 310 'completionstatusallscos' => COMPLETION_DISABLED, 311 ], 312 ['completionstatusrequired'], 313 ], 314 'Completion score enabled only' => [ 315 [ 316 'completionstatusrequired' => COMPLETION_DISABLED, 317 'completionscorerequired' => 80, 318 'completionstatusallscos' => COMPLETION_DISABLED, 319 ], 320 ['completionscorerequired'], 321 ], 322 'Completion status and all scos completed both enabled' => [ 323 [ 324 'completionstatusrequired' => 2, 325 'completionscorerequired' => COMPLETION_DISABLED, 326 'completionstatusallscos' => COMPLETION_ENABLED, 327 ], 328 ['completionstatusrequired', 'completionstatusallscos'], 329 ], 330 'Completion status and score both enabled' => [ 331 [ 332 'completionstatusrequired' => COMPLETION_ENABLED, 333 'completionscorerequired' => 80, 334 'completionstatusallscos' => COMPLETION_DISABLED, 335 ], 336 ['completionstatusrequired', 'completionscorerequired'], 337 ], 338 'All custom completion conditions enabled' => [ 339 [ 340 'completionstatusrequired' => 6, 341 'completionscorerequired' => 80, 342 'completionstatusallscos' => COMPLETION_ENABLED, 343 ], 344 ['completionstatusrequired', 'completionscorerequired', 'completionstatusallscos'], 345 ], 346 ]; 347 } 348 349 /** 350 * Test for get_available_custom_rules(). 351 * 352 * @dataProvider get_available_custom_rules_provider 353 * @param array $completionrulesvalues 354 * @param array $expected 355 */ 356 public function test_get_available_custom_rules(array $completionrulesvalues, array $expected) { 357 $customcompletionrules = [ 358 'customcompletionrules' => $completionrulesvalues, 359 ]; 360 361 // Build a mock cm_info instance. 362 $mockcminfo = $this->getMockBuilder(cm_info::class) 363 ->disableOriginalConstructor() 364 ->onlyMethods(['__get']) 365 ->getMock(); 366 367 // Mock the return of magic getter for the customdata attribute. 368 $mockcminfo->expects($this->any()) 369 ->method('__get') 370 ->with('customdata') 371 ->willReturn($customcompletionrules); 372 373 $customcompletion = new custom_completion($mockcminfo, 1); 374 $this->assertEquals($expected, $customcompletion->get_available_custom_rules()); 375 } 376 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body