[ Index ]

PHP Cross Reference of moodle-2.8

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->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  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1