Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

/**
 * This file contains unit test related to xAPI library.
 *
 * @package    core_xapi
 * @copyright  2020 Ferran Recio
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */

namespace core_xapi\local;

use core_xapi\local\statement\item;
use core_xapi\local\statement\item_actor;
use core_xapi\local\statement\item_object;
use core_xapi\local\statement\item_activity;
use core_xapi\local\statement\item_verb;
use core_xapi\local\statement\item_agent;
use core_xapi\local\statement\item_group;
use core_xapi\local\statement\item_result;
use core_xapi\local\statement\item_attachment;
use core_xapi\local\statement\item_context;
use core_xapi\iri;
use core_xapi\xapi_exception;
use advanced_testcase;
use stdClass;

defined('MOODLE_INTERNAL') || die();

/**
 * Contains test cases for testing statement class.
 *
 * @package    core_xapi
 * @since      Moodle 3.9
 * @copyright  2020 Ferran Recio
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
< class statement_testcase extends advanced_testcase {
> class statement_test extends advanced_testcase {
/** * Returns a valid item for a specific attribute. * * @param string $itemname statement item name * @return item the resulting item */ private function get_valid_item(string $itemname): item { global $USER, $CFG; switch ($itemname) { case 'attachments': case 'attachment': $data = (object) [ 'usageType' => iri::generate('example', 'attachment'), 'display' => (object) [ 'en-US' => 'Example', ], 'description' => (object) [ 'en-US' => 'Description example', ], "contentType" => "image/jpg", "length" => 1234, "sha2" => "b94c0f1cffb77475c6f1899111a0181efe1d6177" ]; return item_attachment::create_from_data($data); case 'authority': $data = (object) [ 'objectType' => 'Agent', 'account' => (object) [ 'homePage' => $CFG->wwwroot, 'name' => $USER->id, ], ]; return item_agent::create_from_data($data); } // For now, the rest of the optional properties have no validation // so we create a standard stdClass for all of them. $data = (object)[ 'some' => 'data', ]; $classname = 'core_xapi\local\statement\item_'.$itemname; if (class_exists($classname)) { $item = $classname::create_from_data($data); } else { $item = item::create_from_data($data); } return $item; } /** * Test statement creation. * * @dataProvider create_provider * @param bool $useagent if use agent as actor (or group if false) * @param array $extras extra item elements * @param array $extravalues extra string values */ public function test_create(bool $useagent, array $extras, array $extravalues) { $this->resetAfterTest(); // Create one course with a group. $course = $this->getDataGenerator()->create_course(); $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user->id)); $this->setUser($user); // Our statement. $statement = new statement(); // Populate statement. if ($useagent) { $statement->set_actor(item_agent::create_from_user($user)); } else { $statement->set_actor(item_group::create_from_group($group)); } $statement->set_verb(item_verb::create_from_id('cook')); $statement->set_object(item_activity::create_from_id('paella')); foreach ($extras as $extra) { $method = 'set_'.$extra; $item = $this->get_valid_item($extra); $statement->$method($item); } // For now extra values have no validation. foreach ($extravalues as $extra) { $method = 'set_'.$extra; $statement->$method('Example'); } // Check resulting statement. if ($useagent) { $stuser = $statement->get_user(); $this->assertEquals($user->id, $stuser->id); $stusers = $statement->get_all_users(); $this->assertCount(1, $stusers); } else { $stgroup = $statement->get_group(); $this->assertEquals($group->id, $stgroup->id); $stusers = $statement->get_all_users(); $this->assertCount(1, $stusers); $stuser = array_shift($stusers); $this->assertEquals($user->id, $stuser->id); } $this->assertEquals('cook', $statement->get_verb_id()); $this->assertEquals('paella', $statement->get_activity_id()); // Check resulting json (only first node structure, internal structure // depends on every item json_encode test). $data = json_decode(json_encode($statement)); $this->assertNotEmpty($data->actor); $this->assertNotEmpty($data->verb); $this->assertNotEmpty($data->object); $allextras = ['context', 'result', 'timestamp', 'stored', 'authority', 'version', 'attachments']; $alldefined = array_merge($extras, $extravalues); foreach ($allextras as $extra) { if (in_array($extra, $alldefined)) { $this->assertObjectHasAttribute($extra, $data); $this->assertNotEmpty($data->$extra); } else { $this->assertObjectNotHasAttribute($extra, $data); } } } /** * Data provider for the test_create and test_create_from_data tests. * * @return array */ public function create_provider() : array { return [ 'Agent statement with no extras' => [ true, [], [] ], 'Agent statement with context' => [ true, ['context'], [] ], 'Agent statement with result' => [ true, ['result'], [] ], 'Agent statement with timestamp' => [ true, [], ['timestamp'] ], 'Agent statement with stored' => [ true, [], ['stored'] ], 'Agent statement with authority' => [ true, ['authority'], [] ], 'Agent statement with version' => [ true, [], ['version'] ], 'Group statement with no extras' => [ false, [], [] ], 'Group statement with context' => [ false, ['context'], [] ], 'Group statement with result' => [ false, ['result'], [] ], 'Group statement with timestamp' => [ false, [], ['timestamp'] ], 'Group statement with stored' => [ false, [], ['stored'] ], 'Group statement with authority' => [ false, ['authority'], [] ], 'Group statement with version' => [ false, [], ['version'] ], ]; } /** * Test statement creation from xAPI statement data. * * @dataProvider create_provider * @param bool $useagent if use agent as actor (or group if false) * @param array $extras extra item elements * @param array $extravalues extra string values */ public function test_create_from_data(bool $useagent, array $extras, array $extravalues) { $this->resetAfterTest(); // Create one course with a group. $course = $this->getDataGenerator()->create_course(); $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user->id)); $this->setUser($user); // Populate data. if ($useagent) { $actor = item_agent::create_from_user($user); } else { $actor = item_group::create_from_group($group); } $verb = item_verb::create_from_id('cook'); $object = item_activity::create_from_id('paella'); $data = (object) [ 'actor' => $actor->get_data(), 'verb' => $verb->get_data(), 'object' => $object->get_data(), ]; foreach ($extras as $extra) { $item = $this->get_valid_item($extra); $data->$extra = $item->get_data(); } // For now extra values have no validation. foreach ($extravalues as $extra) { $data->$extra = 'Example'; } $statement = statement::create_from_data($data); // Check resulting statement. if ($useagent) { $stuser = $statement->get_user(); $this->assertEquals($user->id, $stuser->id); $stusers = $statement->get_all_users(); $this->assertCount(1, $stusers); } else { $stgroup = $statement->get_group(); $this->assertEquals($group->id, $stgroup->id); $stusers = $statement->get_all_users(); $this->assertCount(1, $stusers); $stuser = array_shift($stusers); $this->assertEquals($user->id, $stuser->id); } $this->assertEquals('cook', $statement->get_verb_id()); $this->assertEquals('paella', $statement->get_activity_id()); // Check resulting json (only first node structure, internal structure // depends on every item json_encode test). $data = json_decode(json_encode($statement)); $this->assertNotEmpty($data->actor); $this->assertNotEmpty($data->verb); $this->assertNotEmpty($data->object); $allextras = ['context', 'result', 'timestamp', 'stored', 'authority', 'version', 'attachments']; $alldefined = array_merge($extras, $extravalues); foreach ($allextras as $extra) { if (in_array($extra, $alldefined)) { $this->assertObjectHasAttribute($extra, $data); $this->assertNotEmpty($data->object); } else { $this->assertObjectNotHasAttribute($extra, $data); } } } /** * Test adding attachments to statement. * */ public function test_add_attachment() { // Our statement. $statement = new statement(); $attachments = $statement->get_attachments(); $this->assertNull($attachments); $item = $this->get_valid_item('attachment'); $itemdata = $item->get_data(); $statement->add_attachment($item); $attachments = $statement->get_attachments(); $this->assertNotNull($attachments); $this->assertCount(1, $attachments); $attachment = current($attachments); $attachmentdata = $attachment->get_data(); $this->assertEquals($itemdata->usageType, $attachmentdata->usageType); $this->assertEquals($itemdata->length, $attachmentdata->length); // Check resulting json. $statementdata = json_decode(json_encode($statement)); $this->assertObjectHasAttribute('attachments', $statementdata); $this->assertNotEmpty($statementdata->attachments); $this->assertCount(1, $statementdata->attachments); } /** * Test adding attachments to statement. * */ public function test_add_attachment_from_data() { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); $actor = item_agent::create_from_user($user); $verb = item_verb::create_from_id('cook'); $object = item_activity::create_from_id('paella'); $data = (object) [ 'actor' => $actor->get_data(), 'verb' => $verb->get_data(), 'object' => $object->get_data(), ]; $item = $this->get_valid_item('attachment'); $itemdata = $item->get_data(); $data->attachments = [$itemdata]; $statement = statement::create_from_data($data); $attachments = $statement->get_attachments(); $this->assertNotNull($attachments); $this->assertCount(1, $attachments); $attachment = current($attachments); $attachmentdata = $attachment->get_data(); $this->assertEquals($itemdata->usageType, $attachmentdata->usageType); $this->assertEquals($itemdata->length, $attachmentdata->length); $statementdata = json_decode(json_encode($statement)); $this->assertObjectHasAttribute('attachments', $statementdata); $this->assertNotEmpty($statementdata->attachments); $this->assertCount(1, $statementdata->attachments); // Now try to send an invalid attachments. $this->expectException(xapi_exception::class); $data->attachments = 'Invalid data'; $statement = statement::create_from_data($data); } /** * Test all getters into a not set statement. * * @dataProvider invalid_gets_provider * @param string $method the method to test * @param bool $exception if an exception is expected */ public function test_invalid_gets(string $method, bool $exception) { $statement = new statement(); if ($exception) { $this->expectException(xapi_exception::class); } $result = $statement->$method(); $this->assertNull($result); } /** * Data provider for the text_invalid_gets. * * @return array */ public function invalid_gets_provider() : array { return [ 'Method get_user on empty statement' => ['get_user', true], 'Method get_all_users on empty statement' => ['get_all_users', true], 'Method get_group on empty statement' => ['get_group', true], 'Method get_verb_id on empty statement' => ['get_verb_id', true], 'Method get_activity_id on empty statement' => ['get_activity_id', true], 'Method get_actor on empty statement' => ['get_actor', false], 'Method get_verb on empty statement' => ['get_verb', false], 'Method get_object on empty statement' => ['get_object', false], 'Method get_context on empty statement' => ['get_context', false], 'Method get_result on empty statement' => ['get_result', false], 'Method get_timestamp on empty statement' => ['get_timestamp', false], 'Method get_stored on empty statement' => ['get_stored', false], 'Method get_authority on empty statement' => ['get_authority', false], 'Method get_version on empty statement' => ['get_version', false], 'Method get_attachments on empty statement' => ['get_attachments', false], ]; } /** * Try to get a user from a group statement. */ public function test_invalid_get_user() { $this->resetAfterTest(); // Create one course with a group. $course = $this->getDataGenerator()->create_course(); $user = $this->getDataGenerator()->create_user(); $this->getDataGenerator()->enrol_user($user->id, $course->id); $group = $this->getDataGenerator()->create_group(array('courseid' => $course->id)); $this->getDataGenerator()->create_group_member(array('groupid' => $group->id, 'userid' => $user->id)); // Our statement. $statement = new statement(); // Populate statement. $statement->set_actor(item_group::create_from_group($group)); $statement->set_verb(item_verb::create_from_id('cook')); $statement->set_object(item_activity::create_from_id('paella')); $this->expectException(xapi_exception::class); $statement->get_user(); } /** * Try to get a group from an agent statement. */ public function test_invalid_get_group() { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); // Our statement. $statement = new statement(); // Populate statement. $statement->set_actor(item_agent::create_from_user($user)); $statement->set_verb(item_verb::create_from_id('cook')); $statement->set_object(item_activity::create_from_id('paella')); $this->expectException(xapi_exception::class); $statement->get_group(); } /** * Try to get activity Id from a statement with agent object. */ public function test_invalid_get_activity_id() { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); // Our statement. $statement = new statement(); // Populate statement with and agent object. $statement->set_actor(item_agent::create_from_user($user)); $statement->set_verb(item_verb::create_from_id('cook')); $statement->set_object(item_agent::create_from_user($user)); $this->expectException(xapi_exception::class); $statement->get_activity_id(); } /** * Test for invalid structures. * * @dataProvider invalid_data_provider * @param bool $useuser if use user into statement * @param bool $userverb if use verb into statement * @param bool $useobject if use object into statement */ public function test_invalid_data(bool $useuser, bool $userverb, bool $useobject): void { $data = new stdClass(); if ($useuser) { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $data->actor = item_agent::create_from_user($user); } if ($userverb) { $data->verb = item_verb::create_from_id('cook'); } if ($useobject) { $data->object = item_activity::create_from_id('paella'); } $this->expectException(xapi_exception::class); $statement = statement::create_from_data($data); } /** * Data provider for the test_invalid_data tests. * * @return array */ public function invalid_data_provider() : array { return [ 'No actor, no verb, no object' => [false, false, false], 'No actor, verb, no object' => [false, true, false], 'No actor, no verb, object' => [false, false, true], 'No actor, verb, object' => [false, true, true], 'Actor, no verb, no object' => [true, false, false], 'Actor, verb, no object' => [true, true, false], 'Actor, no verb, object' => [true, false, true], ]; } /** * Test minify statement. */ public function test_minify() { $this->resetAfterTest(); $user = $this->getDataGenerator()->create_user(); $this->setUser($user); // Our statement. $statement = new statement(); // Populate statement. $statement->set_actor(item_agent::create_from_user($user)); $statement->set_verb(item_verb::create_from_id('cook')); $statement->set_object(item_activity::create_from_id('paella')); $statement->set_result($this->get_valid_item('result')); $statement->set_context($this->get_valid_item('context')); $statement->set_authority($this->get_valid_item('authority')); $statement->add_attachment($this->get_valid_item('attachment')); $statement->set_version('Example'); $statement->set_timestamp('Example'); $statement->set_stored('Example'); $min = $statement->minify(); // Check calculated fields. $this->assertCount(6, $min); $this->assertArrayNotHasKey('actor', $min); $this->assertArrayHasKey('verb', $min); $this->assertArrayHasKey('object', $min); $this->assertArrayHasKey('context', $min); $this->assertArrayHasKey('result', $min); $this->assertArrayNotHasKey('timestamp', $min); $this->assertArrayNotHasKey('stored', $min); $this->assertArrayHasKey('authority', $min); $this->assertArrayNotHasKey('version', $min); $this->assertArrayHasKey('attachments', $min); } }