[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 /** 18 * Completion tests. 19 * 20 * @package core_completion 21 * @category phpunit 22 * @copyright 2008 Sam Marshall 23 * @copyright 2013 Frédéric Massart 24 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 25 */ 26 27 defined('MOODLE_INTERNAL') || die(); 28 29 global $CFG; 30 require_once($CFG->libdir.'/completionlib.php'); 31 32 class core_completionlib_testcase extends advanced_testcase { 33 protected $course; 34 protected $user; 35 protected $module1; 36 protected $module2; 37 38 protected function mock_setup() { 39 global $DB, $CFG, $USER; 40 41 $this->resetAfterTest(); 42 43 $DB = $this->getMock(get_class($DB)); 44 $CFG->enablecompletion = COMPLETION_ENABLED; 45 $USER = (object)array('id' =>314159); 46 } 47 48 /** 49 * Create course with user and activities. 50 */ 51 protected function setup_data() { 52 global $DB, $CFG; 53 54 $this->resetAfterTest(); 55 56 // Enable completion before creating modules, otherwise the completion data is not written in DB. 57 $CFG->enablecompletion = true; 58 59 // Create a course with activities. 60 $this->course = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); 61 $this->user = $this->getDataGenerator()->create_user(); 62 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 63 $this->assertNotEmpty($studentrole); 64 65 // Get manual enrolment plugin and enrol user. 66 require_once($CFG->dirroot.'/enrol/manual/locallib.php'); 67 $manplugin = enrol_get_plugin('manual'); 68 $maninstance = $DB->get_record('enrol', array('courseid' => $this->course->id, 'enrol' => 'manual'), '*', MUST_EXIST); 69 $manplugin->enrol_user($maninstance, $this->user->id, $studentrole->id); 70 $this->assertEquals(1, $DB->count_records('user_enrolments')); 71 72 $this->module1 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id)); 73 $this->module2 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id)); 74 } 75 76 /** 77 * Asserts that two variables are equal. 78 * 79 * @param mixed $expected 80 * @param mixed $actual 81 * @param string $message 82 * @param float $delta 83 * @param integer $maxDepth 84 * @param boolean $canonicalize 85 * @param boolean $ignoreCase 86 */ 87 public static function assertEquals($expected, $actual, $message = '', $delta = 0, $maxDepth = 10, $canonicalize = FALSE, $ignoreCase = FALSE) { 88 // Nasty cheating hack: prevent random failures on timemodified field. 89 if (is_object($expected) and is_object($actual)) { 90 if (property_exists($expected, 'timemodified') and property_exists($actual, 'timemodified')) { 91 if ($expected->timemodified + 1 == $actual->timemodified) { 92 $expected = clone($expected); 93 $expected->timemodified = $actual->timemodified; 94 } 95 } 96 } 97 parent::assertEquals($expected, $actual, $message, $delta, $maxDepth, $canonicalize, $ignoreCase); 98 } 99 100 public function test_is_enabled() { 101 global $CFG; 102 $this->mock_setup(); 103 104 // Config alone. 105 $CFG->enablecompletion = COMPLETION_DISABLED; 106 $this->assertEquals(COMPLETION_DISABLED, completion_info::is_enabled_for_site()); 107 $CFG->enablecompletion = COMPLETION_ENABLED; 108 $this->assertEquals(COMPLETION_ENABLED, completion_info::is_enabled_for_site()); 109 110 // Course. 111 $course = (object)array('id' =>13); 112 $c = new completion_info($course); 113 $course->enablecompletion = COMPLETION_DISABLED; 114 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled()); 115 $course->enablecompletion = COMPLETION_ENABLED; 116 $this->assertEquals(COMPLETION_ENABLED, $c->is_enabled()); 117 $CFG->enablecompletion = COMPLETION_DISABLED; 118 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled()); 119 120 // Course and CM. 121 $cm = new stdClass(); 122 $cm->completion = COMPLETION_TRACKING_MANUAL; 123 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled($cm)); 124 $CFG->enablecompletion = COMPLETION_ENABLED; 125 $course->enablecompletion = COMPLETION_DISABLED; 126 $this->assertEquals(COMPLETION_DISABLED, $c->is_enabled($cm)); 127 $course->enablecompletion = COMPLETION_ENABLED; 128 $this->assertEquals(COMPLETION_TRACKING_MANUAL, $c->is_enabled($cm)); 129 $cm->completion = COMPLETION_TRACKING_NONE; 130 $this->assertEquals(COMPLETION_TRACKING_NONE, $c->is_enabled($cm)); 131 $cm->completion = COMPLETION_TRACKING_AUTOMATIC; 132 $this->assertEquals(COMPLETION_TRACKING_AUTOMATIC, $c->is_enabled($cm)); 133 } 134 135 public function test_update_state() { 136 $this->mock_setup(); 137 138 $c = $this->getMock('completion_info', array('is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'), array((object)array('id'=>42))); 139 $cm = (object)array('id'=>13, 'course'=>42); 140 141 // Not enabled, should do nothing. 142 $c->expects($this->at(0)) 143 ->method('is_enabled') 144 ->with($cm) 145 ->will($this->returnValue(false)); 146 $c->update_state($cm); 147 148 // Enabled, but current state is same as possible result, do nothing. 149 $current = (object)array('completionstate'=>COMPLETION_COMPLETE); 150 $c->expects($this->at(0)) 151 ->method('is_enabled') 152 ->with($cm) 153 ->will($this->returnValue(true)); 154 $c->expects($this->at(1)) 155 ->method('get_data') 156 ->with($cm, false, 0) 157 ->will($this->returnValue($current)); 158 $c->update_state($cm, COMPLETION_COMPLETE); 159 160 // Enabled, but current state is a specific one and new state is just 161 // complete, so do nothing. 162 $current->completionstate = COMPLETION_COMPLETE_PASS; 163 $c->expects($this->at(0)) 164 ->method('is_enabled') 165 ->with($cm) 166 ->will($this->returnValue(true)); 167 $c->expects($this->at(1)) 168 ->method('get_data') 169 ->with($cm, false, 0) 170 ->will($this->returnValue($current)); 171 $c->update_state($cm, COMPLETION_COMPLETE); 172 173 // Manual, change state (no change). 174 $cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_MANUAL); 175 $current->completionstate=COMPLETION_COMPLETE; 176 $c->expects($this->at(0)) 177 ->method('is_enabled') 178 ->with($cm) 179 ->will($this->returnValue(true)); 180 $c->expects($this->at(1)) 181 ->method('get_data') 182 ->with($cm, false, 0) 183 ->will($this->returnValue($current)); 184 $c->update_state($cm, COMPLETION_COMPLETE); 185 186 // Manual, change state (change). 187 $c->expects($this->at(0)) 188 ->method('is_enabled') 189 ->with($cm) 190 ->will($this->returnValue(true)); 191 $c->expects($this->at(1)) 192 ->method('get_data') 193 ->with($cm, false, 0) 194 ->will($this->returnValue($current)); 195 $changed = clone($current); 196 $changed->timemodified = time(); 197 $changed->completionstate = COMPLETION_INCOMPLETE; 198 $c->expects($this->at(2)) 199 ->method('internal_set_data') 200 ->with($cm, $changed); 201 $c->update_state($cm, COMPLETION_INCOMPLETE); 202 203 // Auto, change state. 204 $cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_AUTOMATIC); 205 $current = (object)array('completionstate'=>COMPLETION_COMPLETE); 206 $c->expects($this->at(0)) 207 ->method('is_enabled') 208 ->with($cm) 209 ->will($this->returnValue(true)); 210 $c->expects($this->at(1)) 211 ->method('get_data') 212 ->with($cm, false, 0) 213 ->will($this->returnValue($current)); 214 $c->expects($this->at(2)) 215 ->method('internal_get_state') 216 ->will($this->returnValue(COMPLETION_COMPLETE_PASS)); 217 $changed = clone($current); 218 $changed->timemodified = time(); 219 $changed->completionstate = COMPLETION_COMPLETE_PASS; 220 $c->expects($this->at(3)) 221 ->method('internal_set_data') 222 ->with($cm, $changed); 223 $c->update_state($cm, COMPLETION_COMPLETE_PASS); 224 } 225 226 public function test_internal_get_state() { 227 global $DB; 228 $this->mock_setup(); 229 230 $c = $this->getMock('completion_info', array('internal_get_grade_state'), array((object)array('id'=>42))); 231 $cm = (object)array('id'=>13, 'course'=>42, 'completiongradeitemnumber'=>null); 232 233 // If view is required, but they haven't viewed it yet. 234 $cm->completionview = COMPLETION_VIEW_REQUIRED; 235 $current = (object)array('viewed'=>COMPLETION_NOT_VIEWED); 236 $this->assertEquals(COMPLETION_INCOMPLETE, $c->internal_get_state($cm, 123, $current)); 237 238 // OK set view not required. 239 $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED; 240 241 // Test not getting module name. 242 $cm->modname='label'; 243 $this->assertEquals(COMPLETION_COMPLETE, $c->internal_get_state($cm, 123, $current)); 244 245 // Test getting module name. 246 $cm->module = 13; 247 unset($cm->modname); 248 /** @var $DB PHPUnit_Framework_MockObject_MockObject */ 249 $DB->expects($this->once()) 250 ->method('get_field') 251 ->with('modules', 'name', array('id'=>13)) 252 ->will($this->returnValue('lable')); 253 $this->assertEquals(COMPLETION_COMPLETE, $c->internal_get_state($cm, 123, $current)); 254 255 // Note: This function is not fully tested (including kind of the main part) because: 256 // * the grade_item/grade_grade calls are static and can't be mocked, 257 // * the plugin_supports call is static and can't be mocked. 258 } 259 260 public function test_set_module_viewed() { 261 $this->mock_setup(); 262 263 $c = $this->getMock('completion_info', 264 array('delete_all_state', 'get_tracked_users', 'update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'), 265 array((object)array('id'=>42))); 266 $cm = (object)array('id'=>13, 'course'=>42); 267 268 // Not tracking completion, should do nothing. 269 $cm->completionview = COMPLETION_VIEW_NOT_REQUIRED; 270 $c->set_module_viewed($cm); 271 272 // Tracking completion but completion is disabled, should do nothing. 273 $cm->completionview = COMPLETION_VIEW_REQUIRED; 274 $c->expects($this->at(0)) 275 ->method('is_enabled') 276 ->with($cm) 277 ->will($this->returnValue(false)); 278 $c->set_module_viewed($cm); 279 280 // Now it's enabled, we expect it to get data. If data already has 281 // viewed, still do nothing. 282 $c->expects($this->at(0)) 283 ->method('is_enabled') 284 ->with($cm) 285 ->will($this->returnValue(true)); 286 $c->expects($this->at(1)) 287 ->method('get_data') 288 ->with($cm, 0) 289 ->will($this->returnValue((object)array('viewed'=>COMPLETION_VIEWED))); 290 $c->set_module_viewed($cm); 291 292 // OK finally one that hasn't been viewed, now it should set it viewed 293 // and update state. 294 $c->expects($this->at(0)) 295 ->method('is_enabled') 296 ->with($cm) 297 ->will($this->returnValue(true)); 298 $c->expects($this->at(1)) 299 ->method('get_data') 300 ->with($cm, false, 1337) 301 ->will($this->returnValue((object)array('viewed'=>COMPLETION_NOT_VIEWED))); 302 $c->expects($this->at(2)) 303 ->method('internal_set_data') 304 ->with($cm, (object)array('viewed'=>COMPLETION_VIEWED)); 305 $c->expects($this->at(3)) 306 ->method('update_state') 307 ->with($cm, COMPLETION_COMPLETE, 1337); 308 $c->set_module_viewed($cm, 1337); 309 } 310 311 public function test_count_user_data() { 312 global $DB; 313 $this->mock_setup(); 314 315 $course = (object)array('id'=>13); 316 $cm = (object)array('id'=>42); 317 318 /** @var $DB PHPUnit_Framework_MockObject_MockObject */ 319 $DB->expects($this->at(0)) 320 ->method('get_field_sql') 321 ->will($this->returnValue(666)); 322 323 $c = new completion_info($course); 324 $this->assertEquals(666, $c->count_user_data($cm)); 325 } 326 327 public function test_delete_all_state() { 328 global $DB, $SESSION; 329 $this->mock_setup(); 330 331 $course = (object)array('id'=>13); 332 $cm = (object)array('id'=>42, 'course'=>13); 333 $c = new completion_info($course); 334 335 // Check it works ok without data in session. 336 /** @var $DB PHPUnit_Framework_MockObject_MockObject */ 337 $DB->expects($this->at(0)) 338 ->method('delete_records') 339 ->with('course_modules_completion', array('coursemoduleid'=>42)) 340 ->will($this->returnValue(true)); 341 $c->delete_all_state($cm); 342 343 // Build up a session to check it deletes the right bits from it 344 // (and not other bits). 345 $SESSION->completioncache = array(); 346 $SESSION->completioncache[13] = array(); 347 $SESSION->completioncache[13][42] = 'foo'; 348 $SESSION->completioncache[13][43] = 'foo'; 349 $SESSION->completioncache[14] = array(); 350 $SESSION->completioncache[14][42] = 'foo'; 351 $DB->expects($this->at(0)) 352 ->method('delete_records') 353 ->with('course_modules_completion', array('coursemoduleid'=>42)) 354 ->will($this->returnValue(true)); 355 $c->delete_all_state($cm); 356 $this->assertEquals(array(13=>array(43=>'foo'), 14=>array(42=>'foo')), $SESSION->completioncache); 357 } 358 359 public function test_reset_all_state() { 360 global $DB; 361 $this->mock_setup(); 362 363 $c = $this->getMock('completion_info', 364 array('delete_all_state', 'get_tracked_users', 'update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'), 365 array((object)array('id'=>42))); 366 367 $cm = (object)array('id'=>13, 'course'=>42, 'completion'=>COMPLETION_TRACKING_AUTOMATIC); 368 369 /** @var $DB PHPUnit_Framework_MockObject_MockObject */ 370 $DB->expects($this->at(0)) 371 ->method('get_recordset') 372 ->will($this->returnValue( 373 new core_completionlib_fake_recordset(array((object)array('id'=>1, 'userid'=>100), (object)array('id'=>2, 'userid'=>101))))); 374 375 $c->expects($this->at(0)) 376 ->method('delete_all_state') 377 ->with($cm); 378 379 $c->expects($this->at(1)) 380 ->method('get_tracked_users') 381 ->will($this->returnValue(array( 382 (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh'), 383 (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy')))); 384 385 $c->expects($this->at(2)) 386 ->method('update_state') 387 ->with($cm, COMPLETION_UNKNOWN, 100); 388 $c->expects($this->at(3)) 389 ->method('update_state') 390 ->with($cm, COMPLETION_UNKNOWN, 101); 391 $c->expects($this->at(4)) 392 ->method('update_state') 393 ->with($cm, COMPLETION_UNKNOWN, 201); 394 395 $c->reset_all_state($cm); 396 } 397 398 public function test_get_data() { 399 global $DB, $SESSION; 400 $this->mock_setup(); 401 402 $c = new completion_info((object)array('id'=>42)); 403 $cm = (object)array('id'=>13, 'course'=>42); 404 405 // 1. Not current user, record exists. 406 $sillyrecord = (object)array('frog'=>'kermit'); 407 408 /** @var $DB PHPUnit_Framework_MockObject_MockObject */ 409 $DB->expects($this->at(0)) 410 ->method('get_record') 411 ->with('course_modules_completion', array('coursemoduleid'=>13, 'userid'=>123)) 412 ->will($this->returnValue($sillyrecord)); 413 $result = $c->get_data($cm, false, 123); 414 $this->assertEquals($sillyrecord, $result); 415 $this->assertFalse(isset($SESSION->completioncache)); 416 417 // 2. Not current user, default record, whole course (ignored). 418 $DB->expects($this->at(0)) 419 ->method('get_record') 420 ->with('course_modules_completion', array('coursemoduleid'=>13, 'userid'=>123)) 421 ->will($this->returnValue(false)); 422 $result=$c->get_data($cm, true, 123); 423 $this->assertEquals((object)array( 424 'id'=>'0', 'coursemoduleid'=>13, 'userid'=>123, 'completionstate'=>0, 425 'viewed'=>0, 'timemodified'=>0), $result); 426 $this->assertFalse(isset($SESSION->completioncache)); 427 428 // 3. Current user, single record, not from cache. 429 $DB->expects($this->at(0)) 430 ->method('get_record') 431 ->with('course_modules_completion', array('coursemoduleid'=>13, 'userid'=>314159)) 432 ->will($this->returnValue($sillyrecord)); 433 $result = $c->get_data($cm); 434 $this->assertEquals($sillyrecord, $result); 435 $this->assertEquals($sillyrecord, $SESSION->completioncache[42][13]); 436 // When checking time(), allow for second overlaps. 437 $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2); 438 439 // 4. Current user, 'whole course', but from cache. 440 $result = $c->get_data($cm, true); 441 $this->assertEquals($sillyrecord, $result); 442 443 // 5. Current user, single record, cache expired 444 $SESSION->completioncache[42]['updated']=37; // Quite a long time ago. 445 $now = time(); 446 $SESSION->completioncache[17]['updated']=$now; 447 $SESSION->completioncache[39]['updated']=72; // Also a long time ago. 448 $DB->expects($this->at(0)) 449 ->method('get_record') 450 ->with('course_modules_completion', array('coursemoduleid'=>13, 'userid'=>314159)) 451 ->will($this->returnValue($sillyrecord)); 452 $result = $c->get_data($cm, false); 453 $this->assertEquals($sillyrecord, $result); 454 455 // Check that updated value is right, then fudge it to make next compare work. 456 $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2); 457 $SESSION->completioncache[42]['updated']=$now; 458 // Check things got expired from cache. 459 $this->assertEquals(array(42=>array(13=>$sillyrecord, 'updated'=>$now), 17=>array('updated'=>$now)), $SESSION->completioncache); 460 461 // 6. Current user, 'whole course' and record not in cache. 462 unset($SESSION->completioncache); 463 464 // Scenario: Completion data exists for one CMid. 465 $basicrecord = (object)array('coursemoduleid'=>13); 466 $DB->expects($this->at(0)) 467 ->method('get_records_sql') 468 ->will($this->returnValue(array('1'=>$basicrecord))); 469 470 // There are two CMids in total, the one we had data for and another one. 471 $modinfo = new stdClass(); 472 $modinfo->cms = array((object)array('id'=>13), (object)array('id'=>14)); 473 $result = $c->get_data($cm, true, 0, $modinfo); 474 475 // Check result. 476 $this->assertEquals($basicrecord, $result); 477 478 // Check the cache contents. 479 $this->assertTrue(time()-$SESSION->completioncache[42]['updated']<2); 480 $SESSION->completioncache[42]['updated'] = $now; 481 $this->assertEquals(array(42=>array(13=>$basicrecord, 14=>(object)array( 482 'id'=>'0', 'coursemoduleid'=>14, 'userid'=>314159, 'completionstate'=>0, 483 'viewed'=>0, 'timemodified'=>0), 'updated'=>$now)), $SESSION->completioncache); 484 } 485 486 public function test_internal_set_data() { 487 global $DB, $SESSION; 488 $this->setup_data(); 489 490 $this->setUser($this->user); 491 $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); 492 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); 493 $cm = get_coursemodule_from_instance('forum', $forum->id); 494 $c = new completion_info($this->course); 495 496 // 1) Test with new data. 497 $data = new stdClass(); 498 $data->id = 0; 499 $data->userid = $this->user->id; 500 $data->coursemoduleid = $cm->id; 501 $data->completionstate = COMPLETION_COMPLETE; 502 $data->timemodified = time(); 503 $data->viewed = COMPLETION_NOT_VIEWED; 504 505 $c->internal_set_data($cm, $data); 506 $d1 = $DB->get_field('course_modules_completion', 'id', array('coursemoduleid' => $cm->id)); 507 $this->assertEquals($d1, $data->id); 508 $this->assertEquals(array($this->course->id => array($cm->id => $data)), $SESSION->completioncache); 509 510 // 2) Test with existing data and for different user (not cached). 511 unset($SESSION->completioncache); 512 $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); 513 $cm2 = get_coursemodule_from_instance('forum', $forum2->id); 514 $newuser = $this->getDataGenerator()->create_user(); 515 516 $d2 = new stdClass(); 517 $d2->id = 7; 518 $d2->userid = $newuser->id; 519 $d2->coursemoduleid = $cm2->id; 520 $d2->completionstate = COMPLETION_COMPLETE; 521 $d2->timemodified = time(); 522 $d2->viewed = COMPLETION_NOT_VIEWED; 523 $c->internal_set_data($cm2, $d2); 524 $this->assertFalse(isset($SESSION->completioncache)); 525 526 // 3) Test where it THINKS the data is new (from cache) but actually 527 // in the database it has been set since. 528 // 1) Test with new data. 529 $forum3 = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); 530 $cm3 = get_coursemodule_from_instance('forum', $forum3->id); 531 $newuser2 = $this->getDataGenerator()->create_user(); 532 $d3 = new stdClass(); 533 $d3->id = 13; 534 $d3->userid = $newuser2->id; 535 $d3->coursemoduleid = $cm3->id; 536 $d3->completionstate = COMPLETION_COMPLETE; 537 $d3->timemodified = time(); 538 $d3->viewed = COMPLETION_NOT_VIEWED; 539 $DB->insert_record('course_modules_completion', $d3); 540 $c->internal_set_data($cm, $data); 541 } 542 543 public function test_get_progress_all() { 544 global $DB; 545 $this->mock_setup(); 546 547 $c = $this->getMock('completion_info', 548 array('delete_all_state', 'get_tracked_users', 'update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'), 549 array((object)array('id'=>42))); 550 551 // 1) Basic usage. 552 $c->expects($this->at(0)) 553 ->method('get_tracked_users') 554 ->with(false, array(), 0, '', '', '', null) 555 ->will($this->returnValue(array( 556 (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh'), 557 (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy')))); 558 $DB->expects($this->at(0)) 559 ->method('get_in_or_equal') 560 ->with(array(100, 201)) 561 ->will($this->returnValue(array(' IN (100, 201)', array()))); 562 $progress1 = (object)array('userid'=>100, 'coursemoduleid'=>13); 563 $progress2 = (object)array('userid'=>201, 'coursemoduleid'=>14); 564 $DB->expects($this->at(1)) 565 ->method('get_recordset_sql') 566 ->will($this->returnValue(new core_completionlib_fake_recordset(array($progress1, $progress2)))); 567 568 $this->assertEquals(array( 569 100 => (object)array('id'=>100, 'firstname'=>'Woot', 'lastname'=>'Plugh', 570 'progress'=>array(13=>$progress1)), 571 201 => (object)array('id'=>201, 'firstname'=>'Vroom', 'lastname'=>'Xyzzy', 572 'progress'=>array(14=>$progress2)), 573 ), $c->get_progress_all(false)); 574 575 // 2) With more than 1, 000 results. 576 $tracked = array(); 577 $ids = array(); 578 $progress = array(); 579 for ($i = 100; $i<2000; $i++) { 580 $tracked[] = (object)array('id'=>$i, 'firstname'=>'frog', 'lastname'=>$i); 581 $ids[] = $i; 582 $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>13); 583 $progress[] = (object)array('userid'=>$i, 'coursemoduleid'=>14); 584 } 585 $c->expects($this->at(0)) 586 ->method('get_tracked_users') 587 ->with(true, 3, 0, '', '', '', null) 588 ->will($this->returnValue($tracked)); 589 $DB->expects($this->at(0)) 590 ->method('get_in_or_equal') 591 ->with(array_slice($ids, 0, 1000)) 592 ->will($this->returnValue(array(' IN whatever', array()))); 593 $DB->expects($this->at(1)) 594 ->method('get_recordset_sql') 595 ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 0, 1000)))); 596 597 $DB->expects($this->at(2)) 598 ->method('get_in_or_equal') 599 ->with(array_slice($ids, 1000)) 600 ->will($this->returnValue(array(' IN whatever2', array()))); 601 $DB->expects($this->at(3)) 602 ->method('get_recordset_sql') 603 ->will($this->returnValue(new core_completionlib_fake_recordset(array_slice($progress, 1000)))); 604 605 $result = $c->get_progress_all(true, 3); 606 $resultok = true; 607 $resultok = $resultok && ($ids == array_keys($result)); 608 609 foreach ($result as $userid => $data) { 610 $resultok = $resultok && $data->firstname == 'frog'; 611 $resultok = $resultok && $data->lastname == $userid; 612 $resultok = $resultok && $data->id == $userid; 613 $cms = $data->progress; 614 $resultok = $resultok && (array(13, 14) == array_keys($cms)); 615 $resultok = $resultok && ((object)array('userid'=>$userid, 'coursemoduleid'=>13) == $cms[13]); 616 $resultok = $resultok && ((object)array('userid'=>$userid, 'coursemoduleid'=>14) == $cms[14]); 617 } 618 $this->assertTrue($resultok); 619 } 620 621 public function test_inform_grade_changed() { 622 $this->mock_setup(); 623 624 $c = $this->getMock('completion_info', 625 array('delete_all_state', 'get_tracked_users', 'update_state', 'internal_get_grade_state', 'is_enabled', 'get_data', 'internal_get_state', 'internal_set_data'), 626 array((object)array('id'=>42))); 627 628 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>null); 629 $item = (object)array('itemnumber'=>3, 'gradepass'=>1, 'hidden'=>0); 630 $grade = (object)array('userid'=>31337, 'finalgrade'=>0, 'rawgrade'=>0); 631 632 // Not enabled (should do nothing). 633 $c->expects($this->at(0)) 634 ->method('is_enabled') 635 ->with($cm) 636 ->will($this->returnValue(false)); 637 $c->inform_grade_changed($cm, $item, $grade, false); 638 639 // Enabled but still no grade completion required, should still do nothing. 640 $c->expects($this->at(0)) 641 ->method('is_enabled') 642 ->with($cm) 643 ->will($this->returnValue(true)); 644 $c->inform_grade_changed($cm, $item, $grade, false); 645 646 // Enabled and completion required but item number is wrong, does nothing. 647 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>7); 648 $c->expects($this->at(0)) 649 ->method('is_enabled') 650 ->with($cm) 651 ->will($this->returnValue(true)); 652 $c->inform_grade_changed($cm, $item, $grade, false); 653 654 // Enabled and completion required and item number right. It is supposed 655 // to call update_state with the new potential state being obtained from 656 // internal_get_grade_state. 657 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>3); 658 $grade = (object)array('userid'=>31337, 'finalgrade'=>1, 'rawgrade'=>0); 659 $c->expects($this->at(0)) 660 ->method('is_enabled') 661 ->with($cm) 662 ->will($this->returnValue(true)); 663 $c->expects($this->at(1)) 664 ->method('update_state') 665 ->with($cm, COMPLETION_COMPLETE_PASS, 31337) 666 ->will($this->returnValue(true)); 667 $c->inform_grade_changed($cm, $item, $grade, false); 668 669 // Same as above but marked deleted. It is supposed to call update_state 670 // with new potential state being COMPLETION_INCOMPLETE. 671 $cm = (object)array('course'=>42, 'id'=>13, 'completion'=>0, 'completiongradeitemnumber'=>3); 672 $grade = (object)array('userid'=>31337, 'finalgrade'=>1, 'rawgrade'=>0); 673 $c->expects($this->at(0)) 674 ->method('is_enabled') 675 ->with($cm) 676 ->will($this->returnValue(true)); 677 $c->expects($this->at(1)) 678 ->method('update_state') 679 ->with($cm, COMPLETION_INCOMPLETE, 31337) 680 ->will($this->returnValue(true)); 681 $c->inform_grade_changed($cm, $item, $grade, true); 682 } 683 684 public function test_internal_get_grade_state() { 685 $this->mock_setup(); 686 687 $item = new stdClass; 688 $grade = new stdClass; 689 690 $item->gradepass = 4; 691 $item->hidden = 0; 692 $grade->rawgrade = 4.0; 693 $grade->finalgrade = null; 694 695 // Grade has pass mark and is not hidden, user passes. 696 $this->assertEquals( 697 COMPLETION_COMPLETE_PASS, 698 completion_info::internal_get_grade_state($item, $grade)); 699 700 // Same but user fails. 701 $grade->rawgrade = 3.9; 702 $this->assertEquals( 703 COMPLETION_COMPLETE_FAIL, 704 completion_info::internal_get_grade_state($item, $grade)); 705 706 // User fails on raw grade but passes on final. 707 $grade->finalgrade = 4.0; 708 $this->assertEquals( 709 COMPLETION_COMPLETE_PASS, 710 completion_info::internal_get_grade_state($item, $grade)); 711 712 // Item is hidden. 713 $item->hidden = 1; 714 $this->assertEquals( 715 COMPLETION_COMPLETE, 716 completion_info::internal_get_grade_state($item, $grade)); 717 718 // Item isn't hidden but has no pass mark. 719 $item->hidden = 0; 720 $item->gradepass = 0; 721 $this->assertEquals( 722 COMPLETION_COMPLETE, 723 completion_info::internal_get_grade_state($item, $grade)); 724 } 725 726 public function test_get_activities() { 727 global $CFG; 728 $this->resetAfterTest(); 729 730 // Enable completion before creating modules, otherwise the completion data is not written in DB. 731 $CFG->enablecompletion = true; 732 733 // Create a course with mixed auto completion data. 734 $course = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); 735 $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); 736 $completionmanual = array('completion' => COMPLETION_TRACKING_MANUAL); 737 $completionnone = array('completion' => COMPLETION_TRACKING_NONE); 738 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionauto); 739 $page = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionauto); 740 $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionmanual); 741 742 $forum2 = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionnone); 743 $page2 = $this->getDataGenerator()->create_module('page', array('course' => $course->id), $completionnone); 744 $data2 = $this->getDataGenerator()->create_module('data', array('course' => $course->id), $completionnone); 745 746 // Create data in another course to make sure it's not considered. 747 $course2 = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); 748 $c2forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id), $completionauto); 749 $c2page = $this->getDataGenerator()->create_module('page', array('course' => $course2->id), $completionmanual); 750 $c2data = $this->getDataGenerator()->create_module('data', array('course' => $course2->id), $completionnone); 751 752 $c = new completion_info($course); 753 $activities = $c->get_activities(); 754 $this->assertCount(3, $activities); 755 $this->assertTrue(isset($activities[$forum->cmid])); 756 $this->assertSame($forum->name, $activities[$forum->cmid]->name); 757 $this->assertTrue(isset($activities[$page->cmid])); 758 $this->assertSame($page->name, $activities[$page->cmid]->name); 759 $this->assertTrue(isset($activities[$data->cmid])); 760 $this->assertSame($data->name, $activities[$data->cmid]->name); 761 762 $this->assertFalse(isset($activities[$forum2->cmid])); 763 $this->assertFalse(isset($activities[$page2->cmid])); 764 $this->assertFalse(isset($activities[$data2->cmid])); 765 } 766 767 public function test_has_activities() { 768 global $CFG; 769 $this->resetAfterTest(); 770 771 // Enable completion before creating modules, otherwise the completion data is not written in DB. 772 $CFG->enablecompletion = true; 773 774 // Create a course with mixed auto completion data. 775 $course = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); 776 $course2 = $this->getDataGenerator()->create_course(array('enablecompletion' => true)); 777 $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); 778 $completionnone = array('completion' => COMPLETION_TRACKING_NONE); 779 $c1forum = $this->getDataGenerator()->create_module('forum', array('course' => $course->id), $completionauto); 780 $c2forum = $this->getDataGenerator()->create_module('forum', array('course' => $course2->id), $completionnone); 781 782 $c1 = new completion_info($course); 783 $c2 = new completion_info($course2); 784 785 $this->assertTrue($c1->has_activities()); 786 $this->assertFalse($c2->has_activities()); 787 } 788 789 /** 790 * Test course module completion update event. 791 */ 792 public function test_course_module_completion_updated_event() { 793 global $USER, $CFG; 794 795 $this->setup_data(); 796 797 $this->setAdminUser(); 798 799 $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); 800 $forum = $this->getDataGenerator()->create_module('forum', array('course' => $this->course->id), $completionauto); 801 802 $c = new completion_info($this->course); 803 $activities = $c->get_activities(); 804 $this->assertEquals(1, count($activities)); 805 $this->assertTrue(isset($activities[$forum->cmid])); 806 $this->assertEquals($activities[$forum->cmid]->name, $forum->name); 807 808 $current = $c->get_data($activities[$forum->cmid], false, $this->user->id); 809 $current->completionstate = COMPLETION_COMPLETE; 810 $current->timemodified = time(); 811 $sink = $this->redirectEvents(); 812 $c->internal_set_data($activities[$forum->cmid], $current); 813 $events = $sink->get_events(); 814 $event = reset($events); 815 $this->assertInstanceOf('\core\event\course_module_completion_updated', $event); 816 $this->assertEquals($forum->cmid, $event->get_record_snapshot('course_modules_completion', $event->objectid)->coursemoduleid); 817 $this->assertEquals($current, $event->get_record_snapshot('course_modules_completion', $event->objectid)); 818 $this->assertEquals(context_module::instance($forum->cmid), $event->get_context()); 819 $this->assertEquals($USER->id, $event->userid); 820 $this->assertEquals($this->user->id, $event->relateduserid); 821 $this->assertInstanceOf('moodle_url', $event->get_url()); 822 $this->assertEventLegacyData($current, $event); 823 } 824 825 /** 826 * Test course completed event. 827 */ 828 public function test_course_completed_event() { 829 global $USER; 830 831 $this->setup_data(); 832 $this->setAdminUser(); 833 834 $completionauto = array('completion' => COMPLETION_TRACKING_AUTOMATIC); 835 $ccompletion = new completion_completion(array('course' => $this->course->id, 'userid' => $this->user->id)); 836 837 // Mark course as complete and get triggered event. 838 $sink = $this->redirectEvents(); 839 $ccompletion->mark_complete(); 840 $events = $sink->get_events(); 841 $event = reset($events); 842 843 $this->assertInstanceOf('\core\event\course_completed', $event); 844 $this->assertEquals($this->course->id, $event->get_record_snapshot('course_completions', $event->objectid)->course); 845 $this->assertEquals($this->course->id, $event->courseid); 846 $this->assertEquals($USER->id, $event->userid); 847 $this->assertEquals($this->user->id, $event->relateduserid); 848 $this->assertEquals(context_course::instance($this->course->id), $event->get_context()); 849 $this->assertInstanceOf('moodle_url', $event->get_url()); 850 $data = $ccompletion->get_record_data(); 851 $this->assertEventLegacyData($data, $event); 852 } 853 854 /** 855 * Test course completed event. 856 */ 857 public function test_course_completion_updated_event() { 858 $this->setup_data(); 859 $coursecontext = context_course::instance($this->course->id); 860 $coursecompletionevent = \core\event\course_completion_updated::create( 861 array( 862 'courseid' => $this->course->id, 863 'context' => $coursecontext 864 ) 865 ); 866 867 // Mark course as complete and get triggered event. 868 $sink = $this->redirectEvents(); 869 $coursecompletionevent->trigger(); 870 $events = $sink->get_events(); 871 $event = array_pop($events); 872 $sink->close(); 873 874 $this->assertInstanceOf('\core\event\course_completion_updated', $event); 875 $this->assertEquals($this->course->id, $event->courseid); 876 $this->assertEquals($coursecontext, $event->get_context()); 877 $this->assertInstanceOf('moodle_url', $event->get_url()); 878 $expectedlegacylog = array($this->course->id, 'course', 'completion updated', 'completion.php?id='.$this->course->id); 879 $this->assertEventLegacyLogData($expectedlegacylog, $event); 880 } 881 } 882 883 class core_completionlib_fake_recordset implements Iterator { 884 protected $closed; 885 protected $values, $index; 886 887 public function __construct($values) { 888 $this->values = $values; 889 $this->index = 0; 890 } 891 892 public function current() { 893 return $this->values[$this->index]; 894 } 895 896 public function key() { 897 return $this->values[$this->index]; 898 } 899 900 public function next() { 901 $this->index++; 902 } 903 904 public function rewind() { 905 $this->index = 0; 906 } 907 908 public function valid() { 909 return count($this->values) > $this->index; 910 } 911 912 public function close() { 913 $this->closed = true; 914 } 915 916 public function was_closed() { 917 return $this->closed; 918 } 919 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |