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] [Versions 401 and 402] [Versions 401 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   * mod_h5pactivity attempt tests
  19   *
  20   * @package    mod_h5pactivity
  21   * @category   test
  22   * @copyright  2020 Ferran Recio <ferran@moodle.com>
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  namespace mod_h5pactivity\local;
  27  
  28  use \core_xapi\local\statement;
  29  use \core_xapi\local\statement\item;
  30  use \core_xapi\local\statement\item_agent;
  31  use \core_xapi\local\statement\item_activity;
  32  use \core_xapi\local\statement\item_definition;
  33  use \core_xapi\local\statement\item_verb;
  34  use \core_xapi\local\statement\item_result;
  35  use stdClass;
  36  
  37  /**
  38   * Attempt tests class for mod_h5pactivity.
  39   *
  40   * @package    mod_h5pactivity
  41   * @category   test
  42   * @copyright  2020 Ferran Recio <ferran@moodle.com>
  43   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  44   */
  45  class attempt_test extends \advanced_testcase {
  46  
  47      /**
  48       * Generate a scenario to run all tests.
  49       * @return array course_modules, user record, course record
  50       */
  51      private function generate_testing_scenario(): array {
  52          $this->resetAfterTest();
  53          $this->setAdminUser();
  54  
  55          $course = $this->getDataGenerator()->create_course();
  56          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
  57          $cm = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
  58          $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
  59  
  60          return [$cm, $student, $course];
  61      }
  62  
  63      /**
  64       * Test for create_attempt method.
  65       */
  66      public function test_create_attempt() {
  67  
  68          list($cm, $student) = $this->generate_testing_scenario();
  69  
  70          // Create first attempt.
  71          $attempt = attempt::new_attempt($student, $cm);
  72          $this->assertEquals($student->id, $attempt->get_userid());
  73          $this->assertEquals($cm->instance, $attempt->get_h5pactivityid());
  74          $this->assertEquals(1, $attempt->get_attempt());
  75  
  76          // Create a second attempt.
  77          $attempt = attempt::new_attempt($student, $cm);
  78          $this->assertEquals($student->id, $attempt->get_userid());
  79          $this->assertEquals($cm->instance, $attempt->get_h5pactivityid());
  80          $this->assertEquals(2, $attempt->get_attempt());
  81      }
  82  
  83      /**
  84       * Test for last_attempt method
  85       */
  86      public function test_last_attempt() {
  87  
  88          list($cm, $student) = $this->generate_testing_scenario();
  89  
  90          // Create first attempt.
  91          $attempt = attempt::last_attempt($student, $cm);
  92          $this->assertEquals($student->id, $attempt->get_userid());
  93          $this->assertEquals($cm->instance, $attempt->get_h5pactivityid());
  94          $this->assertEquals(1, $attempt->get_attempt());
  95          $lastid = $attempt->get_id();
  96  
  97          // Get last attempt.
  98          $attempt = attempt::last_attempt($student, $cm);
  99          $this->assertEquals($student->id, $attempt->get_userid());
 100          $this->assertEquals($cm->instance, $attempt->get_h5pactivityid());
 101          $this->assertEquals(1, $attempt->get_attempt());
 102          $this->assertEquals($lastid, $attempt->get_id());
 103  
 104          // Now force a new attempt.
 105          $attempt = attempt::new_attempt($student, $cm);
 106          $this->assertEquals($student->id, $attempt->get_userid());
 107          $this->assertEquals($cm->instance, $attempt->get_h5pactivityid());
 108          $this->assertEquals(2, $attempt->get_attempt());
 109          $lastid = $attempt->get_id();
 110  
 111          // Get last attempt.
 112          $attempt = attempt::last_attempt($student, $cm);
 113          $this->assertEquals($student->id, $attempt->get_userid());
 114          $this->assertEquals($cm->instance, $attempt->get_h5pactivityid());
 115          $this->assertEquals(2, $attempt->get_attempt());
 116          $this->assertEquals($lastid, $attempt->get_id());
 117      }
 118  
 119      /**
 120       * Test saving statements.
 121       *
 122       * @dataProvider save_statement_data
 123       * @param string $subcontent subcontent identifier
 124       * @param bool $hasdefinition generate definition
 125       * @param bool $hasresult generate result
 126       * @param array $results 0 => insert ok, 1 => maxscore, 2 => rawscore, 3 => count
 127       */
 128      public function test_save_statement(string $subcontent, bool $hasdefinition, bool $hasresult, array $results) {
 129  
 130          list($cm, $student) = $this->generate_testing_scenario();
 131  
 132          $attempt = attempt::new_attempt($student, $cm);
 133          $this->assertEquals(0, $attempt->get_maxscore());
 134          $this->assertEquals(0, $attempt->get_rawscore());
 135          $this->assertEquals(0, $attempt->count_results());
 136          $this->assertEquals(0, $attempt->get_duration());
 137          $this->assertNull($attempt->get_completion());
 138          $this->assertNull($attempt->get_success());
 139          $this->assertFalse($attempt->get_scoreupdated());
 140  
 141          $statement = $this->generate_statement($hasdefinition, $hasresult);
 142          $result = $attempt->save_statement($statement, $subcontent);
 143          $this->assertEquals($results[0], $result);
 144          $this->assertEquals($results[1], $attempt->get_maxscore());
 145          $this->assertEquals($results[2], $attempt->get_rawscore());
 146          $this->assertEquals($results[3], $attempt->count_results());
 147          $this->assertEquals($results[4], $attempt->get_duration());
 148          $this->assertEquals($results[5], $attempt->get_completion());
 149          $this->assertEquals($results[6], $attempt->get_success());
 150          if ($results[5]) {
 151              $this->assertTrue($attempt->get_scoreupdated());
 152          } else {
 153              $this->assertFalse($attempt->get_scoreupdated());
 154          }
 155      }
 156  
 157      /**
 158       * Data provider for data request creation tests.
 159       *
 160       * @return array
 161       */
 162      public function save_statement_data(): array {
 163          return [
 164              'Statement without definition and result' => [
 165                  '', false, false, [false, 0, 0, 0, 0, null, null]
 166              ],
 167              'Statement with definition but no result' => [
 168                  '', true, false, [false, 0, 0, 0, 0, null, null]
 169              ],
 170              'Statement with result but no definition' => [
 171                  '', true, false, [false, 0, 0, 0, 0, null, null]
 172              ],
 173              'Statement subcontent without definition and result' => [
 174                  '111-222-333', false, false, [false, 0, 0, 0, 0, null, null]
 175              ],
 176              'Statement subcontent with definition but no result' => [
 177                  '111-222-333', true, false, [false, 0, 0, 0, 0, null, null]
 178              ],
 179              'Statement subcontent with result but no definition' => [
 180                  '111-222-333', true, false, [false, 0, 0, 0, 0, null, null]
 181              ],
 182              'Statement with definition, result but no subcontent' => [
 183                  '', true, true, [true, 2, 2, 1, 25, 1, 1]
 184              ],
 185              'Statement with definition, result and subcontent' => [
 186                  '111-222-333', true, true, [true, 0, 0, 1, 0, null, null]
 187              ],
 188          ];
 189      }
 190  
 191      /**
 192       * Test delete results from attempt.
 193       */
 194      public function test_delete_results() {
 195  
 196          list($cm, $student) = $this->generate_testing_scenario();
 197  
 198          $attempt = $this->generate_full_attempt($student, $cm);
 199          $attempt->delete_results();
 200          $this->assertEquals(0, $attempt->count_results());
 201      }
 202  
 203      /**
 204       * Test delete attempt.
 205       */
 206      public function test_delete_attempt() {
 207          global $DB;
 208  
 209          list($cm, $student) = $this->generate_testing_scenario();
 210  
 211          // Check no previous attempts are created.
 212          $count = $DB->count_records('h5pactivity_attempts');
 213          $this->assertEquals(0, $count);
 214          $count = $DB->count_records('h5pactivity_attempts_results');
 215          $this->assertEquals(0, $count);
 216  
 217          // Generate one attempt.
 218          $attempt1 = $this->generate_full_attempt($student, $cm);
 219          $count = $DB->count_records('h5pactivity_attempts');
 220          $this->assertEquals(1, $count);
 221          $count = $DB->count_records('h5pactivity_attempts_results');
 222          $this->assertEquals(2, $count);
 223  
 224          // Generate a second attempt.
 225          $attempt2 = $this->generate_full_attempt($student, $cm);
 226          $count = $DB->count_records('h5pactivity_attempts');
 227          $this->assertEquals(2, $count);
 228          $count = $DB->count_records('h5pactivity_attempts_results');
 229          $this->assertEquals(4, $count);
 230  
 231          // Delete the first attempt.
 232          attempt::delete_attempt($attempt1);
 233          $count = $DB->count_records('h5pactivity_attempts');
 234          $this->assertEquals(1, $count);
 235          $count = $DB->count_records('h5pactivity_attempts_results');
 236          $this->assertEquals(2, $count);
 237          $this->assertEquals(2, $attempt2->count_results());
 238      }
 239  
 240      /**
 241       * Test delete all attempts.
 242       *
 243       * @dataProvider delete_all_attempts_data
 244       * @param bool $hasstudent if user is specificed
 245       * @param int[] 0-3 => statements count results, 4-5 => totals
 246       */
 247      public function test_delete_all_attempts(bool $hasstudent, array $results) {
 248          global $DB;
 249  
 250          list($cm, $student, $course) = $this->generate_testing_scenario();
 251  
 252          // For this test we need extra activity and student.
 253          $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]);
 254          $cm2 = get_coursemodule_from_id('h5pactivity', $activity->cmid, 0, false, MUST_EXIST);
 255          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 256  
 257          // Check no previous attempts are created.
 258          $count = $DB->count_records('h5pactivity_attempts');
 259          $this->assertEquals(0, $count);
 260          $count = $DB->count_records('h5pactivity_attempts_results');
 261          $this->assertEquals(0, $count);
 262  
 263          // Generate some attempts attempt on both activities and students.
 264          $attempts = [];
 265          $attempts[] = $this->generate_full_attempt($student, $cm);
 266          $attempts[] = $this->generate_full_attempt($student2, $cm);
 267          $attempts[] = $this->generate_full_attempt($student, $cm2);
 268          $attempts[] = $this->generate_full_attempt($student2, $cm2);
 269          $count = $DB->count_records('h5pactivity_attempts');
 270          $this->assertEquals(4, $count);
 271          $count = $DB->count_records('h5pactivity_attempts_results');
 272          $this->assertEquals(8, $count);
 273  
 274          // Delete all specified attempts.
 275          $user = ($hasstudent) ? $student : null;
 276          attempt::delete_all_attempts($cm, $user);
 277  
 278          // Check data.
 279          for ($assert = 0; $assert < 4; $assert++) {
 280              $count = $attempts[$assert]->count_results();
 281              $this->assertEquals($results[$assert], $count);
 282          }
 283          $count = $DB->count_records('h5pactivity_attempts');
 284          $this->assertEquals($results[4], $count);
 285          $count = $DB->count_records('h5pactivity_attempts_results');
 286          $this->assertEquals($results[5], $count);
 287      }
 288  
 289      /**
 290       * Data provider for data request creation tests.
 291       *
 292       * @return array
 293       */
 294      public function delete_all_attempts_data(): array {
 295          return [
 296              'Delete all attempts from activity' => [
 297                  false, [0, 0, 2, 2, 2, 4]
 298              ],
 299              'Delete all attempts from user' => [
 300                  true, [0, 2, 2, 2, 3, 6]
 301              ],
 302          ];
 303      }
 304  
 305      /**
 306       * Test set_score method.
 307       *
 308       */
 309      public function test_set_score(): void {
 310          global $DB;
 311  
 312          list($cm, $student, $course) = $this->generate_testing_scenario();
 313  
 314          // Generate one attempt.
 315          $attempt = $this->generate_full_attempt($student, $cm);
 316  
 317          $dbattempt = $DB->get_record('h5pactivity_attempts', ['id' => $attempt->get_id()]);
 318          $this->assertEquals($dbattempt->rawscore, $attempt->get_rawscore());
 319          $this->assertEquals(2, $dbattempt->rawscore);
 320          $this->assertEquals($dbattempt->maxscore, $attempt->get_maxscore());
 321          $this->assertEquals(2, $dbattempt->maxscore);
 322          $this->assertEquals(1, $dbattempt->scaled);
 323  
 324          // Set attempt score.
 325          $attempt->set_score(5, 10);
 326  
 327          $this->assertEquals(5, $attempt->get_rawscore());
 328          $this->assertEquals(10, $attempt->get_maxscore());
 329          $this->assertTrue($attempt->get_scoreupdated());
 330  
 331          // Save new score into DB.
 332          $attempt->save();
 333  
 334          $dbattempt = $DB->get_record('h5pactivity_attempts', ['id' => $attempt->get_id()]);
 335          $this->assertEquals($dbattempt->rawscore, $attempt->get_rawscore());
 336          $this->assertEquals(5, $dbattempt->rawscore);
 337          $this->assertEquals($dbattempt->maxscore, $attempt->get_maxscore());
 338          $this->assertEquals(10, $dbattempt->maxscore);
 339          $this->assertEquals(0.5, $dbattempt->scaled);
 340      }
 341  
 342      /**
 343       * Test set_duration method.
 344       *
 345       * @dataProvider basic_setters_data
 346       * @param string $attribute the stribute to test
 347       * @param int $oldvalue attribute old value
 348       * @param int $newvalue attribute new expected value
 349       */
 350      public function test_basic_setters(string $attribute, int $oldvalue, int $newvalue): void {
 351          global $DB;
 352  
 353          list($cm, $student, $course) = $this->generate_testing_scenario();
 354  
 355          // Generate one attempt.
 356          $attempt = $this->generate_full_attempt($student, $cm);
 357  
 358          $setmethod = 'set_'.$attribute;
 359          $getmethod = 'get_'.$attribute;
 360  
 361          $dbattempt = $DB->get_record('h5pactivity_attempts', ['id' => $attempt->get_id()]);
 362          $this->assertEquals($dbattempt->$attribute, $attempt->$getmethod());
 363          $this->assertEquals($oldvalue, $dbattempt->$attribute);
 364  
 365          // Set attempt attribute.
 366          $attempt->$setmethod($newvalue);
 367  
 368          $this->assertEquals($newvalue, $attempt->$getmethod());
 369  
 370          // Save new score into DB.
 371          $attempt->save();
 372  
 373          $dbattempt = $DB->get_record('h5pactivity_attempts', ['id' => $attempt->get_id()]);
 374          $this->assertEquals($dbattempt->$attribute, $attempt->$getmethod());
 375          $this->assertEquals($newvalue, $dbattempt->$attribute);
 376  
 377          // Set null $attribute.
 378          $attempt->$setmethod(null);
 379  
 380          $this->assertNull($attempt->$getmethod());
 381  
 382          // Save new score into DB.
 383          $attempt->save();
 384  
 385          $dbattempt = $DB->get_record('h5pactivity_attempts', ['id' => $attempt->get_id()]);
 386          $this->assertEquals($dbattempt->$attribute, $attempt->$getmethod());
 387          $this->assertNull($dbattempt->$attribute);
 388      }
 389  
 390      /**
 391       * Data provider for testing basic setters.
 392       *
 393       * @return array
 394       */
 395      public function basic_setters_data(): array {
 396          return [
 397              'Set attempt duration' => [
 398                  'duration', 25, 35
 399              ],
 400              'Set attempt completion' => [
 401                  'completion', 1, 0
 402              ],
 403              'Set attempt success' => [
 404                  'success', 1, 0
 405              ],
 406          ];
 407      }
 408  
 409      /**
 410       * Generate a fake attempt with two results.
 411       *
 412       * @param stdClass $student a user record
 413       * @param stdClass $cm a course_module record
 414       * @return attempt
 415       */
 416      private function generate_full_attempt($student, $cm): attempt {
 417          $attempt = attempt::new_attempt($student, $cm);
 418          $this->assertEquals(0, $attempt->get_maxscore());
 419          $this->assertEquals(0, $attempt->get_rawscore());
 420          $this->assertEquals(0, $attempt->count_results());
 421  
 422          $statement = $this->generate_statement(true, true);
 423          $saveok = $attempt->save_statement($statement, '');
 424          $this->assertTrue($saveok);
 425          $saveok = $attempt->save_statement($statement, '111-222-333');
 426          $this->assertTrue($saveok);
 427          $this->assertEquals(2, $attempt->count_results());
 428  
 429          return $attempt;
 430      }
 431  
 432      /**
 433       * Return a xAPI partial statement with object defined.
 434       * @param bool $hasdefinition if has to include definition
 435       * @param bool $hasresult if has to include results
 436       * @return statement
 437       */
 438      private function generate_statement(bool $hasdefinition, bool $hasresult): statement {
 439          global $USER;
 440  
 441          $statement = new statement();
 442          $statement->set_actor(item_agent::create_from_user($USER));
 443          $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed'));
 444          $definition = null;
 445          if ($hasdefinition) {
 446              $definition = item_definition::create_from_data((object)[
 447                  'interactionType' => 'compound',
 448                  'correctResponsesPattern' => '1',
 449              ]);
 450          }
 451          $statement->set_object(item_activity::create_from_id('something', $definition));
 452          if ($hasresult) {
 453              $statement->set_result(item_result::create_from_data((object)[
 454                  'completion' => true,
 455                  'success' => true,
 456                  'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1],
 457                  'duration' => 'PT25S',
 458              ]));
 459          }
 460          return $statement;
 461      }
 462  }