Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401]

   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  namespace core_completion;
  18  
  19  /**
  20   * Test completion criteria.
  21   *
  22   * @package   core_completion
  23   * @category  test
  24   * @copyright 2021 Mikhail Golenkov <mikhailgolenkov@catalyst-au.net>
  25   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  26   */
  27  class completion_criteria_test extends \advanced_testcase {
  28  
  29      /**
  30       * Test setup.
  31       */
  32      public function setUp(): void {
  33          global $CFG;
  34          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_course.php');
  35          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_activity.php');
  36          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_duration.php');
  37          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_grade.php');
  38          require_once($CFG->dirroot.'/completion/criteria/completion_criteria_date.php');
  39  
  40          $this->setAdminUser();
  41          $this->resetAfterTest();
  42      }
  43  
  44      /**
  45       * Test that activity completion dates are used when activity criteria is marked as completed.
  46       */
  47      public function test_completion_criteria_activity(): void {
  48          global $DB;
  49          $timestarted = time();
  50  
  51          // Create a course, an activity and enrol a user.
  52          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
  53          $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id], ['completion' => 1]);
  54          $user = $this->getDataGenerator()->create_user();
  55          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
  56          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
  57  
  58          // Set completion criteria and mark the user to complete the criteria.
  59          $criteriadata = (object) [
  60              'id' => $course->id,
  61              'criteria_activity' => [$assign->cmid => 1],
  62          ];
  63          $criterion = new \completion_criteria_activity();
  64          $criterion->update_config($criteriadata);
  65          $cmassign = get_coursemodule_from_id('assign', $assign->cmid);
  66          $completion = new \completion_info($course);
  67          $completion->update_state($cmassign, COMPLETION_COMPLETE, $user->id);
  68  
  69          // Completion criteria for the user is supposed to be marked as completed at now().
  70          $result = \core_completion_external::get_activities_completion_status($course->id, $user->id);
  71          $actual = reset($result['statuses']);
  72          $this->assertEquals(1, $actual['state']);
  73          $this->assertGreaterThanOrEqual($timestarted, $actual['timecompleted']);
  74  
  75          // And the whole course is marked as completed at now().
  76          $ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
  77          $this->assertGreaterThanOrEqual($timestarted, $ccompletion->timecompleted);
  78          $this->assertTrue($ccompletion->is_complete());
  79      }
  80  
  81      /**
  82       * Test that enrolment timestart are used when duration criteria is marked as completed.
  83       */
  84      public function test_completion_criteria_duration_timestart(): void {
  85          global $DB;
  86          $timestarted = 1610000000;
  87          $durationperiod = DAYSECS;
  88  
  89          // Create a course and users.
  90          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
  91          $user = $this->getDataGenerator()->create_and_enrol($course, 'student', null, 'manual', $timestarted);
  92  
  93          // Set completion criteria.
  94          $criteriadata = (object) [
  95              'id' => $course->id,
  96              'criteria_duration' => 1,
  97              'criteria_duration_days' => $durationperiod,
  98          ];
  99          $criterion = new \completion_criteria_duration();
 100          $criterion->update_config($criteriadata);
 101  
 102          // Run completion scheduled task.
 103          $task = new \core\task\completion_regular_task();
 104          $this->expectOutputRegex("/Marking complete/");
 105          $task->execute();
 106          // Hopefully, some day MDL-33320 will be fixed and all these sleeps
 107          // and double cron calls in behat and unit tests will be removed.
 108          sleep(1);
 109          $task->execute();
 110  
 111          // The course for User is supposed to be marked as completed at $timestarted + $durationperiod.
 112          $ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
 113          $this->assertEquals($timestarted + $durationperiod, $ccompletion->timecompleted);
 114          $this->assertTrue($ccompletion->is_complete());
 115      }
 116  
 117      /**
 118       * Test that enrolment timecreated are used when duration criteria is marked as completed.
 119       */
 120      public function test_completion_criteria_duration_timecreated(): void {
 121          global $DB;
 122  
 123          $timecreated = 1620000000;
 124          $durationperiod = DAYSECS;
 125  
 126          // Create a course and users.
 127          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 128  
 129          // Create and enrol user with an empty time start, but update the record like it was created at $timecreated.
 130          $user = $this->getDataGenerator()->create_and_enrol($course);
 131          $DB->set_field('user_enrolments', 'timecreated', $timecreated, ['userid' => $user->id]);
 132  
 133          // Set completion criteria.
 134          $criteriadata = (object) [
 135              'id' => $course->id,
 136              'criteria_duration' => 1,
 137              'criteria_duration_days' => $durationperiod,
 138          ];
 139          $criterion = new \completion_criteria_duration();
 140          $criterion->update_config($criteriadata);
 141  
 142          // Run completion scheduled task.
 143          $task = new \core\task\completion_regular_task();
 144          $this->expectOutputRegex("/Marking complete/");
 145          $task->execute();
 146  
 147          // Hopefully, some day MDL-33320 will be fixed and all these sleeps
 148          // and double cron calls in behat and unit tests will be removed.
 149          sleep(1);
 150          $task->execute();
 151  
 152          // The course for user is supposed to be marked as completed at $timecreated + $durationperiod.
 153          $ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
 154          $this->assertEquals($timecreated + $durationperiod, $ccompletion->timecompleted);
 155          $this->assertTrue($ccompletion->is_complete());
 156      }
 157  
 158      /**
 159       * Test that criteria date is used as a course completion date.
 160       */
 161      public function test_completion_criteria_date(): void {
 162          global $DB;
 163          $timeend = 1610000000;
 164  
 165          // Create a course and enrol a user.
 166          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 167          $user = $this->getDataGenerator()->create_user();
 168          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 169          $this->getDataGenerator()->enrol_user($user->id, $course->id, $studentrole->id);
 170  
 171          // Set completion criteria.
 172          $criteriadata = (object) [
 173              'id' => $course->id,
 174              'criteria_date' => 1,
 175              'criteria_date_value' => $timeend,
 176          ];
 177          $criterion = new \completion_criteria_date();
 178          $criterion->update_config($criteriadata);
 179  
 180          // Run completion scheduled task.
 181          $task = new \core\task\completion_regular_task();
 182          $this->expectOutputRegex("/Marking complete/");
 183          $task->execute();
 184          // Hopefully, some day MDL-33320 will be fixed and all these sleeps
 185          // and double cron calls in behat and unit tests will be removed.
 186          sleep(1);
 187          $task->execute();
 188  
 189          // The course is supposed to be marked as completed at $timeend.
 190          $ccompletion = new \completion_completion(['userid' => $user->id, 'course' => $course->id]);
 191          $this->assertEquals($timeend, $ccompletion->timecompleted);
 192          $this->assertTrue($ccompletion->is_complete());
 193      }
 194  
 195      /**
 196       * Test that grade timemodified is used when grade criteria is marked as completed.
 197       */
 198      public function test_completion_criteria_grade(): void {
 199          global $DB;
 200          $timegraded = 1610000000;
 201  
 202          // Create a course and enrol a couple of users.
 203          $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]);
 204          $user1 = $this->getDataGenerator()->create_user();
 205          $user2 = $this->getDataGenerator()->create_user();
 206          $studentrole = $DB->get_record('role', ['shortname' => 'student']);
 207          $this->getDataGenerator()->enrol_user($user1->id, $course->id, $studentrole->id);
 208          $this->getDataGenerator()->enrol_user($user2->id, $course->id, $studentrole->id);
 209  
 210          // Set completion criteria.
 211          $criteriadata = (object) [
 212              'id' => $course->id,
 213              'criteria_grade' => 1,
 214              'criteria_grade_value' => 66,
 215          ];
 216          $criterion = new \completion_criteria_grade();
 217          $criterion->update_config($criteriadata);
 218  
 219          $coursegradeitem = \grade_item::fetch_course_item($course->id);
 220  
 221          // Grade User 1 with a passing grade.
 222          $grade1 = new \grade_grade();
 223          $grade1->itemid = $coursegradeitem->id;
 224          $grade1->timemodified = $timegraded;
 225          $grade1->userid = $user1->id;
 226          $grade1->finalgrade = 80;
 227          $grade1->insert();
 228  
 229          // Grade User 2 with a non-passing grade.
 230          $grade2 = new \grade_grade();
 231          $grade2->itemid = $coursegradeitem->id;
 232          $grade2->timemodified = $timegraded;
 233          $grade2->userid = $user2->id;
 234          $grade2->finalgrade = 40;
 235          $grade2->insert();
 236  
 237          // Run completion scheduled task.
 238          $task = new \core\task\completion_regular_task();
 239          $this->expectOutputRegex("/Marking complete/");
 240          $task->execute();
 241          // Hopefully, some day MDL-33320 will be fixed and all these sleeps
 242          // and double cron calls in behat and unit tests will be removed.
 243          sleep(1);
 244          $task->execute();
 245  
 246          // The course for User 1 is supposed to be marked as completed when the user was graded.
 247          $ccompletion = new \completion_completion(['userid' => $user1->id, 'course' => $course->id]);
 248          $this->assertEquals($timegraded, $ccompletion->timecompleted);
 249          $this->assertTrue($ccompletion->is_complete());
 250  
 251          // The course for User 2 is supposed to be marked as not completed.
 252          $ccompletion = new \completion_completion(['userid' => $user2->id, 'course' => $course->id]);
 253          $this->assertFalse($ccompletion->is_complete());
 254      }
 255  }