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 generator 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\xapi;
  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 context_module;
  36  use stdClass;
  37  
  38  /**
  39   * Attempt tests class for mod_h5pactivity.
  40   *
  41   * @package    mod_h5pactivity
  42   * @category   test
  43   * @copyright  2020 Ferran Recio <ferran@moodle.com>
  44   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  class handler_test extends \advanced_testcase {
  47  
  48      /**
  49       * Generate a valid scenario for each tests.
  50       *
  51       * @return stdClass an object with all scenario data in it
  52       */
  53      private function generate_testing_scenario(): stdClass {
  54  
  55          $this->resetAfterTest();
  56          $this->setAdminUser();
  57  
  58          $data = new stdClass();
  59  
  60          $data->course = $this->getDataGenerator()->create_course();
  61  
  62          // Generate 2 users, one enroled into course and one not.
  63          $data->student = $this->getDataGenerator()->create_and_enrol($data->course, 'student');
  64          $data->otheruser = $this->getDataGenerator()->create_user();
  65  
  66          // H5P activity.
  67          $data->activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $data->course]);
  68          $data->context = context_module::instance($data->activity->cmid);
  69  
  70          $data->xapihandler = handler::create('mod_h5pactivity');
  71          $this->assertNotEmpty($data->xapihandler);
  72          $this->assertInstanceOf('\mod_h5pactivity\xapi\handler', $data->xapihandler);
  73  
  74          $this->setUser($data->student);
  75  
  76          return $data;
  77      }
  78  
  79      /**
  80       * Test for xapi_handler with valid statements.
  81       */
  82      public function test_xapi_handler() {
  83          global $DB;
  84  
  85          $data = $this->generate_testing_scenario();
  86          $xapihandler = $data->xapihandler;
  87          $context = $data->context;
  88          $student = $data->student;
  89          $otheruser = $data->otheruser;
  90  
  91          // Check we have 0 entries in the attempts tables.
  92          $count = $DB->count_records('h5pactivity_attempts');
  93          $this->assertEquals(0, $count);
  94          $count = $DB->count_records('h5pactivity_attempts_results');
  95          $this->assertEquals(0, $count);
  96  
  97          $statements = $this->generate_statements($context, $student);
  98  
  99          // Insert first statement.
 100          $event = $xapihandler->statement_to_event($statements[0]);
 101          $this->assertNotNull($event);
 102          $count = $DB->count_records('h5pactivity_attempts');
 103          $this->assertEquals(1, $count);
 104          $count = $DB->count_records('h5pactivity_attempts_results');
 105          $this->assertEquals(1, $count);
 106  
 107          // Insert second statement.
 108          $event = $xapihandler->statement_to_event($statements[1]);
 109          $this->assertNotNull($event);
 110          $count = $DB->count_records('h5pactivity_attempts');
 111          $this->assertEquals(1, $count);
 112          $count = $DB->count_records('h5pactivity_attempts_results');
 113          $this->assertEquals(2, $count);
 114  
 115          // Insert again first statement.
 116          $event = $xapihandler->statement_to_event($statements[0]);
 117          $this->assertNotNull($event);
 118          $count = $DB->count_records('h5pactivity_attempts');
 119          $this->assertEquals(2, $count);
 120          $count = $DB->count_records('h5pactivity_attempts_results');
 121          $this->assertEquals(3, $count);
 122  
 123          // Insert again second statement.
 124          $event = $xapihandler->statement_to_event($statements[1]);
 125          $this->assertNotNull($event);
 126          $count = $DB->count_records('h5pactivity_attempts');
 127          $this->assertEquals(2, $count);
 128          $count = $DB->count_records('h5pactivity_attempts_results');
 129          $this->assertEquals(4, $count);
 130      }
 131  
 132      /**
 133       * Testing wrong statements scenarios.
 134       *
 135       * @dataProvider xapi_handler_errors_data
 136       * @param bool $hasverb valid verb
 137       * @param bool $hasdefinition generate definition
 138       * @param bool $hasresult generate result
 139       * @param bool $hascontext valid context
 140       * @param bool $hasuser valid user
 141       * @param bool $generateattempt if generates an empty attempt
 142       */
 143      public function test_xapi_handler_errors(bool $hasverb, bool $hasdefinition, bool $hasresult,
 144              bool $hascontext, bool $hasuser, bool $generateattempt) {
 145          global $DB, $CFG;
 146  
 147          $data = $this->generate_testing_scenario();
 148          $xapihandler = $data->xapihandler;
 149          $context = $data->context;
 150          $student = $data->student;
 151          $otheruser = $data->otheruser;
 152  
 153          // Check we have 0 entries in the attempts tables.
 154          $count = $DB->count_records('h5pactivity_attempts');
 155          $this->assertEquals(0, $count);
 156          $count = $DB->count_records('h5pactivity_attempts_results');
 157          $this->assertEquals(0, $count);
 158  
 159          $statement = new statement();
 160          if ($hasverb) {
 161              $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed'));
 162          } else {
 163              $statement->set_verb(item_verb::create_from_id('cook'));
 164          }
 165          $definition = null;
 166          if ($hasdefinition) {
 167              $definition = item_definition::create_from_data((object)[
 168                  'interactionType' => 'compound',
 169                  'correctResponsesPattern' => '1',
 170              ]);
 171          }
 172          if ($hascontext) {
 173              $statement->set_object(item_activity::create_from_id($context->id, $definition));
 174          } else {
 175              $statement->set_object(item_activity::create_from_id('paella', $definition));
 176          }
 177          if ($hasresult) {
 178              $statement->set_result(item_result::create_from_data((object)[
 179                  'completion' => true,
 180                  'success' => true,
 181                  'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1],
 182              ]));
 183          }
 184          if ($hasuser) {
 185              $statement->set_actor(item_agent::create_from_user($student));
 186          } else {
 187              $statement->set_actor(item_agent::create_from_user($otheruser));
 188          }
 189  
 190          $event = $xapihandler->statement_to_event($statement);
 191          $this->assertNull($event);
 192          // No enties should be generated.
 193          $count = $DB->count_records('h5pactivity_attempts');
 194          $attempts = ($generateattempt) ? 1 : 0;
 195          $this->assertEquals($attempts, $count);
 196          $count = $DB->count_records('h5pactivity_attempts_results');
 197          $this->assertEquals(0, $count);
 198      }
 199  
 200      /**
 201       * Data provider for data request creation tests.
 202       *
 203       * @return array
 204       */
 205      public function xapi_handler_errors_data(): array {
 206          return [
 207              // Invalid Definitions and results possibilities.
 208              'Invalid definition and result' => [
 209                  true, false, false, true, true, false
 210              ],
 211              'Invalid result' => [
 212                  true, true, false, true, true, false
 213              ],
 214              'Invalid definition (generate empty attempt)' => [
 215                  true, false, true, true, true, true
 216              ],
 217              // Invalid verb possibilities.
 218              'Invalid verb, definition and result' => [
 219                  false, false, false, true, true, false
 220              ],
 221              'Invalid verb and result' => [
 222                  false, true, false, true, true, false
 223              ],
 224              'Invalid verb and result' => [
 225                  false, false, true, true, true, false
 226              ],
 227              // Invalid context possibilities.
 228              'Invalid definition, result and context' => [
 229                  true, false, false, false, true, false
 230              ],
 231              'Invalid result' => [
 232                  true, true, false, false, true, false
 233              ],
 234              'Invalid result and context' => [
 235                  true, false, true, false, true, false
 236              ],
 237              'Invalid verb, definition result and context' => [
 238                  false, false, false, false, true, false
 239              ],
 240              'Invalid verb, result and context' => [
 241                  false, true, false, false, true, false
 242              ],
 243              'Invalid verb, result and context' => [
 244                  false, false, true, false, true, false
 245              ],
 246              // Invalid user possibilities.
 247              'Invalid definition, result and user' => [
 248                  true, false, false, true, false, false
 249              ],
 250              'Invalid result and user' => [
 251                  true, true, false, true, false, false
 252              ],
 253              'Invalid definition and user' => [
 254                  true, false, true, true, false, false
 255              ],
 256              'Invalid verb, definition, result and user' => [
 257                  false, false, false, true, false, false
 258              ],
 259              'Invalid verb, result and user' => [
 260                  false, true, false, true, false, false
 261              ],
 262              'Invalid verb, result and user' => [
 263                  false, false, true, true, false, false
 264              ],
 265              'Invalid definition, result, context and user' => [
 266                  true, false, false, false, false, false
 267              ],
 268              'Invalid result, context and user' => [
 269                  true, true, false, false, false, false
 270              ],
 271              'Invalid definition, context and user' => [
 272                  true, false, true, false, false, false
 273              ],
 274              'Invalid verb, definition, result, context and user' => [
 275                  false, false, false, false, false, false
 276              ],
 277              'Invalid verb, result, context and user' => [
 278                  false, true, false, false, false, false
 279              ],
 280              'Invalid verb, result, context and user' => [
 281                  false, false, true, false, false, false
 282              ],
 283          ];
 284      }
 285  
 286      /**
 287       * Test xapi_handler stored statements.
 288       */
 289      public function test_stored_statements() {
 290          global $DB;
 291  
 292          $data = $this->generate_testing_scenario();
 293          $xapihandler = $data->xapihandler;
 294          $context = $data->context;
 295          $student = $data->student;
 296          $otheruser = $data->otheruser;
 297          $activity = $data->activity;
 298  
 299          // Check we have 0 entries in the attempts tables.
 300          $count = $DB->count_records('h5pactivity_attempts');
 301          $this->assertEquals(0, $count);
 302          $count = $DB->count_records('h5pactivity_attempts_results');
 303          $this->assertEquals(0, $count);
 304  
 305          $statements = $this->generate_statements($context, $student);
 306  
 307          // Insert statements.
 308          $stored = $xapihandler->process_statements($statements);
 309          $this->assertCount(2, $stored);
 310          $this->assertEquals(true, $stored[0]);
 311          $this->assertEquals(true, $stored[1]);
 312          $count = $DB->count_records('h5pactivity_attempts');
 313          $this->assertEquals(1, $count);
 314          $count = $DB->count_records('h5pactivity_attempts_results');
 315          $this->assertEquals(2, $count);
 316  
 317          // Validate stored data.
 318          $attempts = $DB->get_records('h5pactivity_attempts');
 319          $attempt = array_shift($attempts);
 320          $statement = $statements[0];
 321          $data = $statement->get_result()->get_data();
 322          $this->assertEquals(1, $attempt->attempt);
 323          $this->assertEquals($student->id, $attempt->userid);
 324          $this->assertEquals($activity->id, $attempt->h5pactivityid);
 325          $this->assertEquals($data->score->raw, $attempt->rawscore);
 326          $this->assertEquals($data->score->max, $attempt->maxscore);
 327          $this->assertEquals($statement->get_result()->get_duration(), $attempt->duration);
 328          $this->assertEquals($data->completion, $attempt->completion);
 329          $this->assertEquals($data->success, $attempt->success);
 330  
 331          $results = $DB->get_records('h5pactivity_attempts_results');
 332          foreach ($results as $result) {
 333              $statement = (empty($result->subcontent)) ? $statements[0] : $statements[1];
 334              $xapiresult = $statement->get_result()->get_data();
 335              $xapiobject = $statement->get_object()->get_data();
 336              $this->assertEquals($attempt->id, $result->attemptid);
 337              $this->assertEquals($xapiobject->definition->interactionType, $result->interactiontype);
 338              $this->assertEquals($xapiresult->score->raw, $result->rawscore);
 339              $this->assertEquals($xapiresult->score->max, $result->maxscore);
 340              $this->assertEquals($statement->get_result()->get_duration(), $result->duration);
 341              $this->assertEquals($xapiresult->completion, $result->completion);
 342              $this->assertEquals($xapiresult->success, $result->success);
 343          }
 344      }
 345  
 346      /**
 347       * Returns a basic xAPI statements simulating a H5P content.
 348       *
 349       * @param context_module $context activity context
 350       * @param stdClass $user user record
 351       * @return statement[] array of xAPI statements
 352       */
 353      private function generate_statements(context_module $context, stdClass $user): array {
 354          $statements = [];
 355  
 356          $statement = new statement();
 357          $statement->set_actor(item_agent::create_from_user($user));
 358          $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed'));
 359          $definition = item_definition::create_from_data((object)[
 360              'interactionType' => 'compound',
 361              'correctResponsesPattern' => '1',
 362          ]);
 363          $statement->set_object(item_activity::create_from_id($context->id, $definition));
 364          $statement->set_result(item_result::create_from_data((object)[
 365              'completion' => true,
 366              'success' => true,
 367              'score' => (object) ['min' => 0, 'max' => 2, 'raw' => 2, 'scaled' => 1],
 368              'duration' => 'PT25S',
 369          ]));
 370          $statements[] = $statement;
 371  
 372          $statement = new statement();
 373          $statement->set_actor(item_agent::create_from_user($user));
 374          $statement->set_verb(item_verb::create_from_id('http://adlnet.gov/expapi/verbs/completed'));
 375          $definition = item_definition::create_from_data((object)[
 376              'interactionType' => 'matching',
 377              'correctResponsesPattern' => '1',
 378          ]);
 379          $statement->set_object(item_activity::create_from_id($context->id.'?subContentId=111-222-333', $definition));
 380          $statement->set_result(item_result::create_from_data((object)[
 381              'completion' => true,
 382              'success' => true,
 383              'score' => (object) ['min' => 0, 'max' => 1, 'raw' => 0, 'scaled' => 0],
 384              'duration' => 'PT20S',
 385          ]));
 386          $statements[] = $statement;
 387  
 388          return $statements;
 389      }
 390  }