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