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