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 39 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_course;
  18  
  19  defined('MOODLE_INTERNAL') || die();
  20  global $CFG;
  21  
  22  require_once($CFG->dirroot . '/completion/criteria/completion_criteria.php');
  23  require_once($CFG->dirroot . '/completion/criteria/completion_criteria_activity.php');
  24  require_once($CFG->dirroot . '/lib/grade/grade_item.php');
  25  require_once($CFG->dirroot . '/lib/grade/grade_grade.php');
  26  require_once($CFG->dirroot . '/lib/grade/grade_category.php');
  27  require_once($CFG->dirroot . '/lib/grade/constants.php');
  28  
  29  /**
  30   * Unit tests for core targets.
  31   *
  32   * @package   core_course
  33   * @copyright 2019 Victor Deniz <victor@moodle.com>
  34   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  35   */
  36  class targets_test extends \advanced_testcase {
  37  
  38      /**
  39       * Provides course params for the {@link self::test_core_target_course_completion_analysable()} method.
  40       *
  41       * @return array
  42       */
  43      public function analysable_provider() {
  44  
  45          $now = new \DateTime("now", \core_date::get_server_timezone_object());
  46          $year = $now->format('Y');
  47          $month = $now->format('m');
  48  
  49          return [
  50              'coursenotyetstarted' => [
  51                  'params' => [
  52                      'enablecompletion' => 1,
  53                      'startdate' => mktime(0, 0, 0, 10, 24, $year + 1)
  54                  ],
  55                  'isvalid' => get_string('coursenotyetstarted', 'course')
  56              ],
  57              'coursenostudents' => [
  58                  'params' => [
  59                      'enablecompletion' => 1,
  60                      'startdate' => mktime(0, 0, 0, 10, 24, $year - 2),
  61                      'enddate' => mktime(0, 0, 0, 10, 24, $year - 1)
  62                  ],
  63                  'isvalid' => get_string('nocoursestudents', 'course')
  64              ],
  65              'coursenosections' => [
  66                  'params' => [
  67                      'enablecompletion' => 1,
  68                      'format' => 'social',
  69                      'students' => true
  70                  ],
  71                  'isvalid' => get_string('nocoursesections', 'course')
  72              ],
  73              'coursenoendtime' => [
  74                  'params' => [
  75                      'enablecompletion' => 1,
  76                      'format' => 'topics',
  77                      'enddate' => 0,
  78                      'students' => true
  79                  ],
  80                  'isvalid' => get_string('nocourseendtime', 'course')
  81              ],
  82              'courseendbeforestart' => [
  83                  'params' => [
  84                      'enablecompletion' => 1,
  85                      'enddate' => mktime(0, 0, 0, 10, 23, $year - 2),
  86                      'students' => true
  87                  ],
  88                  'isvalid' => get_string('errorendbeforestart', 'course')
  89              ],
  90              'coursetoolong' => [
  91                  'params' => [
  92                      'enablecompletion' => 1,
  93                      'startdate' => mktime(0, 0, 0, 10, 24, $year - 2),
  94                      'enddate' => mktime(0, 0, 0, 10, 23, $year),
  95                      'students' => true
  96                  ],
  97                  'isvalid' => get_string('coursetoolong', 'course')
  98              ],
  99              'coursealreadyfinished' => [
 100                  'params' => [
 101                      'enablecompletion' => 1,
 102                      'startdate' => mktime(0, 0, 0, 10, 24, $year - 2),
 103                      'enddate' => mktime(0, 0, 0, 10, 23, $year - 1),
 104                      'students' => true
 105                  ],
 106                  'isvalid' => get_string('coursealreadyfinished', 'course'),
 107                  'fortraining' => false
 108              ],
 109              'coursenotyetfinished' => [
 110                  'params' => [
 111                      'enablecompletion' => 1,
 112                      'startdate' => mktime(0, 0, 0, $month - 1, 24, $year),
 113                      'enddate' => mktime(0, 0, 0, $month + 2, 23, $year),
 114                      'students' => true
 115                  ],
 116                  'isvalid' => get_string('coursenotyetfinished', 'course')
 117              ],
 118              'coursenocompletion' => [
 119                  'params' => [
 120                      'enablecompletion' => 0,
 121                      'startdate' => mktime(0, 0, 0, $month - 2, 24, $year),
 122                      'enddate' => mktime(0, 0, 0, $month - 1, 23, $year),
 123                      'students' => true
 124                  ],
 125                  'isvalid' => get_string('completionnotenabledforcourse', 'completion')
 126              ],
 127              'coursehiddentraining' => [
 128                  'params' => [
 129                      'enablecompletion' => 1,
 130                      'startdate' => mktime(0, 0, 0, $month - 1, 24, $year - 1),
 131                      'enddate' => mktime(0, 0, 0, $month - 1, 23, $year),
 132                      'students' => true,
 133                      'visible' => '0',
 134                  ],
 135                  'isvalid' => true,
 136              ],
 137              'coursehiddenprediction' => [
 138                  'params' => [
 139                      'enablecompletion' => 1,
 140                      'startdate' => mktime(0, 0, 0, $month - 1, 24, $year),
 141                      'enddate' => mktime(0, 0, 0, $month - 1, 23, $year + 1),
 142                      'students' => true,
 143                      'visible' => '0',
 144                  ],
 145                  'isvalid' => get_string('hiddenfromstudents'),
 146                  'fortraining' => false
 147              ],
 148          ];
 149      }
 150  
 151      /**
 152       * Provides enrolment params for the {@link self::test_core_target_course_completion_samples()} method.
 153       *
 154       * @return array
 155       */
 156      public function sample_provider() {
 157          $now = time();
 158          return [
 159              'enrolmentendbeforecourse' => [
 160                  'coursestart' => $now,
 161                  'courseend' => $now + (WEEKSECS * 8),
 162                  'timestart' => $now,
 163                  'timeend' => $now - DAYSECS,
 164                  'isvalidfortraining' => false,
 165                  'isvalidforprediction' => false
 166              ],
 167              'enrolmenttoolong' => [
 168                  'coursestart' => $now,
 169                  'courseend' => $now + (WEEKSECS * 8),
 170                  'timestart' => $now - (YEARSECS + (WEEKSECS * 8)),
 171                  'timeend' => $now + (WEEKSECS * 8),
 172                  'isvalidfortraining' => false,
 173                  'isvalidforprediction' => false
 174              ],
 175              'enrolmentstartaftercourse' => [
 176                  'coursestart' => $now,
 177                  'courseend' => $now + (WEEKSECS * 8),
 178                  'timestart' => $now + (WEEKSECS * 9),
 179                  'timeend' => $now + (WEEKSECS * 10),
 180                  'isvalidfortraining' => false,
 181                  'isvalidforprediction' => false
 182              ],
 183              'enrolmentstartsafternow' => [
 184                  'coursestart' => $now,
 185                  'courseend' => $now + (WEEKSECS * 8),
 186                  'timestart' => $now + (WEEKSECS * 2),
 187                  'timeend' => $now + (WEEKSECS * 7),
 188                  'isvalidfortraining' => false,
 189                  'isvalidforprediction' => false
 190              ],
 191              'enrolmentfinishedbeforenow' => [
 192                  'coursestart' => $now - (WEEKSECS * 4),
 193                  'courseend' => $now - (WEEKSECS * 1),
 194                  'timestart' => $now - (WEEKSECS * 3),
 195                  'timeend' => $now - (WEEKSECS * 2),
 196                  'isvalidfortraining' => true,
 197                  'isvalidforprediction' => false
 198              ],
 199          ];
 200      }
 201  
 202      /**
 203       * Provides enrolment params for the {@link self::test_core_target_course_completion_samples()} method.
 204       *
 205       * @return array
 206       */
 207      public function active_during_analysis_time_provider() {
 208          $now = time();
 209  
 210          return [
 211              'enrol-after-end' => [
 212                  'starttime' => $now,
 213                  'endtime' => $now + WEEKSECS,
 214                  'timestart' => $now + (WEEKSECS * 2),
 215                  'timeend' => $now + (WEEKSECS * 3),
 216                  'nullcalculation' => true,
 217              ],
 218              'enrol-before-start' => [
 219                  'starttime' => $now + (WEEKSECS * 2),
 220                  'endtime' => $now + (WEEKSECS * 3),
 221                  'timestart' => $now,
 222                  'timeend' => $now + WEEKSECS,
 223                  'nullcalculation' => true,
 224              ],
 225              'enrol-active-exact-match' => [
 226                  'starttime' => $now,
 227                  'endtime' => $now + (WEEKSECS * 1),
 228                  'timestart' => $now,
 229                  'timeend' => $now + (WEEKSECS * 1),
 230                  'nullcalculation' => false,
 231              ],
 232              'enrol-active' => [
 233                  'starttime' => $now + WEEKSECS,
 234                  'endtime' => $now + (WEEKSECS * 2),
 235                  'timestart' => $now,
 236                  'timeend' => $now + (WEEKSECS * 3),
 237                  'nullcalculation' => false,
 238              ],
 239              'enrol-during-analysis-active-just-for-a-while' => [
 240                  'starttime' => $now,
 241                  'endtime' => $now + (WEEKSECS * 10),
 242                  'timestart' => $now + WEEKSECS,
 243                  'timeend' => $now + (WEEKSECS * 2),
 244                  'nullcalculation' => true,
 245              ],
 246              'enrol-during-analysis-mostly-active' => [
 247                  'starttime' => $now,
 248                  'endtime' => $now + (WEEKSECS * 20),
 249                  'timestart' => $now + WEEKSECS,
 250                  'timeend' => $now + (WEEKSECS * 19),
 251                  'nullcalculation' => false,
 252              ],
 253              'enrol-partly-active-starts-before' => [
 254                  'starttime' => $now + WEEKSECS,
 255                  'endtime' => $now + (WEEKSECS * 10),
 256                  'timestart' => $now,
 257                  'timeend' => $now + (WEEKSECS * 2),
 258                  'nullcalculation' => true,
 259              ],
 260              'enrol-mostly-active-starts-before' => [
 261                  'starttime' => $now + WEEKSECS,
 262                  'endtime' => $now + (WEEKSECS * 10),
 263                  'timestart' => $now,
 264                  'timeend' => $now + (WEEKSECS * 9),
 265                  'nullcalculation' => false,
 266              ],
 267              'enrol-partly-active-ends-afterwards' => [
 268                  'starttime' => $now,
 269                  'endtime' => $now + (WEEKSECS * 10),
 270                  'timestart' => $now + (WEEKSECS * 9),
 271                  'timeend' => $now + (WEEKSECS * 11),
 272                  'nullcalculation' => true,
 273              ],
 274              'enrol-mostly-active-ends-afterwards' => [
 275                  'starttime' => $now,
 276                  'endtime' => $now + (WEEKSECS * 10),
 277                  'timestart' => $now + WEEKSECS,
 278                  'timeend' => $now + (WEEKSECS * 11),
 279                  'nullcalculation' => false,
 280              ],
 281              'enrol-partly-active-no-enrolment-end' => [
 282                  'starttime' => $now,
 283                  'endtime' => $now + (WEEKSECS * 10),
 284                  'timestart' => $now + (WEEKSECS * 9),
 285                  'timeend' => false,
 286                  'nullcalculation' => true,
 287              ],
 288              'enrol-mostly-active-no-enrolment-end-false' => [
 289                  'starttime' => $now,
 290                  'endtime' => $now + (WEEKSECS * 10),
 291                  'timestart' => $now + WEEKSECS,
 292                  'timeend' => false,
 293                  'nullcalculation' => false,
 294              ],
 295              'enrol-mostly-active-no-enrolment-end-zero' => [
 296                  'starttime' => $now,
 297                  'endtime' => $now + (WEEKSECS * 10),
 298                  'timestart' => $now + WEEKSECS,
 299                  'timeend' => 0,
 300                  'nullcalculation' => false,
 301              ],
 302              'enrol-no-enrolment-start-false' => [
 303                  'starttime' => $now,
 304                  'endtime' => $now + (WEEKSECS * 10),
 305                  'timestart' => false,
 306                  'timeend' => $now + (WEEKSECS * 9),
 307                  'nullcalculation' => false,
 308              ],
 309              'enrol-no-enrolment-start-zero' => [
 310                  'starttime' => $now,
 311                  'endtime' => $now + (WEEKSECS * 10),
 312                  'timestart' => 0,
 313                  'timeend' => $now + (WEEKSECS * 9),
 314                  'nullcalculation' => false,
 315              ],
 316              'no-start' => [
 317                  'starttime' => 0,
 318                  'endtime' => $now + (WEEKSECS * 2),
 319                  'timestart' => $now + WEEKSECS,
 320                  'timeend' => $now + (WEEKSECS * 3),
 321                  'nullcalculation' => false,
 322              ],
 323              'no-end' => [
 324                  'starttime' => $now,
 325                  'endtime' => 0,
 326                  'timestart' => $now + (WEEKSECS * 2),
 327                  'timeend' => $now + (WEEKSECS * 3),
 328                  'nullcalculation' => false,
 329              ]
 330          ];
 331      }
 332  
 333      /**
 334       * Test the conditions of a valid analysable, both common and specific to this target (course_completion).
 335       *
 336       * @dataProvider analysable_provider
 337       * @param mixed $courseparams Course data
 338       * @param true|string $isvalid True when analysable is valid, string when it is not
 339       * @param boolean $fortraining True if the course is for training the model
 340       */
 341      public function test_core_target_course_completion_analysable($courseparams, $isvalid, $fortraining = true) {
 342          global $DB;
 343  
 344          $this->resetAfterTest(true);
 345  
 346          try {
 347              $course = $this->getDataGenerator()->create_course($courseparams);
 348          } catch (\moodle_exception $e) {
 349              $course = $this->getDataGenerator()->create_course();
 350              $courserecord = $courseparams;
 351              $courserecord['id'] = $course->id;
 352              unset($courserecord['students']);
 353  
 354              $DB->update_record_raw('course', $courserecord);
 355              $course = get_course($course->id);
 356          }
 357          $user = $this->getDataGenerator()->create_user();
 358  
 359          if (!empty($courseparams['enablecompletion'])) {
 360              $assign = $this->getDataGenerator()->create_module('assign', ['course' => $course->id, 'completion' => 1]);
 361              $cm = get_coursemodule_from_id('assign', $assign->cmid);
 362  
 363              $criteriadata = (object) [
 364                  'id' => $course->id,
 365                  'criteria_activity' => [
 366                      $cm->id => 1
 367                  ]
 368              ];
 369              $criterion = new \completion_criteria_activity();
 370              $criterion->update_config($criteriadata);
 371          }
 372  
 373          $target = new \core_course\analytics\target\course_completion();
 374  
 375          // Test valid analysables.
 376  
 377          if (!empty($courseparams['students'])) {
 378              // Enroll user in course.
 379              $this->getDataGenerator()->enrol_user($user->id, $course->id);
 380          }
 381  
 382          $analysable = new \core_analytics\course($course);
 383          $this->assertEquals($isvalid, $target->is_valid_analysable($analysable, $fortraining));
 384      }
 385  
 386      /**
 387       * Test the conditions of a valid sample, both common and specific to this target (course_completion).
 388       *
 389       * @dataProvider sample_provider
 390       * @param int $coursestart Course start date
 391       * @param int $courseend Course end date
 392       * @param int $timestart Enrol start date
 393       * @param int $timeend Enrol end date
 394       * @param boolean $isvalidfortraining True when sample is valid for training, false when it is not
 395       * @param boolean $isvalidforprediction True when sample is valid for prediction, false when it is not
 396       */
 397      public function test_core_target_course_completion_samples($coursestart, $courseend, $timestart, $timeend,
 398              $isvalidfortraining, $isvalidforprediction) {
 399  
 400          $this->resetAfterTest(true);
 401  
 402          $courserecord = new \stdClass();
 403          $courserecord->startdate = $coursestart;
 404          $courserecord->enddate = $courseend;
 405  
 406          $user = $this->getDataGenerator()->create_user();
 407          $course = $this->getDataGenerator()->create_course($courserecord);
 408          $this->getDataGenerator()->enrol_user($user->id, $course->id, null, 'manual', $timestart, $timeend);
 409  
 410          $target = new \core_course\analytics\target\course_completion();
 411          $analyser = new \core\analytics\analyser\student_enrolments(1, $target, [], [], []);
 412          $analysable = new \core_analytics\course($course);
 413  
 414          $class = new \ReflectionClass('\core\analytics\analyser\student_enrolments');
 415          $method = $class->getMethod('get_all_samples');
 416          $method->setAccessible(true);
 417  
 418          list($sampleids, $samplesdata) = $method->invoke($analyser, $analysable);
 419          $target->add_sample_data($samplesdata);
 420          $sampleid = reset($sampleids);
 421  
 422          $this->assertEquals($isvalidfortraining, $target->is_valid_sample($sampleid, $analysable, true));
 423          $this->assertEquals($isvalidforprediction, $target->is_valid_sample($sampleid, $analysable, false));
 424      }
 425  
 426      /**
 427       * Test the conditions of a valid calculation (course_completion).
 428       *
 429       * @dataProvider active_during_analysis_time_provider
 430       * @param int $starttime Analysis start time
 431       * @param int $endtime Analysis end time
 432       * @param int $timestart Enrol start date
 433       * @param int $timeend Enrol end date
 434       * @param boolean $nullcalculation Whether the calculation should be null or not
 435       */
 436      public function test_core_target_course_completion_active_during_analysis_time($starttime, $endtime, $timestart, $timeend,
 437              $nullcalculation) {
 438  
 439          $this->resetAfterTest(true);
 440  
 441          $user = $this->getDataGenerator()->create_user();
 442          $course = $this->getDataGenerator()->create_course();
 443          $this->getDataGenerator()->enrol_user($user->id, $course->id, null, 'manual', $timestart, $timeend);
 444  
 445          $target = new \core_course\analytics\target\course_completion();
 446          $analyser = new \core\analytics\analyser\student_enrolments(1, $target, [], [], []);
 447          $analysable = new \core_analytics\course($course);
 448  
 449          $class = new \ReflectionClass('\core\analytics\analyser\student_enrolments');
 450          $method = $class->getMethod('get_all_samples');
 451          $method->setAccessible(true);
 452  
 453          list($sampleids, $samplesdata) = $method->invoke($analyser, $analysable);
 454          $target->add_sample_data($samplesdata);
 455          $sampleid = reset($sampleids);
 456  
 457          $reftarget = new \ReflectionObject($target);
 458          $refmethod = $reftarget->getMethod('calculate_sample');
 459          $refmethod->setAccessible(true);
 460  
 461          if ($nullcalculation) {
 462              $this->assertNull($refmethod->invoke($target, $sampleid, $analysable, $starttime, $endtime));
 463          } else {
 464              $this->assertNotNull($refmethod->invoke($target, $sampleid, $analysable, $starttime, $endtime));
 465          }
 466      }
 467  
 468      /**
 469       * Setup user, framework, competencies and course competencies.
 470       */
 471      protected function setup_competencies_environment() {
 472          $this->resetAfterTest(true);
 473          $now = time();
 474          $this->setAdminUser();
 475          $dg = $this->getDataGenerator();
 476          $lpg = $dg->get_plugin_generator('core_competency');
 477  
 478          $course = $dg->create_course(array('startdate' => $now - WEEKSECS, 'enddate' => $now - DAYSECS));
 479          $coursenocompetencies = $dg->create_course(array('startdate' => $now - WEEKSECS, 'enddate' => $now - DAYSECS));
 480  
 481          $u1 = $dg->create_user();
 482          $this->getDataGenerator()->enrol_user($u1->id, $course->id);
 483          $this->getDataGenerator()->enrol_user($u1->id, $coursenocompetencies->id);
 484          $f1 = $lpg->create_framework();
 485          $c1 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
 486          $c2 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
 487          $c3 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
 488          $c4 = $lpg->create_competency(array('competencyframeworkid' => $f1->get('id')));
 489          $cc1 = $lpg->create_course_competency(array('competencyid' => $c1->get('id'), 'courseid' => $course->id,
 490              'ruleoutcome' => \core_competency\course_competency::OUTCOME_NONE));
 491          $cc2 = $lpg->create_course_competency(array('competencyid' => $c2->get('id'), 'courseid' => $course->id,
 492              'ruleoutcome' => \core_competency\course_competency::OUTCOME_EVIDENCE));
 493          $cc3 = $lpg->create_course_competency(array('competencyid' => $c3->get('id'), 'courseid' => $course->id,
 494              'ruleoutcome' => \core_competency\course_competency::OUTCOME_RECOMMEND));
 495          $cc4 = $lpg->create_course_competency(array('competencyid' => $c4->get('id'), 'courseid' => $course->id,
 496              'ruleoutcome' => \core_competency\course_competency::OUTCOME_COMPLETE));
 497  
 498          return array(
 499              'course' => $course,
 500              'coursenocompetencies' => $coursenocompetencies,
 501              'user' => $u1,
 502              'course_competencies' => array($cc1, $cc2, $cc3, $cc4)
 503          );
 504      }
 505  
 506       /**
 507        * Test the specific conditions of a valid analysable for the course_competencies target.
 508        */
 509      public function test_core_target_course_competencies_analysable() {
 510  
 511          $data = $this->setup_competencies_environment();
 512  
 513          $analysable = new \core_analytics\course($data['course']);
 514          $target = new \core_course\analytics\target\course_competencies();
 515  
 516          $this->assertTrue($target->is_valid_analysable($analysable));
 517  
 518          $analysable = new \core_analytics\course($data['coursenocompetencies']);
 519          $this->assertEquals(get_string('nocompetenciesincourse', 'tool_lp'), $target->is_valid_analysable($analysable));
 520      }
 521  
 522      /**
 523       * Test the target value calculation.
 524       */
 525      public function test_core_target_course_competencies_calculate() {
 526  
 527          $data = $this->setup_competencies_environment();
 528  
 529          $target = new \core_course\analytics\target\course_competencies();
 530          $analyser = new \core\analytics\analyser\student_enrolments(1, $target, [], [], []);
 531          $analysable = new \core_analytics\course($data['course']);
 532  
 533          $class = new \ReflectionClass('\core\analytics\analyser\student_enrolments');
 534          $method = $class->getMethod('get_all_samples');
 535          $method->setAccessible(true);
 536  
 537          list($sampleids, $samplesdata) = $method->invoke($analyser, $analysable);
 538          $target->add_sample_data($samplesdata);
 539          $sampleid = reset($sampleids);
 540  
 541          $class = new \ReflectionClass('\core_course\analytics\target\course_competencies');
 542          $method = $class->getMethod('calculate_sample');
 543          $method->setAccessible(true);
 544  
 545          // Method calculate_sample() returns 1 when the user has not achieved all the competencies assigned to the course.
 546          $this->assertEquals(1, $method->invoke($target, $sampleid, $analysable));
 547  
 548          // Grading of all the competences assigned to the course, in such way that the user achieves them all.
 549          foreach ($data['course_competencies'] as $competency) {
 550              \core_competency\api::grade_competency_in_course($data['course']->id, $data['user']->id,
 551                      $competency->get('competencyid'), 3, 'Unit test');
 552          }
 553          // Method calculate_sample() returns 0 when the user has achieved all the competencies assigned to the course.
 554          $this->assertEquals(0, $method->invoke($target, $sampleid, $analysable));
 555      }
 556  
 557      /**
 558       * Test the specific conditions of a valid analysable for the course_gradetopass target.
 559       */
 560      public function test_core_target_course_gradetopass_analysable() {
 561          global $DB;
 562  
 563          $this->resetAfterTest(true);
 564          $now = time();
 565  
 566          $dg = $this->getDataGenerator();
 567  
 568          // Course without grade to pass set.
 569          $course1 = $dg->create_course(array('startdate' => $now - WEEKSECS, 'enddate' => $now - DAYSECS));
 570          $student1 = $dg->create_user();
 571          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 572          $dg->enrol_user($student1->id, $course1->id, $studentrole->id);
 573  
 574          $analysable = new \core_analytics\course($course1);
 575          $target = new \core_course\analytics\target\course_gradetopass();
 576          $this->assertEquals(get_string('gradetopassnotset', 'course'), $target->is_valid_analysable($analysable));
 577  
 578          // Set grade to pass.
 579          $courseitem = \grade_item::fetch_course_item($course1->id);
 580          $courseitem->gradepass = 50;
 581          $DB->update_record('grade_items', $courseitem);
 582          // Since the grade to pass value is cached in the target, a new one it is instanciated.
 583          $target = new \core_course\analytics\target\course_gradetopass();
 584          $this->assertTrue($target->is_valid_analysable($analysable));
 585  
 586      }
 587  
 588      /**
 589       * Test the target value calculation of the course_gradetopass target.
 590       */
 591      public function test_core_target_course_gradetopass_calculate() {
 592          global $DB;
 593  
 594          $this->resetAfterTest(true);
 595  
 596          $dg = $this->getDataGenerator();
 597          $course1 = $dg->create_course();
 598          // Set grade to pass.
 599          $student1 = $dg->create_user();
 600          $student2 = $dg->create_user();
 601          $student3 = $dg->create_user();
 602          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
 603          $dg->enrol_user($student1->id, $course1->id, $studentrole->id);
 604          $dg->enrol_user($student2->id, $course1->id, $studentrole->id);
 605          $dg->enrol_user($student3->id, $course1->id, $studentrole->id);
 606  
 607          // get_all_samples() does not guarantee any order, so let's
 608          // explicitly define the expectations here for later comparing.
 609          // Expectations format being array($userid => expectation, ...)
 610          $expectations = [];
 611  
 612          $courseitem = \grade_item::fetch_course_item($course1->id);
 613          // Student1 (< gradepass) fails, so it's non achieved sample.
 614          $courseitem->update_final_grade($student1->id, 30);
 615          $expectations[$student1->id] = 1;
 616  
 617          // Student2 (> gradepass) passes, so it's achieved sample.
 618          $courseitem->update_final_grade($student2->id, 60);
 619          $expectations[$student2->id] = 0;
 620  
 621          // Student 3 (has no grade) fails, so it's non achieved sample.
 622          $expectations[$student3->id] = 1;
 623  
 624          $courseitem->gradepass = 50;
 625          $DB->update_record('grade_items', $courseitem);
 626  
 627          $target = new \core_course\analytics\target\course_gradetopass();
 628          $analyser = new \core\analytics\analyser\student_enrolments(1, $target, [], [], []);
 629          $analysable = new \core_analytics\course($course1);
 630  
 631          $class = new \ReflectionClass('\core\analytics\analyser\student_enrolments');
 632          $method = $class->getMethod('get_all_samples');
 633          $method->setAccessible(true);
 634  
 635          list($sampleids, $samplesdata) = $method->invoke($analyser, $analysable);
 636          $target->add_sample_data($samplesdata);
 637  
 638          $class = new \ReflectionClass('\core_course\analytics\target\course_gradetopass');
 639          $method = $class->getMethod('calculate_sample');
 640          $method->setAccessible(true);
 641  
 642          // Verify all the expectations are fulfilled.
 643          foreach ($sampleids as $sampleid => $key) {
 644              $this->assertEquals($expectations[$samplesdata[$key]['user']->id], $method->invoke($target, $sampleid, $analysable));
 645          }
 646      }
 647  }