See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Unit tests for the webservice component. 19 * 20 * @package core_webservice 21 * @category test 22 * @copyright 2016 Jun Pataleta <jun@moodle.com> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 namespace core_webservice; 26 27 use webservice; 28 29 defined('MOODLE_INTERNAL') || die(); 30 31 global $CFG; 32 require_once($CFG->dirroot . '/webservice/lib.php'); 33 34 /** 35 * Unit tests for the webservice component. 36 * 37 * @package core_webservice 38 * @category test 39 * @copyright 2016 Jun Pataleta <jun@moodle.com> 40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 41 */ 42 class lib_test extends \advanced_testcase { 43 44 /** 45 * Setup. 46 */ 47 public function setUp(): void { 48 // Calling parent is good, always. 49 parent::setUp(); 50 51 // We always need enabled WS for this testcase. 52 set_config('enablewebservices', '1'); 53 } 54 55 /** 56 * Test init_service_class(). 57 */ 58 public function test_init_service_class() { 59 global $DB, $USER; 60 61 $this->resetAfterTest(true); 62 63 // Set current user. 64 $this->setAdminUser(); 65 66 // Add a web service. 67 $webservice = new \stdClass(); 68 $webservice->name = 'Test web service'; 69 $webservice->enabled = true; 70 $webservice->restrictedusers = false; 71 $webservice->component = 'moodle'; 72 $webservice->timecreated = time(); 73 $webservice->downloadfiles = true; 74 $webservice->uploadfiles = true; 75 $externalserviceid = $DB->insert_record('external_services', $webservice); 76 77 // Add token. 78 $externaltoken = new \stdClass(); 79 $externaltoken->token = 'testtoken'; 80 $externaltoken->tokentype = 0; 81 $externaltoken->userid = $USER->id; 82 $externaltoken->externalserviceid = $externalserviceid; 83 $externaltoken->contextid = 1; 84 $externaltoken->creatorid = $USER->id; 85 $externaltoken->timecreated = time(); 86 $DB->insert_record('external_tokens', $externaltoken); 87 88 // Add a function to the service. 89 $wsmethod = new \stdClass(); 90 $wsmethod->externalserviceid = $externalserviceid; 91 $wsmethod->functionname = 'core_course_get_contents'; 92 $DB->insert_record('external_services_functions', $wsmethod); 93 94 // Initialise the dummy web service. 95 $dummy = new webservice_dummy(WEBSERVICE_AUTHMETHOD_PERMANENT_TOKEN); 96 // Set the token. 97 $dummy->set_token($externaltoken->token); 98 // Run the web service. 99 $dummy->run(); 100 // Get service methods and structs. 101 $servicemethods = $dummy->get_service_methods(); 102 $servicestructs = $dummy->get_service_structs(); 103 $this->assertNotEmpty($servicemethods); 104 // The function core_course_get_contents should be only the only web service function in the moment. 105 $this->assertEquals(1, count($servicemethods)); 106 // The function core_course_get_contents doesn't have a struct class, so the list of service structs should be empty. 107 $this->assertEmpty($servicestructs); 108 109 // Add other functions to the service. 110 // The function core_comment_get_comments has one struct class in its output. 111 $wsmethod->functionname = 'core_comment_get_comments'; 112 $DB->insert_record('external_services_functions', $wsmethod); 113 // The function core_grades_update_grades has one struct class in its input. 114 $wsmethod->functionname = 'core_grades_update_grades'; 115 $DB->insert_record('external_services_functions', $wsmethod); 116 117 // Run the web service again. 118 $dummy->run(); 119 // Get service methods and structs. 120 $servicemethods = $dummy->get_service_methods(); 121 $servicestructs = $dummy->get_service_structs(); 122 $this->assertEquals(3, count($servicemethods)); 123 $this->assertEquals(2, count($servicestructs)); 124 125 // Check the contents of service methods. 126 foreach ($servicemethods as $method) { 127 // Get the external function info. 128 $function = \external_api::external_function_info($method->name); 129 130 // Check input params. 131 foreach ($function->parameters_desc->keys as $name => $keydesc) { 132 $this->check_params($method->inputparams[$name]['type'], $keydesc, $servicestructs); 133 } 134 135 // Check output params. 136 $this->check_params($method->outputparams['return']['type'], $function->returns_desc, $servicestructs); 137 138 // Check description. 139 $this->assertEquals($function->description, $method->description); 140 } 141 } 142 143 /** 144 * Tests update_token_lastaccess() function. 145 * 146 * @throws dml_exception 147 */ 148 public function test_update_token_lastaccess() { 149 global $DB; 150 151 $this->resetAfterTest(true); 152 153 // Set current user. 154 $this->setAdminUser(); 155 156 // Add a web service. 157 $webservice = new \stdClass(); 158 $webservice->name = 'Test web service'; 159 $webservice->enabled = true; 160 $webservice->restrictedusers = false; 161 $webservice->component = 'moodle'; 162 $webservice->timecreated = time(); 163 $webservice->downloadfiles = true; 164 $webservice->uploadfiles = true; 165 $DB->insert_record('external_services', $webservice); 166 167 // Add token. 168 $tokenstr = external_create_service_token($webservice->name, \context_system::instance()->id); 169 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 170 171 // Trigger last access once (at current time). 172 webservice::update_token_lastaccess($token); 173 174 // Check last access. 175 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 176 $this->assertLessThan(5, abs(time() - $token->lastaccess)); 177 178 // Try setting it to +1 second. This should not update yet. 179 $before = (int)$token->lastaccess; 180 webservice::update_token_lastaccess($token, $before + 1); 181 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 182 $this->assertEquals($before, $token->lastaccess); 183 184 // To -1000 seconds. This should not update. 185 webservice::update_token_lastaccess($token, $before - 1000); 186 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 187 $this->assertEquals($before, $token->lastaccess); 188 189 // To +59 seconds. This should also not quite update. 190 webservice::update_token_lastaccess($token, $before + 59); 191 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 192 $this->assertEquals($before, $token->lastaccess); 193 194 // Finally to +60 seconds, where it should update. 195 webservice::update_token_lastaccess($token, $before + 60); 196 $token = $DB->get_record('external_tokens', ['token' => $tokenstr]); 197 $this->assertEquals($before + 60, $token->lastaccess); 198 } 199 200 /** 201 * Tests for the {@see webservice::get_missing_capabilities_by_users()} implementation. 202 */ 203 public function test_get_missing_capabilities_by_users() { 204 global $DB; 205 206 $this->resetAfterTest(true); 207 $wsman = new webservice(); 208 209 $user1 = $this->getDataGenerator()->create_user(); 210 $user2 = $this->getDataGenerator()->create_user(); 211 $user3 = $this->getDataGenerator()->create_user(); 212 213 // Add a test web service. 214 $serviceid = $wsman->add_external_service((object)[ 215 'name' => 'Test web service', 216 'enabled' => 1, 217 'requiredcapability' => '', 218 'restrictedusers' => false, 219 'component' => 'moodle', 220 'downloadfiles' => false, 221 'uploadfiles' => false, 222 ]); 223 224 // Add a function to the service that does not declare any capability as required. 225 $wsman->add_external_function_to_service('core_webservice_get_site_info', $serviceid); 226 227 // Users can be provided as an array of objects, arrays or integers (ids). 228 $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, array($user2), $user3->id], $serviceid)); 229 230 // Add a function to the service that declares some capability as required, but that capability is common for 231 // any user. Here we use 'core_message_delete_conversation' which declares 'moodle/site:deleteownmessage' which 232 // in turn is granted to the authenticated user archetype by default. 233 $wsman->add_external_function_to_service('core_message_delete_conversation', $serviceid); 234 235 // So all three users should have this capability implicitly. 236 $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid)); 237 238 // Add a function to the service that declares some non-common capability. Here we use 239 // 'core_group_add_group_members' that wants 'moodle/course:managegroups'. 240 $wsman->add_external_function_to_service('core_group_add_group_members', $serviceid); 241 242 // Make it so that the $user1 has the capability in some course. 243 $course1 = $this->getDataGenerator()->create_course(); 244 $this->getDataGenerator()->enrol_user($user1->id, $course1->id, 'editingteacher'); 245 246 // Check that no missing capability is reported for the $user1. We don't care at what actual context the 247 // external function call will evaluate the permission. We just check that there is a chance that the user has 248 // the capability somewhere. 249 $this->assertEmpty($wsman->get_missing_capabilities_by_users([$user1], $serviceid)); 250 251 // But there is no place at the site where the capability would be granted to the other two users, so it is 252 // reported as missing. 253 $missing = $wsman->get_missing_capabilities_by_users([$user1, $user2, $user3], $serviceid); 254 $this->assertArrayNotHasKey($user1->id, $missing); 255 $this->assertContains('moodle/course:managegroups', $missing[$user2->id]); 256 $this->assertContains('moodle/course:managegroups', $missing[$user3->id]); 257 } 258 259 /** 260 * Data provider for {@see test_get_active_tokens} 261 * 262 * @return array 263 */ 264 public function get_active_tokens_provider(): array { 265 return [ 266 'No expiration' => [0, true], 267 'Active' => [time() + DAYSECS, true], 268 'Expired' => [time() - DAYSECS, false], 269 ]; 270 } 271 272 /** 273 * Test getting active tokens for a user 274 * 275 * @param int $validuntil 276 * @param bool $expectedactive 277 * 278 * @dataProvider get_active_tokens_provider 279 */ 280 public function test_get_active_tokens(int $validuntil, bool $expectedactive): void { 281 $this->resetAfterTest(); 282 283 $user = $this->getDataGenerator()->create_user(); 284 285 /** @var core_webservice_generator $generator */ 286 $generator = $this->getDataGenerator()->get_plugin_generator('core_webservice'); 287 288 $service = $generator->create_service(['name' => 'My test service', 'shortname' => 'mytestservice']); 289 $generator->create_token(['userid' => $user->id, 'service' => $service->shortname, 'validuntil' => $validuntil]); 290 291 $tokens = webservice::get_active_tokens($user->id); 292 if ($expectedactive) { 293 $this->assertCount(1, $tokens); 294 $this->assertEquals($service->id, reset($tokens)->externalserviceid); 295 } else { 296 $this->assertEmpty($tokens); 297 } 298 } 299 300 /** 301 * Utility method that tests the parameter type of a method info's input/output parameter. 302 * 303 * @param string $type The parameter type that is being evaluated. 304 * @param mixed $methoddesc The method description of the WS function. 305 * @param array $servicestructs The list of generated service struct classes. 306 */ 307 private function check_params($type, $methoddesc, $servicestructs) { 308 if ($methoddesc instanceof external_value) { 309 // Test for simple types. 310 if (in_array($methoddesc->type, [PARAM_INT, PARAM_FLOAT, PARAM_BOOL])) { 311 $this->assertEquals($methoddesc->type, $type); 312 } else { 313 $this->assertEquals('string', $type); 314 } 315 } else if ($methoddesc instanceof external_single_structure) { 316 // Test that the class name of the struct class is in the array of service structs. 317 $structinfo = $this->get_struct_info($servicestructs, $type); 318 $this->assertNotNull($structinfo); 319 // Test that the properties of the struct info exist in the method description. 320 foreach ($structinfo->properties as $propname => $proptype) { 321 $this->assertTrue($this->in_keydesc($methoddesc, $propname)); 322 } 323 } else if ($methoddesc instanceof external_multiple_structure) { 324 // Test for array types. 325 $this->assertEquals('array', $type); 326 } 327 } 328 329 /** 330 * Gets the struct information from the list of struct classes based on the given struct class name. 331 * 332 * @param array $structarray The list of generated struct classes. 333 * @param string $structclass The name of the struct class. 334 * @return object|null The struct class info, or null if it's not found. 335 */ 336 private function get_struct_info($structarray, $structclass) { 337 foreach ($structarray as $struct) { 338 if ($struct->classname === $structclass) { 339 return $struct; 340 } 341 } 342 return null; 343 } 344 345 /** 346 * Searches the keys of the given external_single_structure object if it contains a certain property name. 347 * 348 * @param external_single_structure $keydesc 349 * @param string $propertyname The property name to be searched for. 350 * @return bool True if the property name is found in $keydesc. False, otherwise. 351 */ 352 private function in_keydesc(external_single_structure $keydesc, $propertyname) { 353 foreach ($keydesc->keys as $key => $desc) { 354 if ($key === $propertyname) { 355 return true; 356 } 357 } 358 return false; 359 } 360 } 361 362 /** 363 * Class webservice_dummy. 364 * 365 * Dummy webservice class for testing the \webservice_base_server class and enable us to expose variables we want to test. 366 * 367 * @package core_webservice 368 * @category test 369 * @copyright 2016 Jun Pataleta <jun@moodle.com> 370 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 371 */ 372 class webservice_dummy extends \webservice_base_server { 373 374 /** 375 * webservice_dummy constructor. 376 * 377 * @param int $authmethod The authentication method. 378 */ 379 public function __construct($authmethod) { 380 parent::__construct($authmethod); 381 382 // Arbitrarily naming this as REST in order not to have to register another WS protocol and set capabilities. 383 $this->wsname = 'rest'; 384 } 385 386 /** 387 * Token setter method. 388 * 389 * @param string $token The web service token. 390 */ 391 public function set_token($token) { 392 $this->token = $token; 393 } 394 395 /** 396 * This method parses the request input, it needs to get: 397 * 1/ user authentication - username+password or token 398 * 2/ function name 399 * 3/ function parameters 400 */ 401 protected function parse_request() { 402 // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now. 403 } 404 405 /** 406 * Send the result of function call to the WS client. 407 */ 408 protected function send_response() { 409 // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now. 410 } 411 412 /** 413 * Send the error information to the WS client. 414 * 415 * @param exception $ex 416 */ 417 protected function send_error($ex = null) { 418 // Just a method stub. No need to implement at the moment since it's not really being used for this test case for now. 419 } 420 421 /** 422 * run() method implementation. 423 */ 424 public function run() { 425 $this->authenticate_user(); 426 $this->init_service_class(); 427 } 428 429 /** 430 * Getter method of servicemethods array. 431 * 432 * @return array 433 */ 434 public function get_service_methods() { 435 return $this->servicemethods; 436 } 437 438 /** 439 * Getter method of servicestructs array. 440 * 441 * @return array 442 */ 443 public function get_service_structs() { 444 return $this->servicestructs; 445 } 446 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body