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