[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/tests/ -> completionlib_test.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1