Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403]

   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   * External completion functions unit tests
  19   *
  20   * @package    core_completion
  21   * @category   external
  22   * @copyright  2015 Juan Leyva <juan@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   * @since      Moodle 2.9
  25   */
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  global $CFG;
  30  
  31  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  32  
  33  /**
  34   * External completion functions unit tests
  35   *
  36   * @package    core_completion
  37   * @category   external
  38   * @copyright  2015 Juan Leyva <juan@moodle.com>
  39   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  40   * @since      Moodle 2.9
  41   */
  42  class core_completion_externallib_testcase extends externallib_advanced_testcase {
  43  
  44      /**
  45       * Test update_activity_completion_status_manually
  46       */
  47      public function test_update_activity_completion_status_manually() {
  48          global $DB, $CFG;
  49  
  50          $this->resetAfterTest(true);
  51  
  52          $CFG->enablecompletion = true;
  53          $user = $this->getDataGenerator()->create_user();
  54          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
  55          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
  56                                                               array('completion' => 1));
  57          $cm = get_coursemodule_from_id('data', $data->cmid);
  58  
  59          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
  60          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
  61  
  62          $this->setUser($user);
  63  
  64          $result = core_completion_external::update_activity_completion_status_manually($data->cmid, true);
  65          // We need to execute the return values cleaning process to simulate the web service server.
  66          $result = external_api::clean_returnvalue(
  67              core_completion_external::update_activity_completion_status_manually_returns(), $result);
  68  
  69          // Check in DB.
  70          $this->assertEquals(1, $DB->get_field('course_modules_completion', 'completionstate',
  71                              array('coursemoduleid' => $data->cmid)));
  72  
  73          // Check using the API.
  74          $completion = new completion_info($course);
  75          $completiondata = $completion->get_data($cm);
  76          $this->assertEquals(1, $completiondata->completionstate);
  77          $this->assertTrue($result['status']);
  78  
  79          $result = core_completion_external::update_activity_completion_status_manually($data->cmid, false);
  80          // We need to execute the return values cleaning process to simulate the web service server.
  81          $result = external_api::clean_returnvalue(
  82              core_completion_external::update_activity_completion_status_manually_returns(), $result);
  83  
  84          $this->assertEquals(0, $DB->get_field('course_modules_completion', 'completionstate',
  85                              array('coursemoduleid' => $data->cmid)));
  86          $completiondata = $completion->get_data($cm);
  87          $this->assertEquals(0, $completiondata->completionstate);
  88          $this->assertTrue($result['status']);
  89      }
  90  
  91      /**
  92       * Test update_activity_completion_status
  93       */
  94      public function test_get_activities_completion_status() {
  95          global $DB, $CFG;
  96  
  97          $this->resetAfterTest(true);
  98  
  99          $CFG->enablecompletion = true;
 100          $student = $this->getDataGenerator()->create_user();
 101          $teacher = $this->getDataGenerator()->create_user();
 102  
 103          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1,
 104                                                                      'groupmode' => SEPARATEGROUPS,
 105                                                                      'groupmodeforce' => 1));
 106          availability_completion\condition::wipe_static_cache();
 107  
 108          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
 109                                                               array('completion' => 1));
 110          $forum = $this->getDataGenerator()->create_module('forum',  array('course' => $course->id),
 111                                                               array('completion' => 1));
 112          $availability = '{"op":"&","c":[{"type":"completion","cm":' . $forum->cmid .',"e":1}],"showc":[true]}';
 113          $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['availability' => $availability]);
 114          $page = $this->getDataGenerator()->create_module('page',  array('course' => $course->id),
 115                                                              array('completion' => 1, 'visible' => 0));
 116  
 117          $cmdata = get_coursemodule_from_id('data', $data->cmid);
 118          $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 119  
 120          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 121          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
 122          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 123          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 124  
 125          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 126          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 127  
 128          // Teacher and student in different groups initially.
 129          groups_add_member($group1->id, $student->id);
 130          groups_add_member($group2->id, $teacher->id);
 131  
 132          $this->setUser($student);
 133          // Forum complete.
 134          $completion = new completion_info($course);
 135          $completion->update_state($cmforum, COMPLETION_COMPLETE);
 136  
 137          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 138          // We need to execute the return values cleaning process to simulate the web service server.
 139          $result = external_api::clean_returnvalue(
 140              core_completion_external::get_activities_completion_status_returns(), $result);
 141  
 142          // We added 4 activities, but only 3 with completion enabled and one of those is hidden.
 143          $this->assertCount(2, $result['statuses']);
 144  
 145          $activitiesfound = 0;
 146          foreach ($result['statuses'] as $status) {
 147              if ($status['cmid'] == $forum->cmid and $status['modname'] == 'forum' and $status['instance'] == $forum->id) {
 148                  $activitiesfound++;
 149                  $this->assertEquals(COMPLETION_COMPLETE, $status['state']);
 150                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 151                  $this->assertTrue($status['valueused']);
 152              } else if ($status['cmid'] == $data->cmid and $status['modname'] == 'data' and $status['instance'] == $data->id) {
 153                  $activitiesfound++;
 154                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 155                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 156                  $this->assertFalse($status['valueused']);
 157              }
 158          }
 159          $this->assertEquals(2, $activitiesfound);
 160  
 161          // Teacher should see students status, they are in different groups but the teacher can access all groups.
 162          $this->setUser($teacher);
 163          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 164          // We need to execute the return values cleaning process to simulate the web service server.
 165          $result = external_api::clean_returnvalue(
 166              core_completion_external::get_activities_completion_status_returns(), $result);
 167  
 168          // We added 4 activities, but only 3 with completion enabled and one of those is hidden.
 169          $this->assertCount(3, $result['statuses']);
 170  
 171          // Override status by teacher.
 172          $completion->update_state($cmforum, COMPLETION_INCOMPLETE, $student->id, true);
 173  
 174          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 175          // We need to execute the return values cleaning process to simulate the web service server.
 176          $result = external_api::clean_returnvalue(
 177              core_completion_external::get_activities_completion_status_returns(), $result);
 178  
 179          // Check forum has been overriden by the teacher.
 180          foreach ($result['statuses'] as $status) {
 181              if ($status['cmid'] == $forum->cmid) {
 182                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 183                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 184                  $this->assertEquals($teacher->id, $status['overrideby']);
 185                  break;
 186              }
 187          }
 188  
 189          // Teacher should see his own completion status.
 190  
 191          // Forum complete for teacher.
 192          $completion = new completion_info($course);
 193          $completion->update_state($cmforum, COMPLETION_COMPLETE);
 194  
 195          $result = core_completion_external::get_activities_completion_status($course->id, $teacher->id);
 196          // We need to execute the return values cleaning process to simulate the web service server.
 197          $result = external_api::clean_returnvalue(
 198              core_completion_external::get_activities_completion_status_returns(), $result);
 199  
 200          // We added 4 activities, but only 3 with completion enabled (one of those is hidden but the teacher can see it).
 201          $this->assertCount(3, $result['statuses']);
 202  
 203          $activitiesfound = 0;
 204          foreach ($result['statuses'] as $status) {
 205              if ($status['cmid'] == $forum->cmid and $status['modname'] == 'forum' and $status['instance'] == $forum->id) {
 206                  $activitiesfound++;
 207                  $this->assertEquals(COMPLETION_COMPLETE, $status['state']);
 208                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 209              } else {
 210                  $activitiesfound++;
 211                  $this->assertEquals(COMPLETION_INCOMPLETE, $status['state']);
 212                  $this->assertEquals(COMPLETION_TRACKING_MANUAL, $status['tracking']);
 213              }
 214          }
 215          $this->assertEquals(3, $activitiesfound);
 216  
 217          // Change teacher role capabilities (disable access all groups).
 218          $context = context_course::instance($course->id);
 219          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
 220          accesslib_clear_all_caches_for_unit_testing();
 221  
 222          try {
 223              $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 224              $this->fail('Exception expected due to groups permissions.');
 225          } catch (moodle_exception $e) {
 226              $this->assertEquals('accessdenied', $e->errorcode);
 227          }
 228  
 229          // Now add the teacher in the same group.
 230          groups_add_member($group1->id, $teacher->id);
 231          $result = core_completion_external::get_activities_completion_status($course->id, $student->id);
 232          // We need to execute the return values cleaning process to simulate the web service server.
 233          $result = external_api::clean_returnvalue(
 234              core_completion_external::get_activities_completion_status_returns(), $result);
 235          // We added 4 activities, but only 3 with completion enabled and one of those is hidden.
 236          $this->assertCount(3, $result['statuses']);
 237      }
 238  
 239      /**
 240       * Test override_activity_completion_status
 241       */
 242      public function test_override_activity_completion_status() {
 243          global $DB, $CFG;
 244          $this->resetAfterTest(true);
 245  
 246          // Create course with teacher and student enrolled.
 247          $CFG->enablecompletion = true;
 248          $course  = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 249          $student = $this->getDataGenerator()->create_user();
 250          $teacher = $this->getDataGenerator()->create_user();
 251          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 252          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 253          $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
 254          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 255  
 256          // Create 2 activities, one with manual completion (data), one with automatic completion triggered by viewing it (forum).
 257          $data    = $this->getDataGenerator()->create_module('data', ['course' => $course->id], ['completion' => 1]);
 258          $forum   = $this->getDataGenerator()->create_module('forum',  ['course' => $course->id],
 259                                                              ['completion' => 2, 'completionview' => 1]);
 260          $cmdata = get_coursemodule_from_id('data', $data->cmid);
 261          $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 262  
 263          // Manually complete the data activity as the student.
 264          $this->setUser($student);
 265          $completion = new completion_info($course);
 266          $completion->update_state($cmdata, COMPLETION_COMPLETE);
 267  
 268          // Test overriding the status of the manual-completion-activity 'incomplete'.
 269          $this->setUser($teacher);
 270          $result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_INCOMPLETE);
 271          $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 272          $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
 273          $completiondata = $completion->get_data($cmdata, false, $student->id);
 274          $this->assertEquals(COMPLETION_INCOMPLETE, $completiondata->completionstate);
 275  
 276          // Test overriding the status of the manual-completion-activity back to 'complete'.
 277          $result = core_completion_external::override_activity_completion_status($student->id, $data->cmid, COMPLETION_COMPLETE);
 278          $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 279          $this->assertEquals($result['state'], COMPLETION_COMPLETE);
 280          $completiondata = $completion->get_data($cmdata, false, $student->id);
 281          $this->assertEquals(COMPLETION_COMPLETE, $completiondata->completionstate);
 282  
 283          // Test overriding the status of the auto-completion-activity to 'complete'.
 284          $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_COMPLETE);
 285          $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 286          $this->assertEquals($result['state'], COMPLETION_COMPLETE);
 287          $completionforum = $completion->get_data($cmforum, false, $student->id);
 288          $this->assertEquals(COMPLETION_COMPLETE, $completionforum->completionstate);
 289  
 290          // Test overriding the status of the auto-completion-activity to 'incomplete'.
 291          $result = core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_INCOMPLETE);
 292          $result = external_api::clean_returnvalue(core_completion_external::override_activity_completion_status_returns(), $result);
 293          $this->assertEquals($result['state'], COMPLETION_INCOMPLETE);
 294          $completionforum = $completion->get_data($cmforum, false, $student->id);
 295          $this->assertEquals(COMPLETION_INCOMPLETE, $completionforum->completionstate);
 296  
 297          // Test overriding the status of the auto-completion-activity to an invalid state.
 298          $this->expectException('moodle_exception');
 299          core_completion_external::override_activity_completion_status($student->id, $forum->cmid, 3);
 300      }
 301  
 302      /**
 303       * Test overriding the activity completion status as a user without the capability to do so.
 304       */
 305      public function test_override_status_user_without_capability() {
 306          global $DB, $CFG;
 307          $this->resetAfterTest(true);
 308  
 309          // Create course with teacher and student enrolled.
 310          $CFG->enablecompletion = true;
 311          $course  = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 312          $student = $this->getDataGenerator()->create_user();
 313          $teacher = $this->getDataGenerator()->create_user();
 314          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 315          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 316          $teacherrole = $DB->get_record('role', ['shortname' => 'teacher']);
 317          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 318          $coursecontext = context_course::instance($course->id);
 319  
 320          // Create an activity with automatic completion (a forum).
 321          $forum   = $this->getDataGenerator()->create_module('forum',  ['course' => $course->id],
 322              ['completion' => 2, 'completionview' => 1]);
 323  
 324          // Test overriding the status of the activity for a user without the capability.
 325          $this->setUser($teacher);
 326          assign_capability('moodle/course:overridecompletion', CAP_PREVENT, $teacherrole->id, $coursecontext);
 327          $this->expectException('required_capability_exception');
 328          core_completion_external::override_activity_completion_status($student->id, $forum->cmid, COMPLETION_COMPLETE);
 329      }
 330  
 331      /**
 332       * Test get_course_completion_status
 333       */
 334      public function test_get_course_completion_status() {
 335          global $DB, $CFG, $COMPLETION_CRITERIA_TYPES;
 336          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
 337          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
 338          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_unenrol.php');
 339          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
 340          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_duration.php');
 341          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_grade.php');
 342          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_role.php');
 343          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_course.php');
 344  
 345          $this->resetAfterTest(true);
 346  
 347          $CFG->enablecompletion = true;
 348          $student = $this->getDataGenerator()->create_user();
 349          $teacher = $this->getDataGenerator()->create_user();
 350  
 351          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1,
 352                                                                      'groupmode' => SEPARATEGROUPS,
 353                                                                      'groupmodeforce' => 1));
 354  
 355          $data = $this->getDataGenerator()->create_module('data', array('course' => $course->id),
 356                                                               array('completion' => 1));
 357          $forum = $this->getDataGenerator()->create_module('forum',  array('course' => $course->id),
 358                                                               array('completion' => 1));
 359          $assign = $this->getDataGenerator()->create_module('assign',  array('course' => $course->id));
 360  
 361          $cmdata = get_coursemodule_from_id('data', $data->cmid);
 362          $cmforum = get_coursemodule_from_id('forum', $forum->cmid);
 363  
 364          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 365          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
 366          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 367          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id);
 368  
 369          $group1 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 370          $group2 = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 371          // Teacher and student in different groups initially.
 372          groups_add_member($group1->id, $student->id);
 373          groups_add_member($group2->id, $teacher->id);
 374  
 375          // Set completion rules.
 376          $completion = new completion_info($course);
 377  
 378          // Loop through each criteria type and run its update_config() method.
 379  
 380          $criteriadata = new stdClass();
 381          $criteriadata->id = $course->id;
 382          $criteriadata->criteria_activity = array();
 383          // Some activities.
 384          $criteriadata->criteria_activity[$cmdata->id] = 1;
 385          $criteriadata->criteria_activity[$cmforum->id] = 1;
 386  
 387          // In a week criteria date value.
 388          $criteriadata->criteria_date_value = time() + WEEKSECS;
 389  
 390          // Self completion.
 391          $criteriadata->criteria_self = 1;
 392  
 393          foreach ($COMPLETION_CRITERIA_TYPES as $type) {
 394              $class = 'completion_criteria_'.$type;
 395              $criterion = new $class();
 396              $criterion->update_config($criteriadata);
 397          }
 398  
 399          // Handle overall aggregation.
 400          $aggdata = array(
 401              'course'        => $course->id,
 402              'criteriatype'  => null
 403          );
 404          $aggregation = new completion_aggregation($aggdata);
 405          $aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
 406          $aggregation->save();
 407  
 408          $aggdata['criteriatype'] = COMPLETION_CRITERIA_TYPE_ACTIVITY;
 409          $aggregation = new completion_aggregation($aggdata);
 410          $aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
 411          $aggregation->save();
 412  
 413          $this->setUser($student);
 414  
 415          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 416          // We need to execute the return values cleaning process to simulate the web service server.
 417          $studentresult = external_api::clean_returnvalue(
 418              core_completion_external::get_course_completion_status_returns(), $result);
 419  
 420          // 3 different criteria.
 421          $this->assertCount(3, $studentresult['completionstatus']['completions']);
 422  
 423          $this->assertEquals(COMPLETION_AGGREGATION_ALL, $studentresult['completionstatus']['aggregation']);
 424          $this->assertFalse($studentresult['completionstatus']['completed']);
 425  
 426          $this->assertEquals('No', $studentresult['completionstatus']['completions'][0]['status']);
 427          $this->assertEquals('No', $studentresult['completionstatus']['completions'][1]['status']);
 428          $this->assertEquals('No', $studentresult['completionstatus']['completions'][2]['status']);
 429  
 430          // Teacher should see students status, they are in different groups but the teacher can access all groups.
 431          $this->setUser($teacher);
 432          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 433          // We need to execute the return values cleaning process to simulate the web service server.
 434          $teacherresult = external_api::clean_returnvalue(
 435              core_completion_external::get_course_completion_status_returns(), $result);
 436  
 437          $this->assertEquals($studentresult, $teacherresult);
 438  
 439          // Change teacher role capabilities (disable access al goups).
 440          $context = context_course::instance($course->id);
 441          assign_capability('moodle/site:accessallgroups', CAP_PROHIBIT, $teacherrole->id, $context);
 442          accesslib_clear_all_caches_for_unit_testing();
 443  
 444          try {
 445              $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 446              $this->fail('Exception expected due to groups permissions.');
 447          } catch (moodle_exception $e) {
 448              $this->assertEquals('accessdenied', $e->errorcode);
 449          }
 450  
 451          // Now add the teacher in the same group.
 452          groups_add_member($group1->id, $teacher->id);
 453          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 454          // We need to execute the return values cleaning process to simulate the web service server.
 455          $teacherresult = external_api::clean_returnvalue(
 456              core_completion_external::get_course_completion_status_returns(), $result);
 457  
 458          $this->assertEquals($studentresult, $teacherresult);
 459  
 460      }
 461  
 462      /**
 463       * Test mark_course_self_completed
 464       */
 465      public function test_mark_course_self_completed() {
 466          global $DB, $CFG;
 467          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_self.php');
 468  
 469          $this->resetAfterTest(true);
 470  
 471          $CFG->enablecompletion = true;
 472          $student = $this->getDataGenerator()->create_user();
 473          $teacher = $this->getDataGenerator()->create_user();
 474  
 475          $course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
 476  
 477          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 478          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id);
 479  
 480          // Set completion rules.
 481          $completion = new completion_info($course);
 482  
 483          $criteriadata = new stdClass();
 484          $criteriadata->id = $course->id;
 485          $criteriadata->criteria_activity = array();
 486  
 487          // Self completion.
 488          $criteriadata->criteria_self = COMPLETION_CRITERIA_TYPE_SELF;
 489          $class = 'completion_criteria_self';
 490          $criterion = new $class();
 491          $criterion->update_config($criteriadata);
 492  
 493          // Handle overall aggregation.
 494          $aggdata = array(
 495              'course'        => $course->id,
 496              'criteriatype'  => null
 497          );
 498          $aggregation = new completion_aggregation($aggdata);
 499          $aggregation->setMethod(COMPLETION_AGGREGATION_ALL);
 500          $aggregation->save();
 501  
 502          $this->setUser($student);
 503  
 504          $result = core_completion_external::mark_course_self_completed($course->id);
 505          // We need to execute the return values cleaning process to simulate the web service server.
 506          $result = external_api::clean_returnvalue(
 507              core_completion_external::mark_course_self_completed_returns(), $result);
 508  
 509          // We expect a valid result.
 510          $this->assertEquals(true, $result['status']);
 511  
 512          $result = core_completion_external::get_course_completion_status($course->id, $student->id);
 513          // We need to execute the return values cleaning process to simulate the web service server.
 514          $result = external_api::clean_returnvalue(
 515              core_completion_external::get_course_completion_status_returns(), $result);
 516  
 517          // Course must be completed.
 518          $this->assertEquals(COMPLETION_COMPLETE, $result['completionstatus']['completions'][0]['complete']);
 519  
 520          try {
 521              $result = core_completion_external::mark_course_self_completed($course->id);
 522              $this->fail('Exception expected due course already self completed.');
 523          } catch (moodle_exception $e) {
 524              $this->assertEquals('useralreadymarkedcomplete', $e->errorcode);
 525          }
 526  
 527      }
 528  
 529  }