first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+474
View File
@@ -0,0 +1,474 @@
<?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/>.
namespace core_external;
/**
* Unit tests for core_external\external_api.
*
* @package core_external
* @category test
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @covers \core_external\external_api
*/
class external_api_test extends \advanced_testcase {
/**
* Test the validate_parameters method.
*
* @covers \core_external\external_api::validate_parameters
*/
public function test_validate_params(): void {
$params = ['text' => 'aaa', 'someid' => '6'];
$description = new external_function_parameters([
'someid' => new external_value(PARAM_INT, 'Some int value'),
'text' => new external_value(PARAM_ALPHA, 'Some text value'),
]);
$result = external_api::validate_parameters($description, $params);
$this->assertCount(2, $result);
reset($result);
$this->assertSame('someid', key($result));
$this->assertSame(6, $result['someid']);
$this->assertSame('aaa', $result['text']);
$params = [
'someids' => ['1', 2, 'a' => '3'],
'scalar' => 666,
];
$description = new external_function_parameters([
'someids' => new external_multiple_structure(new external_value(PARAM_INT, 'Some ID')),
'scalar' => new external_value(PARAM_ALPHANUM, 'Some text value'),
]);
$result = external_api::validate_parameters($description, $params);
$this->assertCount(2, $result);
reset($result);
$this->assertSame('someids', key($result));
$this->assertEquals([0 => 1, 1 => 2, 2 => 3], $result['someids']);
$this->assertSame('666', $result['scalar']);
$params = ['text' => 'aaa'];
$description = new external_function_parameters([
'someid' => new external_value(PARAM_INT, 'Some int value', VALUE_DEFAULT),
'text' => new external_value(PARAM_ALPHA, 'Some text value'),
]);
$result = external_api::validate_parameters($description, $params);
$this->assertCount(2, $result);
reset($result);
$this->assertSame('someid', key($result));
$this->assertNull($result['someid']);
$this->assertSame('aaa', $result['text']);
$params = ['text' => 'aaa'];
$description = new external_function_parameters([
'someid' => new external_value(PARAM_INT, 'Some int value', VALUE_DEFAULT, 6),
'text' => new external_value(PARAM_ALPHA, 'Some text value'),
]);
$result = external_api::validate_parameters($description, $params);
$this->assertCount(2, $result);
reset($result);
$this->assertSame('someid', key($result));
$this->assertSame(6, $result['someid']);
$this->assertSame('aaa', $result['text']);
// Missing required value (an exception is thrown).
$testdata = [];
try {
external_api::clean_returnvalue($description, $testdata);
$this->fail('Exception expected');
} catch (\moodle_exception $ex) {
$this->assertInstanceOf(\invalid_response_exception::class, $ex);
$this->assertSame('Invalid response value detected (Error in response - '
. 'Missing following required key in a single structure: text)', $ex->getMessage());
}
// Test nullable external_value may optionally return data.
$description = new external_function_parameters([
'value' => new external_value(PARAM_INT, '', VALUE_REQUIRED, null, NULL_ALLOWED)
]);
$testdata = ['value' => null];
$cleanedvalue = external_api::clean_returnvalue($description, $testdata);
$this->assertSame($testdata, $cleanedvalue);
$testdata = ['value' => 1];
$cleanedvalue = external_api::clean_returnvalue($description, $testdata);
$this->assertSame($testdata, $cleanedvalue);
// Test nullable external_single_structure may optionally return data.
$description = new external_function_parameters([
'value' => new external_single_structure(['value2' => new external_value(PARAM_INT)],
'', VALUE_REQUIRED, null, NULL_ALLOWED)
]);
$testdata = ['value' => null];
$cleanedvalue = external_api::clean_returnvalue($description, $testdata);
$this->assertSame($testdata, $cleanedvalue);
$testdata = ['value' => ['value2' => 1]];
$cleanedvalue = external_api::clean_returnvalue($description, $testdata);
$this->assertSame($testdata, $cleanedvalue);
// Test nullable external_multiple_structure may optionally return data.
$description = new external_function_parameters([
'value' => new external_multiple_structure(
new external_value(PARAM_INT), '', VALUE_REQUIRED, null, NULL_ALLOWED)
]);
$testdata = ['value' => null];
$cleanedvalue = external_api::clean_returnvalue($description, $testdata);
$this->assertSame($testdata, $cleanedvalue);
$testdata = ['value' => [1]];
$cleanedvalue = external_api::clean_returnvalue($description, $testdata);
$this->assertSame($testdata, $cleanedvalue);
}
/**
* Test for clean_returnvalue() for testing that returns the PHP type.
*
* @covers \core_external\external_api::clean_returnvalue
*/
public function test_clean_returnvalue_return_php_type(): void {
$returndesc = new external_single_structure([
'value' => new external_value(PARAM_RAW, 'Some text', VALUE_OPTIONAL, null, NULL_NOT_ALLOWED),
]);
// Check return type on exception because the external values does not allow NULL values.
$testdata = ['value' => null];
try {
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
} catch (\moodle_exception $e) {
$this->assertInstanceOf(\invalid_response_exception::class, $e);
$this->assertStringContainsString('of PHP type "NULL"', $e->debuginfo);
}
}
/**
* Test for clean_returnvalue().
*
* @covers \core_external\external_api::clean_returnvalue
*/
public function test_clean_returnvalue(): void {
// Build some return value decription.
$returndesc = new external_multiple_structure(
new external_single_structure(
[
'object' => new external_single_structure(
['value1' => new external_value(PARAM_INT, 'this is a int')]),
'value2' => new external_value(PARAM_TEXT, 'some text', VALUE_OPTIONAL),
]
));
// Clean an object (it should be cast into an array).
$object = new \stdClass();
$object->value1 = 1;
$singlestructure['object'] = $object;
$singlestructure['value2'] = 'Some text';
$testdata = [$singlestructure];
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$cleanedsinglestructure = array_pop($cleanedvalue);
$this->assertSame($object->value1, $cleanedsinglestructure['object']['value1']);
$this->assertSame($singlestructure['value2'], $cleanedsinglestructure['value2']);
// Missing VALUE_OPTIONAL.
$object = new \stdClass();
$object->value1 = 1;
$singlestructure = new \stdClass();
$singlestructure->object = $object;
$testdata = [$singlestructure];
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$cleanedsinglestructure = array_pop($cleanedvalue);
$this->assertSame($object->value1, $cleanedsinglestructure['object']['value1']);
$this->assertArrayNotHasKey('value2', $cleanedsinglestructure);
// Unknown attribute (the value should be ignored).
$object = [];
$object['value1'] = 1;
$singlestructure = [];
$singlestructure['object'] = $object;
$singlestructure['value2'] = 'Some text';
$singlestructure['unknownvalue'] = 'Some text to ignore';
$testdata = [$singlestructure];
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$cleanedsinglestructure = array_pop($cleanedvalue);
$this->assertSame($object['value1'], $cleanedsinglestructure['object']['value1']);
$this->assertSame($singlestructure['value2'], $cleanedsinglestructure['value2']);
$this->assertArrayNotHasKey('unknownvalue', $cleanedsinglestructure);
// Missing required value (an exception is thrown).
$object = [];
$singlestructure = [];
$singlestructure['object'] = $object;
$singlestructure['value2'] = 'Some text';
$testdata = [$singlestructure];
try {
external_api::clean_returnvalue($returndesc, $testdata);
$this->fail('Exception expected');
} catch (\moodle_exception $ex) {
$this->assertInstanceOf(\invalid_response_exception::class, $ex);
$this->assertSame('Invalid response value detected (object => Invalid response value detected '
. '(Error in response - Missing following required key in a single structure: value1): Error in response - '
. 'Missing following required key in a single structure: value1)', $ex->getMessage());
}
// Fail if no data provided when value required.
$testdata = null;
try {
external_api::clean_returnvalue($returndesc, $testdata);
$this->fail('Exception expected');
} catch (\moodle_exception $ex) {
$this->assertInstanceOf(\invalid_response_exception::class, $ex);
$this->assertSame('Invalid response value detected (Only arrays accepted. The bad value is: \'\')',
$ex->getMessage());
}
// Test nullable external_multiple_structure may optionally return data.
$returndesc = new external_multiple_structure(
new external_value(PARAM_INT),
'', VALUE_REQUIRED, null, NULL_ALLOWED);
$testdata = null;
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$this->assertSame($testdata, $cleanedvalue);
$testdata = [1];
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$this->assertSame($testdata, $cleanedvalue);
// Test nullable external_single_structure may optionally return data.
$returndesc = new external_single_structure(['value' => new external_value(PARAM_INT)],
'', VALUE_REQUIRED, null, NULL_ALLOWED);
$testdata = null;
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$this->assertSame($testdata, $cleanedvalue);
$testdata = ['value' => 1];
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$this->assertSame($testdata, $cleanedvalue);
// Test nullable external_value may optionally return data.
$returndesc = new external_value(PARAM_INT, '', VALUE_REQUIRED, null, NULL_ALLOWED);
$testdata = null;
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$this->assertSame($testdata, $cleanedvalue);
$testdata = 1;
$cleanedvalue = external_api::clean_returnvalue($returndesc, $testdata);
$this->assertSame($testdata, $cleanedvalue);
}
/**
* Test \core_external\external_api::get_context_from_params().
*
* @covers \core_external\external_api::get_context_from_params
*/
public function test_get_context_from_params(): void {
$this->resetAfterTest(true);
$course = $this->getDataGenerator()->create_course();
$realcontext = \context_course::instance($course->id);
// Use context id.
$fetchedcontext = $this->get_context_from_params(["contextid" => $realcontext->id]);
$this->assertEquals($realcontext, $fetchedcontext);
// Use context level and instance id.
$fetchedcontext = $this->get_context_from_params(["contextlevel" => "course", "instanceid" => $course->id]);
$this->assertEquals($realcontext, $fetchedcontext);
// Use context level numbers instead of legacy short level names.
$fetchedcontext = $this->get_context_from_params(
["contextlevel" => \core\context\course::LEVEL, "instanceid" => $course->id]);
$this->assertEquals($realcontext, $fetchedcontext);
// Passing empty values.
try {
$fetchedcontext = $this->get_context_from_params(["contextid" => 0]);
$this->fail('Exception expected from get_context_wrapper()');
} catch (\moodle_exception $e) {
$this->assertInstanceOf(\invalid_parameter_exception::class, $e);
}
try {
$fetchedcontext = $this->get_context_from_params(["instanceid" => 0]);
$this->fail('Exception expected from get_context_wrapper()');
} catch (\moodle_exception $e) {
$this->assertInstanceOf(\invalid_parameter_exception::class, $e);
}
try {
$fetchedcontext = $this->get_context_from_params(["contextid" => null]);
$this->fail('Exception expected from get_context_wrapper()');
} catch (\moodle_exception $e) {
$this->assertInstanceOf(\invalid_parameter_exception::class, $e);
}
// Tests for context with instanceid equal to 0 (System context).
$realcontext = \context_system::instance();
$fetchedcontext = $this->get_context_from_params(["contextlevel" => "system", "instanceid" => 0]);
$this->assertEquals($realcontext, $fetchedcontext);
// Passing wrong level name.
try {
$fetchedcontext = $this->get_context_from_params(["contextlevel" => "random", "instanceid" => $course->id]);
$this->fail('exception expected when level name is invalid');
} catch (\moodle_exception $e) {
$this->assertInstanceOf('invalid_parameter_exception', $e);
$this->assertSame('Invalid parameter value detected (Invalid context level = random)', $e->getMessage());
}
// Passing wrong level number.
try {
$fetchedcontext = $this->get_context_from_params(["contextlevel" => -10, "instanceid" => $course->id]);
$this->fail('exception expected when level name is invalid');
} catch (\moodle_exception $e) {
$this->assertInstanceOf('invalid_parameter_exception', $e);
$this->assertSame('Invalid parameter value detected (Invalid context level = -10)', $e->getMessage());
}
}
/**
* Test \core_external\external_api::get_context()_from_params parameter validation.
*
* @covers \core_external\external_api::get_context
*/
public function test_get_context_params(): void {
global $USER;
// Call without correct context details.
$this->expectException('invalid_parameter_exception');
$this->get_context_from_params(['roleid' => 3, 'userid' => $USER->id]);
}
/**
* Test \core_external\external_api::get_context()_from_params parameter validation.
*
* @covers \core_external\external_api::get_context
*/
public function test_get_context_params2(): void {
global $USER;
// Call without correct context details.
$this->expectException('invalid_parameter_exception');
$this->get_context_from_params(['roleid' => 3, 'userid' => $USER->id, 'contextlevel' => "course"]);
}
/**
* Test \core_external\external_api::get_context()_from_params parameter validation.
* @covers \core_external\external_api::get_context
*/
public function test_get_context_params3(): void {
global $USER;
// Call without correct context details.
$this->resetAfterTest(true);
$course = self::getDataGenerator()->create_course();
$this->expectException('invalid_parameter_exception');
$this->get_context_from_params(['roleid' => 3, 'userid' => $USER->id, 'instanceid' => $course->id]);
}
/**
* Data provider for the test_all_external_info test.
*
* @return array
*/
public function all_external_info_provider(): array {
global $DB;
// We are testing here that all the external function descriptions can be generated without
// producing warnings. E.g. misusing optional params will generate a debugging message which
// will fail this test.
$functions = $DB->get_records('external_functions', [], 'name');
$return = [];
foreach ($functions as $f) {
$return[$f->name] = [$f];
}
return $return;
}
/**
* Test \core_external\external_api::external_function_info.
*
* @runInSeparateProcess
* @dataProvider all_external_info_provider
* @covers \core_external\external_api::external_function_info
* @param \stdClass $definition
*/
public function test_all_external_info(\stdClass $definition): void {
$desc = external_api::external_function_info($definition);
$this->assertNotEmpty($desc->name);
$this->assertNotEmpty($desc->classname);
$this->assertNotEmpty($desc->methodname);
$this->assertEquals($desc->component, clean_param($desc->component, PARAM_COMPONENT));
$this->assertInstanceOf(external_function_parameters::class, $desc->parameters_desc);
if ($desc->returns_desc != null) {
$this->assertInstanceOf(external_description::class, $desc->returns_desc);
}
}
/**
* Test the \core_external\external_api::call_external_function() function.
*
* @covers \core_external\external_api::call_external_function
*/
public function test_call_external_function(): void {
global $PAGE, $COURSE, $CFG;
$this->resetAfterTest(true);
// Call some webservice functions and verify they are correctly handling $PAGE and $COURSE.
// First test a function that calls validate_context outside a course.
$this->setAdminUser();
$category = $this->getDataGenerator()->create_category();
$params = [
'contextid' => \context_coursecat::instance($category->id)->id,
'name' => 'aaagrrryyy',
'idnumber' => '',
'description' => '',
];
$cohort1 = $this->getDataGenerator()->create_cohort($params);
$cohort2 = $this->getDataGenerator()->create_cohort();
$beforepage = $PAGE;
$beforecourse = $COURSE;
$params = ['cohortids' => [$cohort1->id, $cohort2->id]];
$result = external_api::call_external_function('core_cohort_get_cohorts', $params);
$this->assertSame($beforepage, $PAGE);
$this->assertSame($beforecourse, $COURSE);
// Now test a function that calls validate_context inside a course.
$course = $this->getDataGenerator()->create_course();
$beforepage = $PAGE;
$beforecourse = $COURSE;
$params = ['courseid' => $course->id, 'options' => []];
$result = external_api::call_external_function('core_enrol_get_enrolled_users', $params);
$this->assertSame($beforepage, $PAGE);
$this->assertSame($beforecourse, $COURSE);
// Test a function that triggers a PHP exception.
require_once($CFG->dirroot . '/lib/tests/fixtures/test_external_function_throwable.php');
// Call our test function.
$result = \test_external_function_throwable::call_external_function('core_throw_exception', [], false);
$this->assertTrue($result['error']);
$this->assertArrayHasKey('exception', $result);
$this->assertEquals($result['exception']->message, 'Exception - Modulo by zero');
}
/**
* Call the get_contect_from_params methods on the api class.
*
* @return mixed
*/
protected function get_context_from_params() {
$rc = new \ReflectionClass(external_api::class);
$method = $rc->getMethod('get_context_from_params');
return $method->invokeArgs(null, func_get_args());
}
}
+280
View File
@@ -0,0 +1,280 @@
<?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/>.
namespace core;
use core_external\external_api;
use externallib_advanced_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/lib/external/externallib.php');
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* External library functions unit tests
*
* @package core
* @category phpunit
* @copyright 2012 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class external_externallib_test extends externallib_advanced_testcase {
/**
* Test get_string
*/
public function test_get_string(): void {
$this->resetAfterTest(true);
$service = new \stdClass();
$service->name = 'Dummy Service';
$service->id = 12;
// String with two parameters.
$returnedstring = \core_external::get_string('addservice', 'webservice', null,
array(array('name' => 'name', 'value' => $service->name),
array('name' => 'id', 'value' => $service->id)));
// We need to execute the return values cleaning process to simulate the web service server.
$returnedstring = external_api::clean_returnvalue(\core_external::get_string_returns(), $returnedstring);
$corestring = get_string('addservice', 'webservice', $service);
$this->assertSame($corestring, $returnedstring);
// String with one parameter.
$acapname = 'A capability name';
$returnedstring = \core_external::get_string('missingrequiredcapability', 'webservice', null,
array(array('value' => $acapname)));
// We need to execute the return values cleaning process to simulate the web service server.
$returnedstring = external_api::clean_returnvalue(\core_external::get_string_returns(), $returnedstring);
$corestring = get_string('missingrequiredcapability', 'webservice', $acapname);
$this->assertSame($corestring, $returnedstring);
// String without parameters.
$returnedstring = \core_external::get_string('missingpassword', 'webservice');
// We need to execute the return values cleaning process to simulate the web service server.
$returnedstring = external_api::clean_returnvalue(\core_external::get_string_returns(), $returnedstring);
$corestring = get_string('missingpassword', 'webservice');
$this->assertSame($corestring, $returnedstring);
// String with two parameter but one is invalid (not named).
$this->expectException('moodle_exception');
$returnedstring = \core_external::get_string('addservice', 'webservice', null,
array(array('value' => $service->name),
array('name' => 'id', 'value' => $service->id)));
}
/**
* Test get_string with HTML.
*/
public function test_get_string_containing_html(): void {
$result = \core_external::get_string('registrationinfo');
$actual = external_api::clean_returnvalue(\core_external::get_string_returns(), $result);
$expected = get_string('registrationinfo', 'moodle');
$this->assertSame($expected, $actual);
}
/**
* Test get_string with arguments containing HTML.
*/
public function test_get_string_with_args_containing_html(): void {
$result = \core_external::get_string('added', 'moodle', null, [['value' => '<strong>Test</strong>']]);
$actual = external_api::clean_returnvalue(\core_external::get_string_returns(), $result);
$expected = get_string('added', 'moodle', '<strong>Test</strong>');
$this->assertSame($expected, $actual);
}
/**
* Test get_strings
*/
public function test_get_strings(): void {
$this->resetAfterTest(true);
$stringmanager = get_string_manager();
$service = new \stdClass();
$service->name = 'Dummy Service';
$service->id = 12;
$returnedstrings = \core_external::get_strings(
array(
array(
'stringid' => 'addservice', 'component' => 'webservice',
'stringparams' => array(array('name' => 'name', 'value' => $service->name),
array('name' => 'id', 'value' => $service->id)
),
'lang' => 'en'
),
array('stringid' => 'addaservice', 'component' => 'webservice', 'lang' => 'en')
));
// We need to execute the return values cleaning process to simulate the web service server.
$returnedstrings = external_api::clean_returnvalue(\core_external::get_strings_returns(), $returnedstrings);
foreach($returnedstrings as $returnedstring) {
$corestring = $stringmanager->get_string($returnedstring['stringid'],
$returnedstring['component'],
$service,
'en');
$this->assertSame($corestring, $returnedstring['string']);
}
}
/**
* Test get_strings with HTML.
*/
public function test_get_strings_containing_html(): void {
$result = \core_external::get_strings([['stringid' => 'registrationinfo'], ['stringid' => 'loginaspasswordexplain']]);
$actual = external_api::clean_returnvalue(\core_external::get_strings_returns(), $result);
$this->assertSame(get_string('registrationinfo', 'moodle'), $actual[0]['string']);
$this->assertSame(get_string('loginaspasswordexplain', 'moodle'), $actual[1]['string']);
}
/**
* Test get_strings with arguments containing HTML.
*/
public function test_get_strings_with_args_containing_html(): void {
$result = \core_external::get_strings([
['stringid' => 'added', 'stringparams' => [['value' => '<strong>Test</strong>']]],
['stringid' => 'loggedinas', 'stringparams' => [['value' => '<strong>Test</strong>']]]]
);
$actual = external_api::clean_returnvalue(\core_external::get_strings_returns(), $result);
$this->assertSame(get_string('added', 'moodle', '<strong>Test</strong>'), $actual[0]['string']);
$this->assertSame(get_string('loggedinas', 'moodle', '<strong>Test</strong>'), $actual[1]['string']);
}
/**
* Test get_component_strings
*/
public function test_get_component_strings(): void {
global $USER;
$this->resetAfterTest(true);
$stringmanager = get_string_manager();
$wsstrings = $stringmanager->load_component_strings('webservice', current_language());
$componentstrings = \core_external::get_component_strings('webservice');
// We need to execute the return values cleaning process to simulate the web service server.
$componentstrings = external_api::clean_returnvalue(\core_external::get_component_strings_returns(), $componentstrings);
$this->assertEquals(count($componentstrings), count($wsstrings));
foreach($componentstrings as $string) {
$this->assertSame($string['string'], $wsstrings[$string['stringid']]);
}
}
/**
* Test update_inplace_editable()
*/
public function test_update_inplace_editable(): void {
$this->resetAfterTest(true);
// Call service for component that does not have inplace_editable callback.
try {
\core_external::update_inplace_editable('tool_log', 'itemtype', 1, 'newvalue');
$this->fail('Exception expected');
} catch (\moodle_exception $e) {
$this->assertEquals('Error calling update processor', $e->getMessage());
}
// This is a very basic test for the return value of the external function.
// More detailed test for tag updating can be found in core_tag component.
$this->setAdminUser();
$tag = $this->getDataGenerator()->create_tag();
$res = \core_external::update_inplace_editable('core_tag', 'tagname', $tag->id, 'new tag name');
$res = external_api::clean_returnvalue(\core_external::update_inplace_editable_returns(), $res);
$this->assertEquals('new tag name', $res['value']);
}
/**
* Test update_inplace_editable with mathjax.
*/
public function test_update_inplace_editable_with_mathjax(): void {
$this->resetAfterTest(true);
$this->setAdminUser();
// Enable MathJax filter in content and headings.
$this->configure_filters([
['name' => 'mathjaxloader', 'state' => TEXTFILTER_ON, 'move' => -1, 'applytostrings' => true],
]);
// Create a forum.
$course = $this->getDataGenerator()->create_course();
$forum = self::getDataGenerator()->create_module('forum', array('course' => $course->id, 'name' => 'forum name'));
// Change the forum name.
$newname = 'New forum name $$(a+b)=2$$';
$res = \core_external::update_inplace_editable('core_course', 'activityname', $forum->cmid, $newname);
$res = external_api::clean_returnvalue(\core_external::update_inplace_editable_returns(), $res);
// Format original data.
$context = \context_module::instance($forum->cmid);
$newname = \core_external\util::format_string($newname, $context);
$editlabel = get_string('newactivityname', '', $newname);
// Check editlabel is the same and has mathjax.
$this->assertStringContainsString('<span class="filter_mathjaxloader_equation">', $res['editlabel']);
$this->assertEquals($editlabel, $res['editlabel']);
}
public function test_get_user_dates(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Set default timezone to Australia/Perth, else time calculated
// will not match expected values.
$this->setTimezone(99, 'Australia/Perth');
$context = \context_system::instance();
$request = [
[
'timestamp' => 1293876000,
'format' => '%A, %d %B %Y, %I:%M'
],
[
'timestamp' => 1293876000,
'format' => '%d %m %Y'
],
[
'timestamp' => 1293876000,
'format' => '%d %m %Y',
'type' => 'gregorian'
],
[
'timestamp' => 1293876000,
'format' => 'some invalid format'
],
];
$result = \core_external::get_user_dates($context->id, null, null, $request);
$result = external_api::clean_returnvalue(\core_external::get_user_dates_returns(), $result);
$this->assertEquals('Saturday, 1 January 2011, 6:00', $result['dates'][0]);
$this->assertEquals('1 01 2011', $result['dates'][1]);
$this->assertEquals('1 01 2011', $result['dates'][2]);
$this->assertEquals('some invalid format', $result['dates'][3]);
}
}
+51
View File
@@ -0,0 +1,51 @@
<?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/>.
namespace core_external;
/**
* Unit tests for core_external\external_files.
*
* @package core_external
* @category test
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @covers \core_external\external_files
*/
class external_files_test extends \advanced_testcase {
/**
* Text external files structure.
*
* @covers \core_external\external_files
*/
public function test_files_structure(): void {
$description = new external_files();
// First check that the expected default values and keys are returned.
$expectedkeys = array_flip([
'filename', 'filepath', 'filesize', 'fileurl', 'timemodified', 'mimetype',
'isexternalfile', 'repositorytype', 'icon',
]);
$returnedkeys = array_flip(array_keys($description->content->keys));
$this->assertEquals($expectedkeys, $returnedkeys);
$this->assertEquals('List of files.', $description->desc);
$this->assertEquals(VALUE_REQUIRED, $description->required);
foreach ($description->content->keys as $key) {
$this->assertEquals(VALUE_OPTIONAL, $key->required);
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?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/>.
namespace core_external;
/**
* Unit tests for core_external\external_settings.
*
* @package core_external
* @category test
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @covers \core_external\external_settings
*/
class external_settings_test extends \advanced_testcase {
/**
* Reset the singleton between tests.
*/
public function tearDown(): void {
external_settings::reset();
}
/**
* Tests for external_settings class.
*
* @covers \core_external\external_settings::get_instance
*/
public function test_external_settings(): void {
$settings = external_settings::get_instance();
$this->assertInstanceOf(external_settings::class, $settings);
}
/**
* Check external_settings defaults.
*
* @covers \core_external\external_settings::get_instance
*/
public function test_external_settings_defaults(): void {
$settings = external_settings::get_instance();
$currentraw = $settings->get_raw();
$currentfilter = $settings->get_filter();
$currentfile = $settings->get_file();
$currentfileurl = $settings->get_fileurl();
$this->assertInstanceOf(external_settings::class, $settings);
// Check apis.
$settings->set_file('plugin.php');
$this->assertEquals('plugin.php', $settings->get_file());
$settings->set_filter(false);
$this->assertFalse($settings->get_filter());
$settings->set_fileurl(false);
$this->assertFalse($settings->get_fileurl());
$settings->set_raw(true);
$this->assertTrue($settings->get_raw());
}
/**
* Check external_settings file API calls.
*
* @covers \core_external\external_settings::set_file
* @covers \core_external\external_settings::get_file
*/
public function test_external_settings_set_file(): void {
$settings = external_settings::get_instance();
$settings->set_file('plugin.php');
$this->assertEquals('plugin.php', $settings->get_file());
}
/**
* Check external_settings filter API calls.
*
* @covers \core_external\external_settings::set_filter
* @covers \core_external\external_settings::get_filter
*/
public function test_external_settings_set_filter(): void {
$settings = external_settings::get_instance();
$settings->set_filter(false);
$this->assertFalse($settings->get_filter());
}
/**
* Check external_settings file API calls.
*
* @covers \core_external\external_settings::set_fileurl
* @covers \core_external\external_settings::get_fileurl
*/
public function test_external_settings_set_fileurl(): void {
$settings = external_settings::get_instance();
$settings->set_fileurl(false);
$this->assertFalse($settings->get_fileurl());
}
/**
* Check external_settings raw API calls.
*
* @covers \core_external\external_settings::set_raw
* @covers \core_external\external_settings::get_raw
*/
public function test_external_settings_set_raw(): void {
$settings = external_settings::get_instance();
$settings->set_raw(true);
$this->assertTrue($settings->get_raw());
}
}
+67
View File
@@ -0,0 +1,67 @@
<?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/>.
namespace core_external;
use advanced_testcase;
/**
* Unit tests for core_external\external_description.
*
* @package core
* @category test
* @copyright 2023 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass external_value
*/
class external_value_test extends advanced_testcase {
/**
* Data provider for the required param test.
*
* @return array[]
*/
public function required_param_provider(): array {
return [
[ VALUE_DEFAULT, false ],
[ VALUE_REQUIRED, false ],
[ VALUE_OPTIONAL, false ],
[ 'aaa', true, 'aaa' ],
[ [VALUE_OPTIONAL], true, 'Array: ' . VALUE_OPTIONAL ],
[ -1000, true, -1000 ],
];
}
/**
* Tests the constructor for the $required parameter validation.
*
* @dataProvider required_param_provider
* @param int $required The required param being tested.
* @param bool $debuggingexpected Whether debugging is expected.
* @param mixed $requiredstr The string value of the $required param in the debugging message.
* @return void
*/
public function test_required_param_validation($required, $debuggingexpected, $requiredstr = ''): void {
$externalvalue = new external_value(PARAM_INT, 'Cool description', $required);
if ($debuggingexpected) {
$this->assertDebuggingCalled("Invalid \$required parameter value: '{$requiredstr}' .
It must be either VALUE_DEFAULT, VALUE_REQUIRED, or VALUE_OPTIONAL", DEBUG_DEVELOPER);
}
$this->assertEquals(PARAM_INT, $externalvalue->type);
$this->assertEquals('Cool description', $externalvalue->desc);
$this->assertEquals($required, $externalvalue->required);
}
}
+512
View File
@@ -0,0 +1,512 @@
<?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/>.
/**
* Data provider tests.
*
* @package core_external
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_external\privacy;
use core_external\privacy\provider;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use core_privacy\local\request\transform;
use core_privacy\local\request\writer;
use core_privacy\tests\provider_testcase;
/**
* External subsytem testcase class.
*
* @package core_external
* @category test
* @copyright 2018 Frédéric Massart
* @author Frédéric Massart <fred@branchup.tech>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends provider_testcase {
public function setUp(): void {
$this->resetAfterTest();
}
/**
* Test the external service get_contexts_for_userid function.
*
* @covers \core_external\privacy\provider::get_contexts_for_userid
*/
public function test_get_contexts_for_userid(): void {
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u3 = $dg->create_user();
$u4 = $dg->create_user();
$u5 = $dg->create_user();
$u1ctx = \context_user::instance($u1->id);
$u2ctx = \context_user::instance($u2->id);
$u3ctx = \context_user::instance($u3->id);
$u5ctx = \context_user::instance($u5->id);
$s = $this->create_service();
$this->create_token(['userid' => $u1->id]);
$this->create_token(['userid' => $u1->id]);
$this->create_token(['userid' => $u2->id, 'creatorid' => $u3->id]);
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u5->id]);
$contextids = provider::get_contexts_for_userid($u1->id)->get_contextids();
$this->assertCount(1, $contextids);
$this->assertTrue(in_array($u1ctx->id, $contextids));
$contextids = provider::get_contexts_for_userid($u2->id)->get_contextids();
$this->assertCount(1, $contextids);
$this->assertTrue(in_array($u2ctx->id, $contextids));
$contextids = provider::get_contexts_for_userid($u3->id)->get_contextids();
$this->assertCount(1, $contextids);
$this->assertTrue(in_array($u2ctx->id, $contextids));
$contextids = provider::get_contexts_for_userid($u4->id)->get_contextids();
$this->assertCount(0, $contextids);
$contextids = provider::get_contexts_for_userid($u5->id)->get_contextids();
$this->assertCount(1, $contextids);
$this->assertTrue(in_array($u5ctx->id, $contextids));
}
/**
* Test delete_data_for_user
*
* @covers \core_external\privacy\provider::delete_data_for_user
*/
public function test_delete_data_for_user(): void {
global $DB;
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u1ctx = \context_user::instance($u1->id);
$u2ctx = \context_user::instance($u2->id);
$s = $this->create_service();
$this->create_token(['userid' => $u1->id, 'creatorid' => $u2->id]);
$this->create_token(['userid' => $u1->id]);
$this->create_token(['userid' => $u2->id]);
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u1->id]);
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u2->id]);
$this->assertEquals(2, $DB->count_records('external_tokens', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('external_tokens', ['userid' => $u2->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u2->id]));
// Delete in another context, nothing happens.
provider::delete_data_for_user(new approved_contextlist($u2, 'core_external', [$u1ctx->id]));
$this->assertEquals(2, $DB->count_records('external_tokens', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('external_tokens', ['userid' => $u2->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u2->id]));
// Delete in my context.
provider::delete_data_for_user(new approved_contextlist($u2, 'core_external', [$u2ctx->id]));
$this->assertEquals(2, $DB->count_records('external_tokens', ['userid' => $u1->id]));
$this->assertEquals(0, $DB->count_records('external_tokens', ['userid' => $u2->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u1->id]));
$this->assertFalse($DB->record_exists('external_services_users', ['userid' => $u2->id]));
}
/**
* Test delete_data_for_all_users_in_context
*
* @covers \core_external\privacy\provider::delete_data_for_all_users_in_context
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u1ctx = \context_user::instance($u1->id);
$u2ctx = \context_user::instance($u2->id);
$s = $this->create_service();
$this->create_token(['userid' => $u1->id, 'creatorid' => $u2->id]);
$this->create_token(['userid' => $u1->id]);
$this->create_token(['userid' => $u2->id]);
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u1->id]);
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u2->id]);
$this->assertEquals(2, $DB->count_records('external_tokens', ['userid' => $u1->id]));
$this->assertEquals(1, $DB->count_records('external_tokens', ['userid' => $u2->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u1->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u2->id]));
provider::delete_data_for_all_users_in_context($u2ctx);
$this->assertEquals(2, $DB->count_records('external_tokens', ['userid' => $u1->id]));
$this->assertEquals(0, $DB->count_records('external_tokens', ['userid' => $u2->id]));
$this->assertTrue($DB->record_exists('external_services_users', ['userid' => $u1->id]));
$this->assertFalse($DB->record_exists('external_services_users', ['userid' => $u2->id]));
provider::delete_data_for_all_users_in_context($u1ctx);
$this->assertEquals(0, $DB->count_records('external_tokens', ['userid' => $u1->id]));
$this->assertEquals(0, $DB->count_records('external_tokens', ['userid' => $u2->id]));
$this->assertFalse($DB->record_exists('external_services_users', ['userid' => $u1->id]));
$this->assertFalse($DB->record_exists('external_services_users', ['userid' => $u2->id]));
}
/**
* Test the export_user_data function.
* @covers \core_external\privacy\provider::export_user_data
*/
public function test_export_data_for_user(): void {
global $DB;
$dg = $this->getDataGenerator();
$u1 = $dg->create_user();
$u2 = $dg->create_user();
$u1ctx = \context_user::instance($u1->id);
$u2ctx = \context_user::instance($u2->id);
$path = [get_string('services', 'core_external')];
$yearago = time() - YEARSECS;
$hourago = time() - HOURSECS;
$s = $this->create_service(['name' => 'Party time!']);
$this->create_token(['userid' => $u1->id, 'timecreated' => $yearago]);
$this->create_token([
'userid' => $u1->id,
'creatorid' => $u2->id,
'iprestriction' => '127.0.0.1',
'lastaccess' => $hourago,
]);
$this->create_token([
'userid' => $u2->id,
'iprestriction' => '192.168.1.0/24',
'lastaccess' => $yearago,
'externalserviceid' => $s->id,
]);
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u2->id]);
// User 1 exporting user 2 context does not give anything.
writer::reset();
provider::export_user_data(new approved_contextlist($u1, 'core_external', [$u2ctx->id]));
$data = writer::with_context($u1ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($u1ctx)->get_related_data($path, 'created_by_you');
$this->assertEmpty($data);
$data = writer::with_context($u2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($u2ctx)->get_related_data($path, 'created_by_you');
$this->assertEmpty($data);
// User 1 exporting their context.
writer::reset();
provider::export_user_data(new approved_contextlist($u1, 'core_external', [$u1ctx->id, $u2ctx->id]));
$data = writer::with_context($u1ctx)->get_data($path);
$this->assertFalse(isset($data->services_user));
$this->assertCount(2, $data->tokens);
$this->assertEquals(transform::datetime($yearago), $data->tokens[0]['created_on']);
$this->assertEquals(null, $data->tokens[0]['ip_restriction']);
$this->assertEquals(transform::datetime($hourago), $data->tokens[1]['last_access']);
$this->assertEquals('127.0.0.1', $data->tokens[1]['ip_restriction']);
$data = writer::with_context($u1ctx)->get_related_data($path, 'created_by_you');
$this->assertEmpty($data);
$data = writer::with_context($u2ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($u2ctx)->get_related_data($path, 'created_by_you');
$this->assertEmpty($data);
// User 2 exporting their context.
writer::reset();
provider::export_user_data(new approved_contextlist($u2, 'core_external', [$u1ctx->id, $u2ctx->id]));
$data = writer::with_context($u2ctx)->get_data($path);
$this->assertCount(1, $data->tokens);
$this->assertEquals('Party time!', $data->tokens[0]['external_service']);
$this->assertEquals(transform::datetime($yearago), $data->tokens[0]['last_access']);
$this->assertEquals('192.168.1.0/24', $data->tokens[0]['ip_restriction']);
$this->assertCount(1, $data->services_user);
$this->assertEquals('Party time!', $data->services_user[0]['external_service']);
$data = writer::with_context($u1ctx)->get_related_data($path, 'created_by_you');
$this->assertCount(1, $data->tokens);
$this->assertEquals(transform::datetime($hourago), $data->tokens[0]['last_access']);
$this->assertEquals('127.0.0.1', $data->tokens[0]['ip_restriction']);
$data = writer::with_context($u1ctx)->get_data($path);
$this->assertEmpty($data);
$data = writer::with_context($u2ctx)->get_related_data($path, 'created_by_you');
$this->assertEmpty($data);
}
/**
* Test that only users with a user context are fetched.
*
* @covers \core_external\privacy\provider::get_users_in_context
*/
public function test_get_users_in_context(): void {
$component = 'core_external';
// Create user u1.
$u1 = $this->getDataGenerator()->create_user();
$u1ctx = \context_user::instance($u1->id);
// Create user u2.
$u2 = $this->getDataGenerator()->create_user();
$u2ctx = \context_user::instance($u2->id);
// Create user u3.
$u3 = $this->getDataGenerator()->create_user();
$u3ctx = \context_user::instance($u3->id);
// Create user u4.
$u4 = $this->getDataGenerator()->create_user();
$u4ctx = \context_user::instance($u4->id);
// Create user u5.
$u5 = $this->getDataGenerator()->create_user();
$u5ctx = \context_user::instance($u5->id);
// The lists of users for each user context ($u1ctx, $u2ctx, etc.) should be empty.
// Related user data have not been created yet.
$userlist1 = new \core_privacy\local\request\userlist($u1ctx, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
$userlist2 = new \core_privacy\local\request\userlist($u2ctx, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(0, $userlist2);
$userlist3 = new \core_privacy\local\request\userlist($u3ctx, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
$userlist4 = new \core_privacy\local\request\userlist($u4ctx, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(0, $userlist4);
$userlist5 = new \core_privacy\local\request\userlist($u5ctx, $component);
provider::get_users_in_context($userlist5);
$this->assertCount(0, $userlist5);
// Create a service.
$s = $this->create_service();
// Create a ws token for u1.
$this->create_token(['userid' => $u1->id]);
// Create a ws token for u2, and u3 as the creator of the token.
$this->create_token(['userid' => $u2->id, 'creatorid' => $u3->id]);
// Create a service user (u4).
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u4->id]);
// The list of users for userlist1 should return one user (u1).
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
$expected = [$u1->id];
$actual = $userlist1->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for userlist2 should return one user (u2).
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
$expected = [$u2->id];
$actual = $userlist2->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for userlist3 should return one user (u3).
provider::get_users_in_context($userlist3);
$this->assertCount(1, $userlist3);
$expected = [$u3->id];
$actual = $userlist3->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for userlist4 should return one user (u4).
provider::get_users_in_context($userlist4);
$this->assertCount(1, $userlist4);
$expected = [$u4->id];
$actual = $userlist4->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for userlist5 should not return any users.
provider::get_users_in_context($userlist5);
$this->assertCount(0, $userlist5);
// The list of users should only return users in the user context.
$systemcontext = \context_system::instance();
$userlist6 = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist6);
$this->assertCount(0, $userlist6);
}
/**
* Test that data for users in approved userlist is deleted.
*
* @covers \core_external\privacy\provider::delete_data_for_users
*/
public function test_delete_data_for_users(): void {
$component = 'core_external';
// Create user u1.
$u1 = $this->getDataGenerator()->create_user();
$u1ctx = \context_user::instance($u1->id);
// Create user u2.
$u2 = $this->getDataGenerator()->create_user();
$u2ctx = \context_user::instance($u2->id);
// Create user u3.
$u3 = $this->getDataGenerator()->create_user();
$u3ctx = \context_user::instance($u3->id);
// Create user u4.
$u4 = $this->getDataGenerator()->create_user();
$u4ctx = \context_user::instance($u4->id);
// Create user u5.
$u5 = $this->getDataGenerator()->create_user();
$u5ctx = \context_user::instance($u5->id);
// Create a service.
$s = $this->create_service();
// Create a ws token for u1.
$this->create_token(['userid' => $u1->id]);
// Create a ws token for u2, and u3 as the creator of the token.
$this->create_token(['userid' => $u2->id, 'creatorid' => $u3->id]);
// Create a service user (u4).
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u4->id]);
// Create a service user (u5).
$this->create_service_user(['externalserviceid' => $s->id, 'userid' => $u5->id]);
// The list of users for u1ctx should return one user (u1).
$userlist1 = new \core_privacy\local\request\userlist($u1ctx, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
// The list of users for u2ctx should return one user (u2).
$userlist2 = new \core_privacy\local\request\userlist($u2ctx, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
// The list of users for u3ctx should return one user (u3).
$userlist3 = new \core_privacy\local\request\userlist($u3ctx, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(1, $userlist3);
// The list of users for u4ctx should return one user (u4).
$userlist4 = new \core_privacy\local\request\userlist($u4ctx, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(1, $userlist4);
$approvedlist = new approved_userlist($u1ctx, $component, $userlist1->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in u1ctx - the user data should now be empty.
$userlist1 = new \core_privacy\local\request\userlist($u1ctx, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(0, $userlist1);
$approvedlist = new approved_userlist($u2ctx, $component, $userlist2->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in u2ctx - the user data should now be empty.
$userlist2 = new \core_privacy\local\request\userlist($u2ctx, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(0, $userlist2);
$approvedlist = new approved_userlist($u3ctx, $component, $userlist3->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in u3ctx - the user data should now be empty.
$userlist3 = new \core_privacy\local\request\userlist($u3ctx, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
$approvedlist = new approved_userlist($u4ctx, $component, $userlist3->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in u4ctx - the user data should now be empty.
$userlist4 = new \core_privacy\local\request\userlist($u4ctx, $component);
provider::get_users_in_context($userlist4);
$this->assertCount(0, $userlist4);
// The list of users for u5ctx should still return one user (u5).
$userlist5 = new \core_privacy\local\request\userlist($u5ctx, $component);
provider::get_users_in_context($userlist5);
$this->assertCount(1, $userlist5);
// User data should only be removed in the user context.
$systemcontext = \context_system::instance();
$approvedlist = new approved_userlist($systemcontext, $component, $userlist5->get_userids());
// Delete using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in u5ctx - the user data should still be present.
$userlist5 = new \core_privacy\local\request\userlist($u5ctx, $component);
provider::get_users_in_context($userlist5);
$this->assertCount(1, $userlist5);
}
/**
* Create a service.
*
* @param array $params The params.
* @return \stdClass
*/
protected function create_service(array $params = []) {
global $DB;
static $i = 0;
$record = (object) array_merge([
'name' => 'Some service',
'enabled' => '1',
'requiredcapability' => '',
'restrictedusers' => '0',
'component' => 'core_external',
'timecreated' => time(),
'timemodified' => time(),
'shortname' => 'service' . $i,
'downloadfiles' => '1',
'uploadfiles' => '1',
], $params);
$record->id = $DB->insert_record('external_services', $record);
return $record;
}
/**
* Create a service user.
*
* @param array $params The params.
* @return \stdClass
*/
protected function create_service_user(array $params) {
global $DB, $USER;
static $i = 0;
$record = (object) array_merge([
'externalserviceid' => null,
'userid' => $USER->id,
'validuntil' => time() + YEARSECS,
'iprestriction' => '',
'timecreated' => time(),
], $params);
$record->id = $DB->insert_record('external_services_users', $record);
return $record;
}
/**
* Create a token.
*
* @param array $params The params.
* @return stdClass
*/
protected function create_token(array $params) {
global $DB, $USER;
$service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE]);
$record = (object) array_merge([
'token' => random_string(64),
'privatetoken' => random_string(64),
'tokentype' => EXTERNAL_TOKEN_PERMANENT,
'contextid' => SYSCONTEXTID,
'externalserviceid' => $service->id,
'userid' => $USER->id,
'validuntil' => time() + YEARSECS,
'iprestriction' => null,
'sid' => null,
'timecreated' => time(),
'lastaccess' => time(),
'creatorid' => $USER->id,
], $params);
$record->id = $DB->insert_record('external_tokens', $record);
return $record;
}
}
+415
View File
@@ -0,0 +1,415 @@
<?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/>.
namespace core_external;
/**
* Unit tests for core_external\util.
*
* @package core_external
* @category test
* @copyright 2022 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @covers \core_external\util
*/
class util_test extends \advanced_testcase {
/** @var \moodle_database The database connection */
protected $db;
/**
* Store the global DB for restore between tests.
*/
public function setUp(): void {
global $DB;
$this->db = $DB;
external_settings::reset();
}
/**
* A helper to include the legacy external functions.
*/
protected function include_legacy_functions(): void {
global $CFG;
$this->assertTrue(
$this->isInIsolation(),
'Inclusion of the legacy test functions requires the test to be run in isolation.',
);
// Note: This is retained for testing of the old functions.
require_once("{$CFG->libdir}/externallib.php");
}
/**
* Reset the global DB between tests.
*/
public function tearDown(): void {
global $DB;
if ($this->db !== null) {
$DB = $this->db;
}
external_settings::reset();
}
/**
* Validate courses, but still return courses even if they fail validation.
*
* @covers \core_external\util::validate_courses
*/
public function test_validate_courses_keepfails(): void {
$this->resetAfterTest(true);
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$c3 = $this->getDataGenerator()->create_course();
$u1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($u1->id, $c1->id);
$courseids = [$c1->id, $c2->id, $c3->id];
$this->setUser($u1);
[$courses, $warnings] = util::validate_courses($courseids, [], false, true);
$this->assertCount(2, $warnings);
$this->assertEquals($c2->id, $warnings[0]['itemid']);
$this->assertEquals($c3->id, $warnings[1]['itemid']);
$this->assertCount(3, $courses);
$this->assertTrue($courses[$c1->id]->contextvalidated);
$this->assertFalse($courses[$c2->id]->contextvalidated);
$this->assertFalse($courses[$c3->id]->contextvalidated);
}
/**
* Validate courses can re-use an array of prefetched courses.
*
* @covers \core_external\util::validate_courses
*/
public function test_validate_courses_prefetch(): void {
$this->resetAfterTest(true);
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$c3 = $this->getDataGenerator()->create_course();
$c4 = $this->getDataGenerator()->create_course();
$u1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($u1->id, $c1->id);
$this->getDataGenerator()->enrol_user($u1->id, $c2->id);
$courseids = [$c1->id, $c2->id, $c3->id];
$courses = [$c2->id => $c2, $c3->id => $c3, $c4->id => $c4];
$this->setUser($u1);
[$courses, $warnings] = util::validate_courses($courseids, $courses);
$this->assertCount(2, $courses);
$this->assertCount(1, $warnings);
$this->assertArrayHasKey($c1->id, $courses);
$this->assertSame($c2, $courses[$c2->id]);
$this->assertArrayNotHasKey($c3->id, $courses);
// The extra course passed is not returned.
$this->assertArrayNotHasKey($c4->id, $courses);
}
/**
* Test the Validate courses standard functionality.
*
* @covers \core_external\util::validate_courses
*/
public function test_validate_courses(): void {
$this->resetAfterTest(true);
$c1 = $this->getDataGenerator()->create_course();
$c2 = $this->getDataGenerator()->create_course();
$c3 = $this->getDataGenerator()->create_course();
$u1 = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($u1->id, $c1->id);
$courseids = [$c1->id, $c2->id, $c3->id];
$this->setAdminUser();
[$courses, $warnings] = util::validate_courses($courseids);
$this->assertEmpty($warnings);
$this->assertCount(3, $courses);
$this->assertArrayHasKey($c1->id, $courses);
$this->assertArrayHasKey($c2->id, $courses);
$this->assertArrayHasKey($c3->id, $courses);
$this->assertEquals($c1->id, $courses[$c1->id]->id);
$this->assertEquals($c2->id, $courses[$c2->id]->id);
$this->assertEquals($c3->id, $courses[$c3->id]->id);
$this->setUser($u1);
[$courses, $warnings] = util::validate_courses($courseids);
$this->assertCount(2, $warnings);
$this->assertEquals($c2->id, $warnings[0]['itemid']);
$this->assertEquals($c3->id, $warnings[1]['itemid']);
$this->assertCount(1, $courses);
$this->assertArrayHasKey($c1->id, $courses);
$this->assertArrayNotHasKey($c2->id, $courses);
$this->assertArrayNotHasKey($c3->id, $courses);
$this->assertEquals($c1->id, $courses[$c1->id]->id);
}
/**
* Text util::get_area_files
*
* @covers \core_external\util::get_area_files
*/
public function test_get_area_files(): void {
global $CFG, $DB;
$this->db = $DB;
$DB = $this->getMockBuilder('moodle_database')->getMock();
$content = base64_encode("Let us create a nice simple file.");
$timemodified = 102030405;
$itemid = 42;
$filesize = strlen($content);
$DB->method('get_records_sql')->willReturn([
(object) [
'filename' => 'example.txt',
'filepath' => '/',
'mimetype' => 'text/plain',
'filesize' => $filesize,
'timemodified' => $timemodified,
'itemid' => $itemid,
'pathnamehash' => sha1('/example.txt'),
],
]);
$component = 'mod_foo';
$filearea = 'area';
$context = 12345;
$expectedfiles = [[
'filename' => 'example.txt',
'filepath' => '/',
'fileurl' => "{$CFG->wwwroot}/webservice/pluginfile.php/{$context}/{$component}/{$filearea}/{$itemid}/example.txt",
'timemodified' => $timemodified,
'filesize' => $filesize,
'mimetype' => 'text/plain',
'isexternalfile' => false,
'icon' => 'f/text',
],
];
// Get all the files for the area.
$files = util::get_area_files($context, $component, $filearea, false);
$this->assertEquals($expectedfiles, $files);
$DB->method('get_in_or_equal')->willReturn([
'= :mock1',
['mock1' => $itemid],
]);
// Get just the file indicated by $itemid.
$files = util::get_area_files($context, $component, $filearea, $itemid);
$this->assertEquals($expectedfiles, $files);
}
/**
* Test default time for user created tokens.
*
* @covers \core_external\util::generate_token_for_current_user
*/
public function test_user_created_tokens_duration(): void {
global $CFG, $DB;
$this->resetAfterTest(true);
$CFG->enablewebservices = 1;
$CFG->enablemobilewebservice = 1;
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$service = $DB->get_record('external_services', ['shortname' => MOODLE_OFFICIAL_MOBILE_SERVICE, 'enabled' => 1]);
$this->setUser($user1);
$timenow = time();
$token = util::generate_token_for_current_user($service);
$this->assertGreaterThanOrEqual($timenow + $CFG->tokenduration, $token->validuntil);
// Change token default time.
$this->setUser($user2);
set_config('tokenduration', DAYSECS);
$token = util::generate_token_for_current_user($service);
$timenow = time();
$this->assertLessThanOrEqual($timenow + DAYSECS, $token->validuntil);
}
/**
* Test the format_text function.
*
* @covers \core_external\util::format_text
* @runInSeparateProcess
*/
public function test_format_text(): void {
$this->include_legacy_functions();
$settings = external_settings::get_instance();
$settings->set_raw(true);
$settings->set_filter(false);
$context = \context_system::instance();
$test = '$$ \pi $$';
$testformat = FORMAT_MARKDOWN;
$correct = [$test, $testformat];
$this->assertSame($correct, util::format_text($test, $testformat, $context, 'core', '', 0));
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct);
$settings->set_raw(false);
$settings->set_filter(true);
$test = '$$ \pi $$';
$testformat = FORMAT_MARKDOWN;
$correct = ['<span class="filter_mathjaxloader_equation"><p><span class="nolink">$$ \pi $$</span></p>
</span>', FORMAT_HTML,
];
$this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0), $correct);
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0), $correct);
// Filters can be opted out from by the developer.
$test = '$$ \pi $$';
$testformat = FORMAT_MARKDOWN;
$correct = ['<p>$$ \pi $$</p>
', FORMAT_HTML,
];
$this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct);
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, ['filter' => false]), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, ['filter' => false]), $correct);
$test = '<p><a id="test"></a><a href="#test">Text</a></p>';
$testformat = FORMAT_HTML;
$correct = [$test, FORMAT_HTML];
$options = ['allowid' => true];
$this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
$test = '<p><a id="test"></a><a href="#test">Text</a></p>';
$testformat = FORMAT_HTML;
$correct = ['<p><a></a><a href="#test">Text</a></p>', FORMAT_HTML];
$options = new \stdClass();
$options->allowid = false;
$this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
$test = '<p><a id="test"></a><a href="#test">Text</a></p>' . "\n" . 'Newline';
$testformat = FORMAT_MOODLE;
$correct = ['<p><a id="test"></a><a href="#test">Text</a></p> Newline', FORMAT_HTML];
$options = new \stdClass();
$options->newlines = false;
$this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
$test = '<p><a id="test"></a><a href="#test">Text</a></p>';
$testformat = FORMAT_MOODLE;
$correct = ['<div class="text_to_html">' . $test . '</div>', FORMAT_HTML];
$options = new \stdClass();
$options->para = true;
$this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
$test = '<p><a id="test"></a><a href="#test">Text</a></p>';
$testformat = FORMAT_MOODLE;
$correct = [$test, FORMAT_HTML];
$options = new \stdClass();
$options->context = $context;
$this->assertSame(util::format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
// Function external_format_text should work with context id or context instance.
$this->assertSame(external_format_text($test, $testformat, $context->id, 'core', '', 0, $options), $correct);
$this->assertSame(external_format_text($test, $testformat, $context, 'core', '', 0, $options), $correct);
}
/**
* Teset the format_string function.
*
* @covers \core_external\util::format_string
* @runInSeparateProcess
*/
public function test_external_format_string(): void {
$this->resetAfterTest();
$this->include_legacy_functions();
$settings = external_settings::get_instance();
// Enable multilang filter to on content and heading.
filter_set_global_state('multilang', TEXTFILTER_ON);
filter_set_applies_to_strings('multilang', 1);
$filtermanager = \filter_manager::instance();
$filtermanager->reset_caches();
$settings->set_raw(true);
$settings->set_filter(true);
$context = \context_system::instance();
$test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ';
$test .= '<script>hi</script> <h3>there</h3>!';
$correct = $test;
$this->assertSame($correct, util::format_string($test, $context));
// Function external_format_string should work with context id or context instance.
$this->assertSame($correct, external_format_string($test, $context));
$this->assertSame($correct, external_format_string($test, $context->id));
$settings->set_raw(false);
$settings->set_filter(false);
$test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ';
$test .= '<script>hi</script> <h3>there</h3>?';
$correct = 'ENFR hi there?';
$this->assertSame($correct, util::format_string($test, $context));
// Function external_format_string should work with context id or context instance.
$this->assertSame($correct, external_format_string($test, $context));
$this->assertSame($correct, external_format_string($test, $context->id));
$settings->set_filter(true);
$test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ';
$test .= '<script>hi</script> <h3>there</h3>@';
$correct = 'EN hi there@';
$this->assertSame($correct, util::format_string($test, $context));
// Function external_format_string should work with context id or context instance.
$this->assertSame($correct, external_format_string($test, $context));
$this->assertSame($correct, external_format_string($test, $context->id));
// Filters can be opted out.
$test = '<span lang="en" class="multilang">EN</span><span lang="fr" class="multilang">FR</span> ';
$test .= '<script>hi</script> <h3>there</h3>%';
$correct = 'ENFR hi there%';
$this->assertSame($correct, util::format_string($test, $context, false, ['filter' => false]));
// Function external_format_string should work with context id or context instance.
$this->assertSame($correct, external_format_string($test, $context->id, false, ['filter' => false]));
$this->assertSame($correct, external_format_string($test, $context, false, ['filter' => false]));
$this->assertSame("& < > \" '", format_string("& < > \" '", true, ['escape' => false]));
}
}