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  /**
  18   * This file contains unit test related to xAPI library.
  19   *
  20   * @package    core_xapi
  21   * @copyright  2020 Ferran Recio
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core_xapi\local;
  26  
  27  use core_xapi\local\statement\item;
  28  use core_xapi\local\statement\item_actor;
  29  use core_xapi\local\statement\item_object;
  30  use core_xapi\local\statement\item_activity;
  31  use core_xapi\local\statement\item_verb;
  32  use core_xapi\local\statement\item_agent;
  33  use core_xapi\local\statement\item_group;
  34  use core_xapi\local\statement\item_result;
  35  use core_xapi\local\statement\item_attachment;
  36  use core_xapi\local\statement\item_context;
  37  use core_xapi\iri;
  38  use core_xapi\xapi_exception;
  39  use advanced_testcase;
  40  use stdClass;
  41  
  42  defined('MOODLE_INTERNAL') || die();
  43  
  44  /**
  45   * Contains test cases for testing statement class.
  46   *
  47   * @package    core_xapi
  48   * @since      Moodle 3.9
  49   * @copyright  2020 Ferran Recio
  50   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  51   */
  52  class statement_test extends advanced_testcase {
  53  
  54      /**
  55       * Returns a valid item for a specific attribute.
  56       *
  57       * @param string $itemname statement item name
  58       * @return item the resulting item
  59       */
  60      private function get_valid_item(string $itemname): item {
  61          global $USER, $CFG;
  62          switch ($itemname) {
  63              case 'attachments':
  64              case 'attachment':
  65                  $data = (object) [
  66                      'usageType' => iri::generate('example', 'attachment'),
  67                      'display' => (object) [
  68                          'en-US' => 'Example',
  69                      ],
  70                      'description' => (object) [
  71                          'en-US' => 'Description example',
  72                      ],
  73                      "contentType" => "image/jpg",
  74                      "length" => 1234,
  75                      "sha2" => "b94c0f1cffb77475c6f1899111a0181efe1d6177"
  76                  ];
  77                  return item_attachment::create_from_data($data);
  78              case 'authority':
  79                  $data = (object) [
  80                      'objectType' => 'Agent',
  81                      'account' => (object) [
  82                          'homePage' => $CFG->wwwroot,
  83                          'name' => $USER->id,
  84                      ],
  85                  ];
  86                  return item_agent::create_from_data($data);
  87          }
  88          // For now, the rest of the optional properties have no validation
  89          // so we create a standard stdClass for all of them.
  90          $data = (object)[
  91              'some' => 'data',
  92          ];
  93          $classname = 'core_xapi\local\statement\item_'.$itemname;
  94          if (class_exists($classname)) {
  95              $item = $classname::create_from_data($data);
  96          } else {
  97              $item = item::create_from_data($data);
  98          }
  99          return $item;
 100      }
 101  
 102      /**
 103       * Test statement creation.
 104       *
 105       * @dataProvider create_provider
 106       * @param bool $useagent if use agent as actor (or group if false)
 107       * @param array $extras extra item elements
 108       * @param array $extravalues extra string values
 109       */
 110      public function test_create(bool $useagent, array $extras, array $extravalues) {
 111  
 112          $this->resetAfterTest();
 113  
 114          // Create one course with a group.
 115          $course = $this->getDataGenerator()->create_course();
 116          $user = $this->getDataGenerator()->create_user();
 117          $this->getDataGenerator()->enrol_user($user->id, $course->id);
 118          $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 119          $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user->id));
 120  
 121          $this->setUser($user);
 122  
 123          // Our statement.
 124          $statement = new statement();
 125  
 126          // Populate statement.
 127          if ($useagent) {
 128              $statement->set_actor(item_agent::create_from_user($user));
 129          } else {
 130              $statement->set_actor(item_group::create_from_group($group));
 131          }
 132          $statement->set_verb(item_verb::create_from_id('cook'));
 133          $statement->set_object(item_activity::create_from_id('paella'));
 134  
 135          foreach ($extras as $extra) {
 136              $method = 'set_'.$extra;
 137              $item = $this->get_valid_item($extra);
 138              $statement->$method($item);
 139          }
 140  
 141          // For now extra values have no validation.
 142          foreach ($extravalues as $extra) {
 143              $method = 'set_'.$extra;
 144              $statement->$method('Example');
 145          }
 146  
 147          // Check resulting statement.
 148          if ($useagent) {
 149              $stuser = $statement->get_user();
 150              $this->assertEquals($user->id, $stuser->id);
 151              $stusers = $statement->get_all_users();
 152              $this->assertCount(1, $stusers);
 153          } else {
 154              $stgroup = $statement->get_group();
 155              $this->assertEquals($group->id, $stgroup->id);
 156              $stusers = $statement->get_all_users();
 157              $this->assertCount(1, $stusers);
 158              $stuser = array_shift($stusers);
 159              $this->assertEquals($user->id, $stuser->id);
 160          }
 161          $this->assertEquals('cook', $statement->get_verb_id());
 162          $this->assertEquals('paella', $statement->get_activity_id());
 163  
 164          // Check resulting json (only first node structure, internal structure
 165          // depends on every item json_encode test).
 166          $data = json_decode(json_encode($statement));
 167          $this->assertNotEmpty($data->actor);
 168          $this->assertNotEmpty($data->verb);
 169          $this->assertNotEmpty($data->object);
 170          $allextras = ['context', 'result', 'timestamp', 'stored', 'authority', 'version', 'attachments'];
 171          $alldefined = array_merge($extras, $extravalues);
 172          foreach ($allextras as $extra) {
 173              if (in_array($extra, $alldefined)) {
 174                  $this->assertObjectHasAttribute($extra, $data);
 175                  $this->assertNotEmpty($data->$extra);
 176              } else {
 177                  $this->assertObjectNotHasAttribute($extra, $data);
 178              }
 179          }
 180      }
 181  
 182      /**
 183       * Data provider for the test_create and test_create_from_data tests.
 184       *
 185       * @return  array
 186       */
 187      public function create_provider() : array {
 188          return [
 189              'Agent statement with no extras' => [
 190                  true, [], []
 191              ],
 192              'Agent statement with context' => [
 193                  true, ['context'], []
 194              ],
 195              'Agent statement with result' => [
 196                  true, ['result'], []
 197              ],
 198              'Agent statement with timestamp' => [
 199                  true, [], ['timestamp']
 200              ],
 201              'Agent statement with stored' => [
 202                  true, [], ['stored']
 203              ],
 204              'Agent statement with authority' => [
 205                  true, ['authority'], []
 206              ],
 207              'Agent statement with version' => [
 208                  true, [], ['version']
 209              ],
 210              'Group statement with no extras' => [
 211                  false, [], []
 212              ],
 213              'Group statement with context' => [
 214                  false, ['context'], []
 215              ],
 216              'Group statement with result' => [
 217                  false, ['result'], []
 218              ],
 219              'Group statement with timestamp' => [
 220                  false, [], ['timestamp']
 221              ],
 222              'Group statement with stored' => [
 223                  false, [], ['stored']
 224              ],
 225              'Group statement with authority' => [
 226                  false, ['authority'], []
 227              ],
 228              'Group statement with version' => [
 229                  false, [], ['version']
 230              ],
 231          ];
 232      }
 233  
 234      /**
 235       * Test statement creation from xAPI statement data.
 236       *
 237       * @dataProvider create_provider
 238       * @param bool $useagent if use agent as actor (or group if false)
 239       * @param array $extras extra item elements
 240       * @param array $extravalues extra string values
 241       */
 242      public function test_create_from_data(bool $useagent, array $extras, array $extravalues) {
 243          $this->resetAfterTest();
 244  
 245          // Create one course with a group.
 246          $course = $this->getDataGenerator()->create_course();
 247          $user = $this->getDataGenerator()->create_user();
 248          $this->getDataGenerator()->enrol_user($user->id, $course->id);
 249          $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 250          $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user->id));
 251  
 252          $this->setUser($user);
 253  
 254          // Populate data.
 255          if ($useagent) {
 256              $actor = item_agent::create_from_user($user);
 257          } else {
 258              $actor = item_group::create_from_group($group);
 259          }
 260          $verb = item_verb::create_from_id('cook');
 261          $object = item_activity::create_from_id('paella');
 262  
 263          $data = (object) [
 264              'actor' => $actor->get_data(),
 265              'verb' => $verb->get_data(),
 266              'object' => $object->get_data(),
 267          ];
 268  
 269          foreach ($extras as $extra) {
 270              $item = $this->get_valid_item($extra);
 271              $data->$extra = $item->get_data();
 272          }
 273  
 274          // For now extra values have no validation.
 275          foreach ($extravalues as $extra) {
 276              $data->$extra = 'Example';
 277          }
 278  
 279          $statement = statement::create_from_data($data);
 280  
 281          // Check resulting statement.
 282          if ($useagent) {
 283              $stuser = $statement->get_user();
 284              $this->assertEquals($user->id, $stuser->id);
 285              $stusers = $statement->get_all_users();
 286              $this->assertCount(1, $stusers);
 287          } else {
 288              $stgroup = $statement->get_group();
 289              $this->assertEquals($group->id, $stgroup->id);
 290              $stusers = $statement->get_all_users();
 291              $this->assertCount(1, $stusers);
 292              $stuser = array_shift($stusers);
 293              $this->assertEquals($user->id, $stuser->id);
 294          }
 295          $this->assertEquals('cook', $statement->get_verb_id());
 296          $this->assertEquals('paella', $statement->get_activity_id());
 297  
 298          // Check resulting json (only first node structure, internal structure
 299          // depends on every item json_encode test).
 300          $data = json_decode(json_encode($statement));
 301          $this->assertNotEmpty($data->actor);
 302          $this->assertNotEmpty($data->verb);
 303          $this->assertNotEmpty($data->object);
 304          $allextras = ['context', 'result', 'timestamp', 'stored', 'authority', 'version', 'attachments'];
 305          $alldefined = array_merge($extras, $extravalues);
 306          foreach ($allextras as $extra) {
 307              if (in_array($extra, $alldefined)) {
 308                  $this->assertObjectHasAttribute($extra, $data);
 309                  $this->assertNotEmpty($data->object);
 310              } else {
 311                  $this->assertObjectNotHasAttribute($extra, $data);
 312              }
 313          }
 314      }
 315  
 316      /**
 317       * Test adding attachments to statement.
 318       *
 319       */
 320      public function test_add_attachment() {
 321  
 322          // Our statement.
 323          $statement = new statement();
 324  
 325          $attachments = $statement->get_attachments();
 326          $this->assertNull($attachments);
 327  
 328          $item = $this->get_valid_item('attachment');
 329          $itemdata = $item->get_data();
 330          $statement->add_attachment($item);
 331  
 332          $attachments = $statement->get_attachments();
 333          $this->assertNotNull($attachments);
 334          $this->assertCount(1, $attachments);
 335  
 336          $attachment = current($attachments);
 337          $attachmentdata = $attachment->get_data();
 338          $this->assertEquals($itemdata->usageType, $attachmentdata->usageType);
 339          $this->assertEquals($itemdata->length, $attachmentdata->length);
 340  
 341          // Check resulting json.
 342          $statementdata = json_decode(json_encode($statement));
 343          $this->assertObjectHasAttribute('attachments', $statementdata);
 344          $this->assertNotEmpty($statementdata->attachments);
 345          $this->assertCount(1, $statementdata->attachments);
 346      }
 347  
 348      /**
 349       * Test adding attachments to statement.
 350       *
 351       */
 352      public function test_add_attachment_from_data() {
 353  
 354          $this->resetAfterTest();
 355  
 356          $user = $this->getDataGenerator()->create_user();
 357          $this->setUser($user);
 358  
 359          $actor = item_agent::create_from_user($user);
 360          $verb = item_verb::create_from_id('cook');
 361          $object = item_activity::create_from_id('paella');
 362  
 363          $data = (object) [
 364              'actor' => $actor->get_data(),
 365              'verb' => $verb->get_data(),
 366              'object' => $object->get_data(),
 367          ];
 368  
 369          $item = $this->get_valid_item('attachment');
 370          $itemdata = $item->get_data();
 371          $data->attachments = [$itemdata];
 372  
 373          $statement = statement::create_from_data($data);
 374  
 375          $attachments = $statement->get_attachments();
 376          $this->assertNotNull($attachments);
 377          $this->assertCount(1, $attachments);
 378  
 379          $attachment = current($attachments);
 380          $attachmentdata = $attachment->get_data();
 381          $this->assertEquals($itemdata->usageType, $attachmentdata->usageType);
 382          $this->assertEquals($itemdata->length, $attachmentdata->length);
 383  
 384          $statementdata = json_decode(json_encode($statement));
 385          $this->assertObjectHasAttribute('attachments', $statementdata);
 386          $this->assertNotEmpty($statementdata->attachments);
 387          $this->assertCount(1, $statementdata->attachments);
 388  
 389          // Now try to send an invalid attachments.
 390          $this->expectException(xapi_exception::class);
 391          $data->attachments = 'Invalid data';
 392          $statement = statement::create_from_data($data);
 393      }
 394  
 395      /**
 396       * Test all getters into a not set statement.
 397       *
 398       * @dataProvider invalid_gets_provider
 399       * @param string $method the method to test
 400       * @param bool $exception if an exception is expected
 401       */
 402      public function test_invalid_gets(string $method, bool $exception) {
 403          $statement = new statement();
 404          if ($exception) {
 405              $this->expectException(xapi_exception::class);
 406          }
 407          $result = $statement->$method();
 408          $this->assertNull($result);
 409      }
 410  
 411      /**
 412       * Data provider for the text_invalid_gets.
 413       *
 414       * @return  array
 415       */
 416      public function invalid_gets_provider() : array {
 417          return [
 418              'Method get_user on empty statement' => ['get_user', true],
 419              'Method get_all_users on empty statement' => ['get_all_users', true],
 420              'Method get_group on empty statement' => ['get_group', true],
 421              'Method get_verb_id on empty statement' => ['get_verb_id', true],
 422              'Method get_activity_id on empty statement' => ['get_activity_id', true],
 423              'Method get_actor on empty statement' => ['get_actor', false],
 424              'Method get_verb on empty statement' => ['get_verb', false],
 425              'Method get_object on empty statement' => ['get_object', false],
 426              'Method get_context on empty statement' => ['get_context', false],
 427              'Method get_result on empty statement' => ['get_result', false],
 428              'Method get_timestamp on empty statement' => ['get_timestamp', false],
 429              'Method get_stored on empty statement' => ['get_stored', false],
 430              'Method get_authority on empty statement' => ['get_authority', false],
 431              'Method get_version on empty statement' => ['get_version', false],
 432              'Method get_attachments on empty statement' => ['get_attachments', false],
 433          ];
 434      }
 435  
 436      /**
 437       * Try to get a user from a group statement.
 438       */
 439      public function test_invalid_get_user() {
 440  
 441          $this->resetAfterTest();
 442  
 443          // Create one course with a group.
 444          $course = $this->getDataGenerator()->create_course();
 445          $user = $this->getDataGenerator()->create_user();
 446          $this->getDataGenerator()->enrol_user($user->id, $course->id);
 447          $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id));
 448          $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user->id));
 449  
 450          // Our statement.
 451          $statement = new statement();
 452  
 453          // Populate statement.
 454          $statement->set_actor(item_group::create_from_group($group));
 455          $statement->set_verb(item_verb::create_from_id('cook'));
 456          $statement->set_object(item_activity::create_from_id('paella'));
 457  
 458          $this->expectException(xapi_exception::class);
 459          $statement->get_user();
 460      }
 461  
 462      /**
 463       * Try to get a group from an agent statement.
 464       */
 465      public function test_invalid_get_group() {
 466          $this->resetAfterTest();
 467  
 468          $user = $this->getDataGenerator()->create_user();
 469  
 470          // Our statement.
 471          $statement = new statement();
 472  
 473          // Populate statement.
 474          $statement->set_actor(item_agent::create_from_user($user));
 475          $statement->set_verb(item_verb::create_from_id('cook'));
 476          $statement->set_object(item_activity::create_from_id('paella'));
 477  
 478          $this->expectException(xapi_exception::class);
 479          $statement->get_group();
 480      }
 481  
 482      /**
 483       * Try to get activity Id from a statement with agent object.
 484       */
 485      public function test_invalid_get_activity_id() {
 486          $this->resetAfterTest();
 487  
 488          $user = $this->getDataGenerator()->create_user();
 489  
 490          // Our statement.
 491          $statement = new statement();
 492  
 493          // Populate statement with and agent object.
 494          $statement->set_actor(item_agent::create_from_user($user));
 495          $statement->set_verb(item_verb::create_from_id('cook'));
 496          $statement->set_object(item_agent::create_from_user($user));
 497  
 498          $this->expectException(xapi_exception::class);
 499          $statement->get_activity_id();
 500      }
 501  
 502      /**
 503       * Test for invalid structures.
 504       *
 505       * @dataProvider invalid_data_provider
 506       * @param bool $useuser if use user into statement
 507       * @param bool $userverb if use verb into statement
 508       * @param bool $useobject if use object into statement
 509       */
 510      public function test_invalid_data(bool $useuser, bool $userverb, bool $useobject): void {
 511  
 512          $data = new stdClass();
 513  
 514          if ($useuser) {
 515              $this->resetAfterTest();
 516              $user = $this->getDataGenerator()->create_user();
 517              $data->actor = item_agent::create_from_user($user);
 518          }
 519          if ($userverb) {
 520              $data->verb = item_verb::create_from_id('cook');
 521          }
 522          if ($useobject) {
 523              $data->object = item_activity::create_from_id('paella');
 524          }
 525  
 526          $this->expectException(xapi_exception::class);
 527          $statement = statement::create_from_data($data);
 528      }
 529  
 530      /**
 531       * Data provider for the test_invalid_data tests.
 532       *
 533       * @return  array
 534       */
 535      public function invalid_data_provider() : array {
 536          return [
 537              'No actor, no verb, no object'  => [false, false, false],
 538              'No actor, verb, no object'     => [false, true, false],
 539              'No actor, no verb, object'     => [false, false, true],
 540              'No actor, verb, object'        => [false, true, true],
 541              'Actor, no verb, no object'     => [true, false, false],
 542              'Actor, verb, no object'        => [true, true, false],
 543              'Actor, no verb, object'        => [true, false, true],
 544          ];
 545      }
 546  
 547      /**
 548       * Test minify statement.
 549       */
 550      public function test_minify() {
 551  
 552          $this->resetAfterTest();
 553  
 554          $user = $this->getDataGenerator()->create_user();
 555  
 556          $this->setUser($user);
 557  
 558          // Our statement.
 559          $statement = new statement();
 560  
 561          // Populate statement.
 562          $statement->set_actor(item_agent::create_from_user($user));
 563          $statement->set_verb(item_verb::create_from_id('cook'));
 564          $statement->set_object(item_activity::create_from_id('paella'));
 565          $statement->set_result($this->get_valid_item('result'));
 566          $statement->set_context($this->get_valid_item('context'));
 567          $statement->set_authority($this->get_valid_item('authority'));
 568          $statement->add_attachment($this->get_valid_item('attachment'));
 569          $statement->set_version('Example');
 570          $statement->set_timestamp('Example');
 571          $statement->set_stored('Example');
 572  
 573          $min = $statement->minify();
 574  
 575          // Check calculated fields.
 576          $this->assertCount(6, $min);
 577          $this->assertArrayNotHasKey('actor', $min);
 578          $this->assertArrayHasKey('verb', $min);
 579          $this->assertArrayHasKey('object', $min);
 580          $this->assertArrayHasKey('context', $min);
 581          $this->assertArrayHasKey('result', $min);
 582          $this->assertArrayNotHasKey('timestamp', $min);
 583          $this->assertArrayNotHasKey('stored', $min);
 584          $this->assertArrayHasKey('authority', $min);
 585          $this->assertArrayNotHasKey('version', $min);
 586          $this->assertArrayHasKey('attachments', $min);
 587      }
 588  }