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
+334
View File
@@ -0,0 +1,334 @@
<?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/>.
/**
* Unit tests for Web service events.
*
* @package webservice
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_webservice\event;
/**
* Unit tests for Web service events.
*
* @package webservice
* @category phpunit
* @copyright 2013 Frédéric Massart
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events_test extends \advanced_testcase {
public function setUp(): void {
$this->resetAfterTest();
}
public function test_function_called(): void {
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'other' => array(
'function' => 'A function'
)
);
$event = \core\event\webservice_function_called::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals('A function', $event->other['function']);
$this->assertEventContextNotUsed($event);
}
public function test_login_failed(): void {
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'other' => array(
'reason' => 'Unit Test',
'method' => 'Some method',
'tokenid' => '123'
)
);
$event = \core\event\webservice_login_failed::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($params['other']['reason'], $event->other['reason']);
$this->assertEquals($params['other']['method'], $event->other['method']);
$this->assertEquals($params['other']['tokenid'], $event->other['tokenid']);
// We cannot set the token in the other properties.
$params['other']['token'] = 'I should not be set';
try {
$event = \core\event\webservice_login_failed::create($params);
$this->fail('The token cannot be allowed in \core\event\webservice_login_failed');
} catch (\coding_exception $e) {
}
$this->assertEventContextNotUsed($event);
}
public function test_service_created(): void {
global $CFG, $DB;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
// Creating a fake service.
$service = (object) array(
'name' => 'Test',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => null,
'timecreated' => time(),
'timemodified' => time(),
'shortname' => null,
'downloadfiles' => 0,
'uploadfiles' => 0
);
$service->id = $DB->insert_record('external_services', $service);
// Trigger the event.
$params = array(
'objectid' => $service->id,
);
$event = \core\event\webservice_service_created::create($params);
$event->add_record_snapshot('external_services', $service);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Assert that the event contains the right information.
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($service->id, $event->objectid);
$this->assertEventContextNotUsed($event);
}
public function test_service_updated(): void {
global $CFG, $DB;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
// Creating a fake service.
$service = (object) array(
'name' => 'Test',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => null,
'timecreated' => time(),
'timemodified' => time(),
'shortname' => null,
'downloadfiles' => 0,
'uploadfiles' => 0
);
$service->id = $DB->insert_record('external_services', $service);
// Trigger the event.
$params = array(
'objectid' => $service->id,
);
$event = \core\event\webservice_service_updated::create($params);
$event->add_record_snapshot('external_services', $service);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Assert that the event contains the right information.
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($service->id, $event->objectid);
$this->assertEventContextNotUsed($event);
}
public function test_service_deleted(): void {
global $CFG, $DB;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
// Creating a fake service.
$service = (object) array(
'name' => 'Test',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => null,
'timecreated' => time(),
'timemodified' => time(),
'shortname' => null,
'downloadfiles' => 0,
'uploadfiles' => 0
);
$service->id = $DB->insert_record('external_services', $service);
// Trigger the event.
$params = array(
'objectid' => $service->id,
);
$event = \core\event\webservice_service_deleted::create($params);
$event->add_record_snapshot('external_services', $service);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
// Assert that the event contains the right information.
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals($service->id, $event->objectid);
$this->assertEventContextNotUsed($event);
}
public function test_service_user_added(): void {
global $CFG;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'relateduserid' => 2
);
$event = \core\event\webservice_service_user_added::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEquals(2, $event->relateduserid);
$this->assertEventContextNotUsed($event);
}
public function test_service_user_removed(): void {
global $CFG;
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'relateduserid' => 2
);
$event = \core\event\webservice_service_user_removed::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEquals(2, $event->relateduserid);
$this->assertEventContextNotUsed($event);
}
public function test_token_created(): void {
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'relateduserid' => 2,
'other' => array(
'auto' => true
)
);
$event = \core\event\webservice_token_created::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEquals(2, $event->relateduserid);
$this->assertEventContextNotUsed($event);
}
public function test_token_sent(): void {
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// The Web service API doesn't allow the testing of the events directly by
// calling some functions which trigger the events, so what we are going here
// is just checking that the event returns the expected information.
$sink = $this->redirectEvents();
$params = array(
'objectid' => 1,
'other' => array(
'auto' => true
)
);
$event = \core\event\webservice_token_sent::create($params);
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = reset($events);
$this->assertEquals(\context_system::instance(), $event->get_context());
$this->assertEquals(1, $event->objectid);
$this->assertEventContextNotUsed($event);
}
}
+281
View File
@@ -0,0 +1,281 @@
<?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_webservice;
use core_external\external_api;
use externallib_advanced_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/externallib.php');
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
/**
* External course functions unit tests
*
* @package core_webservice
* @category external
* @copyright 2012 Paul Charsley
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class externallib_test extends externallib_advanced_testcase {
public function setUp(): void {
// Calling parent is good, always
parent::setUp();
// We always need enabled WS for this testcase
set_config('enablewebservices', '1');
}
public function test_get_site_info(): void {
global $DB, $USER, $CFG, $PAGE;
$this->resetAfterTest(true);
$maxbytes = 10485760;
$userquota = 5242880;
set_config('maxbytes', $maxbytes);
set_config('userquota', $userquota);
// Set current user
set_config('allowuserthemes', 1);
$user = array();
$user['username'] = 'johnd';
$user['firstname'] = 'John';
$user['lastname'] = 'Doe';
$user['theme'] = 'boost';
self::setUser(self::getDataGenerator()->create_user($user));
// Add a web service and token.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$externalserviceid = $DB->insert_record('external_services', $webservice);
// Add a function to the service
$DB->insert_record('external_services_functions', array('externalserviceid' => $externalserviceid,
'functionname' => 'core_course_get_contents'));
$_POST['wstoken'] = 'testtoken';
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
$siteinfo = \core_webservice_external::get_site_info();
// We need to execute the return values cleaning process to simulate the web service server.
$siteinfo = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $siteinfo);
$this->assertEquals('johnd', $siteinfo['username']);
$this->assertEquals('John', $siteinfo['firstname']);
$this->assertEquals('Doe', $siteinfo['lastname']);
$this->assertEquals(current_language(), $siteinfo['lang']);
$this->assertEquals($USER->id, $siteinfo['userid']);
$this->assertEquals(SITEID, $siteinfo['siteid']);
$this->assertEquals(true, $siteinfo['downloadfiles']);
$this->assertEquals($CFG->release, $siteinfo['release']);
$this->assertEquals($CFG->version, $siteinfo['version']);
$this->assertEquals('', $siteinfo['mobilecssurl']);
$this->assertEquals(count($siteinfo['functions']), 1);
$function = array_pop($siteinfo['functions']);
$this->assertEquals($function['name'], 'core_course_get_contents');
$this->assertEquals($function['version'], $siteinfo['version']);
$this->assertEquals(1, $siteinfo['downloadfiles']);
$this->assertEquals(1, $siteinfo['uploadfiles']);
$this->assertCount(12, $siteinfo['advancedfeatures']);
foreach ($siteinfo['advancedfeatures'] as $feature) {
if ($feature['name'] == 'mnet_dispatcher_mode') {
if ($CFG->mnet_dispatcher_mode == 'off') {
$this->assertEquals(0, $feature['value']);
} else {
$this->assertEquals(1, $feature['value']);
}
} else if ($feature['name'] == 'enablecompetencies') {
$expected = (!empty(get_config('core_competency', 'enabled'))) ? 1 : 0;
$this->assertEquals($expected, $feature['value']);
} else {
$this->assertEquals($CFG->{$feature['name']}, $feature['value']);
}
}
$this->assertEquals($userquota, $siteinfo['userquota']);
// We can use the function for the expectation because USER_CAN_IGNORE_FILE_SIZE_LIMITS is
// covered below for admin user. This test is for user not allowed to ignore limits.
$this->assertEquals(get_max_upload_file_size($maxbytes), $siteinfo['usermaxuploadfilesize']);
$this->assertEquals(true, $siteinfo['usercanmanageownfiles']);
$userkey = get_user_key('core_files', $USER->id);
$this->assertEquals($userkey, $siteinfo['userprivateaccesskey']);
$this->assertEquals(HOMEPAGE_MY, $siteinfo['userhomepage']);
$this->assertEquals($CFG->calendartype, $siteinfo['sitecalendartype']);
if (!empty($USER->calendartype)) {
$this->assertEquals($USER->calendartype, $siteinfo['usercalendartype']);
} else {
$this->assertEquals($CFG->calendartype, $siteinfo['usercalendartype']);
}
$this->assertFalse($siteinfo['userissiteadmin']);
$this->assertEquals($CFG->calendartype, $siteinfo['sitecalendartype']);
$this->assertEquals($user['theme'], $siteinfo['theme']);
$this->assertEquals($USER->policyagreed, $siteinfo['policyagreed']);
// Now as admin.
$this->setAdminUser();
// Set a fake token for the user admin.
$_POST['wstoken'] = 'testtoken';
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
// Set a home page by user preferences.
$CFG->defaulthomepage = HOMEPAGE_USER;
set_user_preference('user_home_page_preference', HOMEPAGE_SITE);
$siteinfo = \core_webservice_external::get_site_info();
// We need to execute the return values cleaning process to simulate the web service server.
$siteinfo = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $siteinfo);
$this->assertEquals(0, $siteinfo['userquota']);
$this->assertEquals(USER_CAN_IGNORE_FILE_SIZE_LIMITS, $siteinfo['usermaxuploadfilesize']);
$this->assertEquals(true, $siteinfo['usercanmanageownfiles']);
$this->assertTrue($siteinfo['userissiteadmin']);
$this->assertEmpty($USER->theme);
$this->assertEquals($PAGE->theme->name, $siteinfo['theme']);
$this->assertEquals($CFG->limitconcurrentlogins, $siteinfo['limitconcurrentlogins']);
$this->assertFalse(isset($siteinfo['usersessionscount']));
$CFG->limitconcurrentlogins = 1;
$record = new \stdClass();
$record->state = 0;
$record->sessdata = null;
$record->userid = $USER->id;
$record->timemodified = time();
$record->firstip = $record->lastip = '10.0.0.1';
$record->sid = md5('hokus1');
$record->timecreated = time();
$DB->insert_record('sessions', $record);
$siteinfo = \core_webservice_external::get_site_info();
$siteinfo = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $siteinfo);
$this->assertEquals($CFG->limitconcurrentlogins, $siteinfo['limitconcurrentlogins']);
$this->assertEquals(1, $siteinfo['usersessionscount']);
}
/**
* Test get_site_info with values > PHP_INT_MAX. We check only userquota since maxbytes require PHP ini changes.
*/
public function test_get_site_info_max_int(): void {
$this->resetAfterTest(true);
self::setUser(self::getDataGenerator()->create_user());
// Check values higher than PHP_INT_MAX. This value may come from settings (as string).
$userquota = PHP_INT_MAX . '000';
set_config('userquota', $userquota);
$result = \core_webservice_external::get_site_info();
$result = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $result);
$this->assertEquals(PHP_INT_MAX, $result['userquota']);
}
/**
* Test get_site_info with missing components.
*/
public function test_get_site_missing_components(): void {
global $USER, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
// Add a web service and token.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$externalserviceid = $DB->insert_record('external_services', $webservice);
// Add a function to the service (missing plugin).
$DB->insert_record('external_functions',
[
'component' => 'mod_random',
'name' => 'mod_random_get_info'
]
);
// Insert one from missing component.
$DB->insert_record('external_services_functions',
[
'externalserviceid' => $externalserviceid,
'functionname' => 'mod_random_get_info'
]
);
// Insert a core one.
$DB->insert_record('external_services_functions',
[
'externalserviceid' => $externalserviceid,
'functionname' => 'core_user_get_users'
]
);
$_POST['wstoken'] = 'testtoken';
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
// Execution should complete.
$result = \core_webservice_external::get_site_info();
$result = external_api::clean_returnvalue(\core_webservice_external::get_site_info_returns(), $result);
// Check we ignore the missing component function.
$this->assertCount(1, $result['functions']);
$this->assertEquals('core_user_get_users', $result['functions'][0]['name']);
}
}
@@ -0,0 +1,57 @@
<?php
// This file is part of Moodle - https://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 <https://www.gnu.org/licenses/>.
/**
* Behat data generator for core_webservice.
*
* @package core_webservice
* @category test
* @copyright 2021 Andrew Nicols <andrew@nicols.co.uk>
* @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_core_webservice_generator extends behat_generator_base {
/**
* Get the list of creatable entities for a web service.
*
* @return array
*/
protected function get_creatable_entities(): array {
return [
'Services' => [
'singular' => 'Service',
'datagenerator' => 'service',
'required' => ['name'],
],
'Service functions' => [
'singular' => 'Service function',
'datagenerator' => 'service_functions',
'required' => ['service', 'functions'],
],
'Tokens' => [
'singular' => 'Token',
'datagenerator' => 'token',
'required' => ['user'],
'switchids' => [
'user' => 'userid',
],
],
];
}
}
+142
View File
@@ -0,0 +1,142 @@
<?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/>.
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once(__DIR__ . '/../../lib.php');
/**
* Data generator for core_webservice plugin.
*
* @package core_webservice
* @category test
* @copyright 2021 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class core_webservice_generator extends component_generator_base {
/**
* Create a new webservice service.
*
* @param array $data
* @return stdClass
*/
public function create_service(array $data): \stdClass {
$webservicemanager = new webservice();
$requiredfields = [
'name',
'shortname',
];
foreach ($requiredfields as $fieldname) {
if (!array_key_exists($fieldname, $data)) {
throw new \coding_exception("Field '{$fieldname}' missing when creating new service");
}
}
$optionalfields = [
'enabled' => false,
'requiredcapability' => '',
'restrictedusers' => 0,
'component' => '',
'timemodified' => time(),
];
foreach ($optionalfields as $fieldname => $value) {
if (!array_key_exists($fieldname, $data)) {
$data[$fieldname] = $value;
}
}
$serviceid = $webservicemanager->add_external_service((object) $data);
return $webservicemanager->get_external_service_by_id($serviceid);
}
/**
* Associate a webservice function with service.
*
* @param array $data
*/
public function create_service_functions(array $data): void {
$webservicemanager = new webservice();
$requiredfields = [
'service',
'functions',
];
foreach ($requiredfields as $fieldname) {
if (!array_key_exists($fieldname, $data)) {
throw new \coding_exception("Field '{$fieldname}' missing when creating new service");
}
}
$service = $webservicemanager->get_external_service_by_shortname($data['service']);
$functions = explode(',', $data['functions']);
foreach ($functions as $functionname) {
$functionname = trim($functionname);
$webservicemanager->add_external_function_to_service($functionname, $service->id);
}
}
/**
* Create a new webservice token.
*
* @param array $data
*/
public function create_token(array $data): void {
$webservicemanager = new webservice();
$requiredfields = [
'userid',
'service',
];
foreach ($requiredfields as $fieldname) {
if (!array_key_exists($fieldname, $data)) {
throw new \coding_exception("Field '{$fieldname}' missing when creating new service");
}
}
$optionalfields = [
'context' => context_system::instance(),
'validuntil' => 0,
'iprestriction' => '',
'name' => '',
];
foreach ($optionalfields as $fieldname => $value) {
if (!array_key_exists($fieldname, $data)) {
$data[$fieldname] = $value;
}
}
$service = $webservicemanager->get_external_service_by_shortname($data['service']);
\core_external\util::generate_token(
EXTERNAL_TOKEN_PERMANENT,
$service,
$data['userid'],
$data['context'],
$data['validuntil'],
$data['iprestriction'],
$data['name']
);
}
}
+129
View File
@@ -0,0 +1,129 @@
<?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/>.
use core_external\external_settings;
/**
* Helper base class for external tests. Helpfull to test capabilities.
*
* @package core_webservice
* @copyright 2012 Jerome Mouneyrac
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class externallib_advanced_testcase extends advanced_testcase {
/**
* Assign a capability to $USER
* The function creates a student $USER if $USER->id is empty
*
* @param string $capability capability name
* @param int|context $contextid
* @param int $roleid
* @return int the role id - mainly returned for creation, so calling function can reuse it
*/
public static function assignUserCapability($capability, $contextid, $roleid = null) {
global $USER;
// Create a new student $USER if $USER doesn't exist
if (empty($USER->id)) {
$user = self::getDataGenerator()->create_user();
self::setUser($user);
}
if (empty($roleid)) {
$roleid = create_role('Dummy role', 'dummyrole', 'dummy role description');
}
assign_capability($capability, CAP_ALLOW, $roleid, $contextid);
role_assign($roleid, $USER->id, $contextid);
accesslib_clear_all_caches_for_unit_testing();
return $roleid;
}
/**
* Configure some filters for external tests.
*
* @param array $filters Filters to enable. Each filter should contain:
* - name: name of the filter.
* - state: the state of the filter.
* - move: -1 means up, 0 means the same, 1 means down.
* - applytostrings: true to apply the filter to content and headings, false for just content.
*/
public static function configure_filters($filters) {
global $CFG;
$filterstrings = false;
// Enable the filters.
foreach ($filters as $filter) {
$filter = (array) $filter;
filter_set_global_state($filter['name'], $filter['state'], $filter['move']);
filter_set_applies_to_strings($filter['name'], $filter['applytostrings']);
$filterstrings = $filterstrings || $filter['applytostrings'];
}
// Set WS filtering.
$wssettings = external_settings::get_instance();
$wssettings->set_filter(true);
// Reset filter caches.
$filtermanager = filter_manager::instance();
$filtermanager->reset_caches();
if ($filterstrings) {
// Don't strip tags in strings.
$CFG->formatstringstriptags = false;
}
}
/**
* Unassign a capability to $USER.
*
* @param string $capability capability name.
* @param int $contextid set the context id if you used assignUserCapability.
* @param int $roleid set the role id if you used assignUserCapability.
* @param int $courseid set the course id if you used getDataGenerator->enrol_users.
* @param string $enrol set the enrol plugin name if you used getDataGenerator->enrol_users with a different plugin than 'manual'.
*/
public static function unassignUserCapability($capability, $contextid = null, $roleid = null, $courseid = null, $enrol = 'manual') {
global $DB;
if (!empty($courseid)) {
// Retrieve the role id.
$instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol));
if (count($instances) != 1) {
throw new coding_exception('No found enrol instance for courseid: ' . $courseid . ' and enrol: ' . $enrol);
}
$instance = reset($instances);
if (is_null($roleid) and $instance->roleid) {
$roleid = $instance->roleid;
}
} else {
if (empty($contextid) or empty($roleid)) {
throw new coding_exception('unassignUserCapaibility requires contextid/roleid or courseid');
}
}
unassign_capability($capability, $roleid, $contextid);
accesslib_clear_all_caches_for_unit_testing();
}
}
+454
View File
@@ -0,0 +1,454 @@
<?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/>.
/**
* Unit tests for the webservice component.
*
* @package core_webservice
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_webservice;
use core_external\external_api;
use core_external\external_multiple_structure;
use core_external\external_single_structure;
use core_external\external_value;
use webservice;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/lib.php');
/**
* Unit tests for the webservice component.
*
* @package core_webservice
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class lib_test extends \advanced_testcase {
/**
* Setup.
*/
public function setUp(): void {
// Calling parent is good, always.
parent::setUp();
// We always need enabled WS for this testcase.
set_config('enablewebservices', '1');
}
/**
* Test init_service_class().
*/
public function test_init_service_class(): void {
global $DB, $USER;
$this->resetAfterTest(true);
// Set current user.
$this->setAdminUser();
// Add a web service.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$externalserviceid = $DB->insert_record('external_services', $webservice);
// Add token.
$externaltoken = new \stdClass();
$externaltoken->token = 'testtoken';
$externaltoken->tokentype = 0;
$externaltoken->userid = $USER->id;
$externaltoken->externalserviceid = $externalserviceid;
$externaltoken->contextid = 1;
$externaltoken->creatorid = $USER->id;
$externaltoken->timecreated = time();
$externaltoken->name = \core_external\util::generate_token_name();
$DB->insert_record('external_tokens', $externaltoken);
// Add a function to the service.
$wsmethod = new \stdClass();
$wsmethod->externalserviceid = $externalserviceid;
$wsmethod->functionname = 'core_course_get_contents';
$DB->insert_record('external_services_functions', $wsmethod);
// Initialise the dummy web service.
$dummy = new webservice_dummy(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN);
// Set the token.
$dummy->set_token($externaltoken->token);
// Run the web service.
$dummy->run();
// Get service methods and structs.
$servicemethods = $dummy->get_service_methods();
$servicestructs = $dummy->get_service_structs();
$this->assertNotEmpty($servicemethods);
// The function core_course_get_contents should be only the only web service function in the moment.
$this->assertEquals(1, count($servicemethods));
// The function core_course_get_contents doesn't have a struct class, so the list of service structs should be empty.
$this->assertEmpty($servicestructs);
// Add other functions to the service.
// The function core_comment_get_comments has one struct class in its output.
$wsmethod->functionname = 'core_comment_get_comments';
$DB->insert_record('external_services_functions', $wsmethod);
// The function core_grades_update_grades has one struct class in its input.
$wsmethod->functionname = 'core_grades_update_grades';
$DB->insert_record('external_services_functions', $wsmethod);
// Run the web service again.
$dummy->run();
// Get service methods and structs.
$servicemethods = $dummy->get_service_methods();
$servicestructs = $dummy->get_service_structs();
$this->assertEquals(3, count($servicemethods));
$this->assertEquals(2, count($servicestructs));
// Check the contents of service methods.
foreach ($servicemethods as $method) {
// Get the external function info.
$function = external_api::external_function_info($method->name);
// Check input params.
foreach ($function->parameters_desc->keys as $name => $keydesc) {
$this->check_params($method->inputparams[$name]['type'], $keydesc, $servicestructs);
}
// Check output params.
$this->check_params($method->outputparams['return']['type'], $function->returns_desc, $servicestructs);
// Check description.
$this->assertEquals($function->description, $method->description);
}
}
/**
* Tests update_token_lastaccess() function.
*/
public function test_update_token_lastaccess(): void {
global $DB, $USER;
$this->resetAfterTest(true);
// Set current user.
$this->setAdminUser();
// Add a web service.
$webservice = new \stdClass();
$webservice->name = 'Test web service';
$webservice->enabled = true;
$webservice->restrictedusers = false;
$webservice->component = 'moodle';
$webservice->timecreated = time();
$webservice->downloadfiles = true;
$webservice->uploadfiles = true;
$DB->insert_record('external_services', $webservice);
// Add token.
$tokenstr = \core_external\util::generate_token(
EXTERNAL_TOKEN_EMBEDDED,
\core_external\util::get_service_by_name($webservice->name),
$USER->id,
\core\context\system::instance()
);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
// Trigger last access once (at current time).
webservice::update_token_lastaccess($token);
// Check last access.
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertLessThan(5, abs(time() - $token->lastaccess));
// Try setting it to +1 second. This should not update yet.
$before = (int)$token->lastaccess;
webservice::update_token_lastaccess($token, $before + 1);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before, $token->lastaccess);
// To -1000 seconds. This should not update.
webservice::update_token_lastaccess($token, $before - 1000);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before, $token->lastaccess);
// To +59 seconds. This should also not quite update.
webservice::update_token_lastaccess($token, $before + 59);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before, $token->lastaccess);
// Finally to +60 seconds, where it should update.
webservice::update_token_lastaccess($token, $before + 60);
$token = $DB->get_record('external_tokens', ['token' => $tokenstr]);
$this->assertEquals($before + 60, $token->lastaccess);
}
/**
* Tests for the {@see webservice::get_missing_capabilities_by_users()} implementation.
*/
public function test_get_missing_capabilities_by_users(): void {
global $DB;
$this->resetAfterTest(true);
$wsman = new webservice();
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
// Add a test web service.
$serviceid = $wsman->add_external_service((object)[
'name' => 'Test web service',
'enabled' => 1,
'requiredcapability' => '',
'restrictedusers' => false,
'component' => 'moodle',
'downloadfiles' => false,
'uploadfiles' => false,
]);
// Add a function to the service that does not declare any capability as required.
$wsman->add_external_function_to_service('core_webservice_get_site_info', $serviceid);
// Users can be provided as an array of objects, arrays or integers (ids).
$this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, array($user2), $user3->id], $serviceid));
// Add a function to the service that declares some capability as required, but that capability is common for
// any user. Here we use 'core_message_delete_conversation' which declares 'moodle/site:deleteownmessage' which
// in turn is granted to the authenticated user archetype by default.
$wsman->add_external_function_to_service('core_message_delete_conversation', $serviceid);
// So all three users should have this capability implicitly.
$this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid));
// Add a function to the service that declares some non-common capability. Here we use
// 'core_group_add_group_members' that wants 'moodle/course:managegroups'.
$wsman->add_external_function_to_service('core_group_add_group_members', $serviceid);
// Make it so that the $user1 has the capability in some course.
$course1 = $this->getDataGenerator()->create_course();
$this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'editingteacher');
// Check that no missing capability is reported for the $user1. We don't care at what actual context the
// external function call will evaluate the permission. We just check that there is a chance that the user has
// the capability somewhere.
$this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1], $serviceid));
// But there is no place at the site where the capability would be granted to the other two users, so it is
// reported as missing.
$missing = $wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid);
$this->assertArrayNotHasKey($user1->id, $missing);
$this->assertContains('moodle/course:managegroups', $missing[$user2->id]);
$this->assertContains('moodle/course:managegroups', $missing[$user3->id]);
}
/**
* Data provider for {@see test_get_active_tokens}
*
* @return array
*/
public function get_active_tokens_provider(): array {
return [
'No expiration' => [0, true],
'Active' => [time() + DAYSECS, true],
'Expired' => [time() - DAYSECS, false],
];
}
/**
* Test getting active tokens for a user
*
* @param int $validuntil
* @param bool $expectedactive
*
* @dataProvider get_active_tokens_provider
*/
public function test_get_active_tokens(int $validuntil, bool $expectedactive): void {
$this->resetAfterTest();
$user = $this->getDataGenerator()->create_user();
/** @var \core_webservice_generator $generator */
$generator = $this->getDataGenerator()->get_plugin_generator('core_webservice');
$service = $generator->create_service(['name' => 'My test service', 'shortname' => 'mytestservice']);
$generator->create_token(['userid' => $user->id, 'service' => $service->shortname, 'validuntil' => $validuntil]);
$tokens = webservice::get_active_tokens($user->id);
if ($expectedactive) {
$this->assertCount(1, $tokens);
$this->assertEquals($service->id, reset($tokens)->externalserviceid);
} else {
$this->assertEmpty($tokens);
}
}
/**
* Utility method that tests the parameter type of a method info's input/output parameter.
*
* @param string $type The parameter type that is being evaluated.
* @param mixed $methoddesc The method description of the WS function.
* @param array $servicestructs The list of generated service struct classes.
*/
private function check_params($type, $methoddesc, $servicestructs) {
if ($methoddesc instanceof external_value) {
// Test for simple types.
if (in_array($methoddesc->type, [PARAM_INT, PARAM_FLOAT, PARAM_BOOL])) {
$this->assertEquals($methoddesc->type, $type);
} else {
$this->assertEquals('string', $type);
}
} else if ($methoddesc instanceof external_single_structure) {
// Test that the class name of the struct class is in the array of service structs.
$structinfo = $this->get_struct_info($servicestructs, $type);
$this->assertNotNull($structinfo);
// Test that the properties of the struct info exist in the method description.
foreach ($structinfo->properties as $propname => $proptype) {
$this->assertTrue($this->in_keydesc($methoddesc, $propname));
}
} else if ($methoddesc instanceof external_multiple_structure) {
// Test for array types.
$this->assertEquals('array', $type);
}
}
/**
* Gets the struct information from the list of struct classes based on the given struct class name.
*
* @param array $structarray The list of generated struct classes.
* @param string $structclass The name of the struct class.
* @return object|null The struct class info, or null if it's not found.
*/
private function get_struct_info($structarray, $structclass) {
foreach ($structarray as $struct) {
if ($struct->classname === $structclass) {
return $struct;
}
}
return null;
}
/**
* Searches the keys of the given external_single_structure object if it contains a certain property name.
*
* @param external_single_structure $keydesc
* @param string $propertyname The property name to be searched for.
* @return bool True if the property name is found in $keydesc. False, otherwise.
*/
private function in_keydesc(external_single_structure $keydesc, $propertyname) {
foreach ($keydesc->keys as $key => $desc) {
if ($key === $propertyname) {
return true;
}
}
return false;
}
}
/**
* Class webservice_dummy.
*
* Dummy webservice class for testing the \webservice_base_server class and enable us to expose variables we want to test.
*
* @package core_webservice
* @category test
* @copyright 2016 Jun Pataleta <jun@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class webservice_dummy extends \webservice_base_server {
/**
* webservice_dummy constructor.
*
* @param int $authmethod The authentication method.
*/
public function __construct($authmethod) {
parent::__construct($authmethod);
// Arbitrarily naming this as REST in order not to have to register another WS protocol and set capabilities.
$this->wsname = 'rest';
}
/**
* Token setter method.
*
* @param string $token The web service token.
*/
public function set_token($token) {
$this->token = $token;
}
/**
* This method parses the request input, it needs to get:
* 1/ user authentication - username+password or token
* 2/ function name
* 3/ function parameters
*/
protected function parse_request() {
// Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
}
/**
* Send the result of function call to the WS client.
*/
protected function send_response() {
// Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
}
/**
* Send the error information to the WS client.
*
* @param \Exception $ex
*/
protected function send_error($ex = null) {
// Just a method stub. No need to implement at the moment since it's not really being used for this test case for now.
}
/**
* run() method implementation.
*/
public function run() {
$this->authenticate_user();
$this->init_service_class();
}
/**
* Getter method of servicemethods array.
*
* @return array
*/
public function get_service_methods() {
return $this->servicemethods;
}
/**
* Getter method of servicestructs array.
*
* @return array
*/
public function get_service_structs() {
return $this->servicestructs;
}
}