See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
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 * Tests for the tool_provider class. 19 * 20 * @package enrol_lti 21 * @copyright 2016 Jun Pataleta <jun@moodle.com> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 namespace enrol_lti; 26 27 use core\session\manager; 28 use IMSGlobal\LTI\HTTPMessage; 29 use IMSGlobal\LTI\ToolProvider\ResourceLink; 30 use IMSGlobal\LTI\ToolProvider\ToolConsumer; 31 use IMSGlobal\LTI\ToolProvider\ToolProvider; 32 use IMSGlobal\LTI\ToolProvider\User; 33 34 defined('MOODLE_INTERNAL') || die(); 35 36 /** 37 * Tests for the tool_provider class. 38 * 39 * @package enrol_lti 40 * @copyright 2016 Jun Pataleta <jun@moodle.com> 41 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 42 */ 43 class tool_provider_test extends \advanced_testcase { 44 45 /** 46 * @var \stdClass $tool The LTI tool. 47 */ 48 protected $tool; 49 50 /** 51 * Test set up. 52 * 53 * This is executed before running any tests in this file. 54 */ 55 public function setUp(): void { 56 global $SESSION; 57 $this->resetAfterTest(); 58 59 manager::init_empty_session(); 60 61 // Set this user as the admin. 62 $this->setAdminUser(); 63 64 $data = new \stdClass(); 65 $data->enrolstartdate = time(); 66 $data->secret = 'secret'; 67 $toolrecord = $this->getDataGenerator()->create_lti_tool($data); 68 $this->tool = helper::get_lti_tool($toolrecord->id); 69 $SESSION->notifications = []; 70 } 71 72 /** 73 * Passing non-existent tool ID. 74 */ 75 public function test_constructor_with_non_existent_tool() { 76 $this->expectException('dml_exception'); 77 new tool_provider(-1); 78 } 79 80 /** 81 * Constructor test. 82 */ 83 public function test_constructor() { 84 global $CFG, $SITE; 85 86 $tool = $this->tool; 87 $tp = new tool_provider($tool->id); 88 89 $this->assertNull($tp->consumer); 90 $this->assertNull($tp->returnUrl); 91 $this->assertNull($tp->resourceLink); 92 $this->assertNull($tp->context); 93 $this->assertNotNull($tp->dataConnector); 94 $this->assertEquals('', $tp->defaultEmail); 95 $this->assertEquals(ToolProvider::ID_SCOPE_ID_ONLY, $tp->idScope); 96 $this->assertFalse($tp->allowSharing); 97 $this->assertEquals(ToolProvider::CONNECTION_ERROR_MESSAGE, $tp->message); 98 $this->assertNull($tp->reason); 99 $this->assertEmpty($tp->details); 100 $this->assertEquals($CFG->wwwroot, $tp->baseUrl); 101 102 $this->assertNotNull($tp->vendor); 103 $this->assertEquals($SITE->shortname, $tp->vendor->id); 104 $this->assertEquals($SITE->fullname, $tp->vendor->name); 105 $this->assertEquals($SITE->summary, $tp->vendor->description); 106 107 $token = helper::generate_proxy_token($tool->id); 108 $name = helper::get_name($tool); 109 $description = helper::get_description($tool); 110 111 $this->assertNotNull($tp->product); 112 $this->assertEquals($token, $tp->product->id); 113 $this->assertEquals($name, $tp->product->name); 114 $this->assertEquals($description, $tp->product->description); 115 116 $this->assertNotNull($tp->requiredServices); 117 $this->assertEmpty($tp->optionalServices); 118 $this->assertNotNull($tp->resourceHandlers); 119 } 120 121 /** 122 * Test for handle request. 123 */ 124 public function test_handle_request_no_request_data() { 125 $tool = $this->tool; 126 $tp = new tool_provider($tool->id); 127 128 // Tool provider object should have been created fine. OK flag should be fine for now. 129 $this->assertTrue($tp->ok); 130 131 // Call handleRequest but suppress output. 132 ob_start(); 133 $tp->handleRequest(); 134 ob_end_clean(); 135 136 // There's basically no request data submitted so OK flag should turn out false. 137 $this->assertFalse($tp->ok); 138 } 139 140 /** 141 * Test for tool_provider::onError(). 142 */ 143 public function test_on_error() { 144 $tool = $this->tool; 145 $tp = new dummy_tool_provider($tool->id); 146 $message = "THIS IS AN ERROR!"; 147 $tp->message = $message; 148 $tp->onError(); 149 $errormessage = get_string('failedrequest', 'enrol_lti', ['reason' => $message]); 150 $this->assertStringContainsString($errormessage, $tp->get_error_output()); 151 } 152 153 /** 154 * Test for tool_provider::onRegister() with no tool consumer set. 155 */ 156 public function test_on_register_no_consumer() { 157 $tool = $this->tool; 158 159 $tp = new dummy_tool_provider($tool->id); 160 $tp->onRegister(); 161 162 $this->assertFalse($tp->ok); 163 $this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message); 164 } 165 166 /** 167 * Test for tool_provider::onRegister() without return URL. 168 */ 169 public function test_on_register_no_return_url() { 170 $tool = $this->tool; 171 172 $dataconnector = new data_connector(); 173 $consumer = new ToolConsumer('testkey', $dataconnector); 174 $consumer->ltiVersion = ToolProvider::LTI_VERSION2; 175 $consumer->secret = $tool->secret; 176 $consumer->name = 'TEST CONSUMER NAME'; 177 $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME'; 178 $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID'; 179 $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION'; 180 $consumer->enabled = true; 181 $consumer->protected = true; 182 $consumer->save(); 183 184 $tp = new dummy_tool_provider($tool->id); 185 $tp->consumer = $consumer; 186 187 $tp->onRegister(); 188 $this->assertFalse($tp->ok); 189 $this->assertEquals(get_string('returnurlnotset', 'enrol_lti'), $tp->message); 190 } 191 192 /** 193 * Test for tool_provider::onRegister() when registration fails. 194 */ 195 public function test_on_register_failed() { 196 global $CFG; 197 $tool = $this->tool; 198 199 $dataconnector = new data_connector(); 200 $consumer = new dummy_tool_consumer('testkey', $dataconnector); 201 $consumer->ltiVersion = ToolProvider::LTI_VERSION2; 202 $consumer->secret = $tool->secret; 203 $consumer->name = 'TEST CONSUMER NAME'; 204 $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME'; 205 $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID'; 206 $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION'; 207 $consumer->enabled = true; 208 $consumer->protected = true; 209 $profilejson = file_get_contents(__DIR__ . '/fixtures/tool_consumer_profile.json'); 210 $consumer->profile = json_decode($profilejson); 211 $consumer->save(); 212 213 $tp = new dummy_tool_provider($tool->id); 214 $tp->consumer = $consumer; 215 $tp->returnUrl = $CFG->wwwroot; 216 217 $tp->onRegister(); 218 219 // The OK flag will be false. 220 $this->assertFalse($tp->ok); 221 // Check message. 222 $this->assertEquals(get_string('couldnotestablishproxy', 'enrol_lti'), $tp->message); 223 } 224 225 /** 226 * Test for tool_provider::onRegister() when registration succeeds. 227 */ 228 public function test_on_register() { 229 global $CFG, $DB; 230 $tool = $this->tool; 231 232 $dataconnector = new data_connector(); 233 $consumer = new dummy_tool_consumer('testkey', $dataconnector, false, true); 234 $consumer->ltiVersion = ToolProvider::LTI_VERSION2; 235 $consumer->secret = $tool->secret; 236 $consumer->name = 'TEST CONSUMER NAME'; 237 $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME'; 238 $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID'; 239 $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION'; 240 $consumer->enabled = true; 241 $consumer->protected = true; 242 $profilejson = file_get_contents(__DIR__ . '/fixtures/tool_consumer_profile.json'); 243 $consumer->profile = json_decode($profilejson); 244 $consumer->save(); 245 246 $tp = new dummy_tool_provider($tool->id); 247 $tp->consumer = $consumer; 248 $tp->returnUrl = $CFG->wwwroot; 249 250 // Capture output of onLaunch() method and save it as a string. 251 ob_start(); 252 $tp->onRegister(); 253 $output = ob_get_clean(); 254 255 $successmessage = get_string('successfulregistration', 'enrol_lti'); 256 257 // Check output contents. Confirm that it has the success message and return URL. 258 $this->assertStringContainsString($successmessage, $output); 259 $this->assertStringContainsString($tp->returnUrl, $output); 260 261 // The OK flag will be true on successful registration. 262 $this->assertTrue($tp->ok); 263 264 // Check tool provider message. 265 $this->assertEquals($successmessage, $tp->message); 266 267 // Check published tool and tool consumer mapping. 268 $mappingparams = [ 269 'toolid' => $tool->id, 270 'consumerid' => $tp->consumer->getRecordId() 271 ]; 272 $this->assertTrue($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams)); 273 } 274 275 /** 276 * Test for tool_provider::onLaunch(). 277 */ 278 public function test_on_launch_no_frame_embedding() { 279 $tp = $this->build_dummy_tp(); 280 281 // Capture output of onLaunch() method and save it as a string. 282 ob_start(); 283 // Suppress session header errors. 284 @$tp->onLaunch(); 285 $output = ob_get_clean(); 286 287 $this->assertStringContainsString(get_string('frameembeddingnotenabled', 'enrol_lti'), $output); 288 } 289 290 /** 291 * Test for tool_provider::onLaunch(). 292 */ 293 public function test_on_launch_with_frame_embedding() { 294 global $CFG; 295 $CFG->allowframembedding = true; 296 297 $tp = $this->build_dummy_tp(); 298 299 // If redirect was called here, we will encounter an 'unsupported redirect error'. 300 // We just want to verify that redirect() was called if frame embedding is allowed. 301 $this->expectException('moodle_exception'); 302 303 // Suppress session header errors. 304 @$tp->onLaunch(); 305 } 306 307 /** 308 * Test for tool_provider::onLaunch() with invalid secret and no tool proxy (for LTI 1 launches). 309 */ 310 public function test_on_launch_with_invalid_secret_and_no_proxy() { 311 $tp = $this->build_dummy_tp('badsecret'); 312 313 // Suppress session header errors. 314 @$tp->onLaunch(); 315 $this->assertFalse($tp->ok); 316 $this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message); 317 } 318 319 /** 320 * Test for tool_provider::onLaunch() with invalid launch URL. 321 */ 322 public function test_on_launch_proxy_with_invalid_launch_url() { 323 $proxy = [ 324 'tool_profile' => [ 325 'resource_handler' => [ 326 [ 327 'message' => [ 328 [ 329 'message_type' => 'basic-lti-launch-request', 330 'path' => '/enrol/lti/tool.php' 331 ] 332 ] 333 ] 334 ] 335 ] 336 ]; 337 $tp = $this->build_dummy_tp($this->tool->secret, $proxy); 338 // Suppress session header errors. 339 @$tp->onLaunch(); 340 341 $this->assertFalse($tp->ok); 342 $this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message); 343 } 344 345 /** 346 * Test for tool_provider::onLaunch() with invalid launch URL. 347 */ 348 public function test_on_launch_proxy_with_valid_launch_url() { 349 $tool = $this->tool; 350 351 $proxy = [ 352 'tool_profile' => [ 353 'resource_handler' => [ 354 [ 355 'message' => [ 356 [ 357 'message_type' => 'basic-lti-launch-request', 358 'path' => '/enrol/lti/tool.php?id=' . $tool->id 359 ] 360 ] 361 ] 362 ] 363 ] 364 ]; 365 $tp = $this->build_dummy_tp($this->tool->secret, $proxy); 366 367 // Capture output of onLaunch() method and save it as a string. 368 ob_start(); 369 // Suppress session header errors. 370 @$tp->onLaunch(); 371 $output = ob_get_clean(); 372 373 $this->assertTrue($tp->ok); 374 $this->assertEquals(get_string('success'), $tp->message); 375 $this->assertStringContainsString(get_string('frameembeddingnotenabled', 'enrol_lti'), $output); 376 } 377 378 /** 379 * Test for tool_provider::onLaunch() for a request with message type other than basic-lti-launch-request. 380 */ 381 public function test_on_launch_proxy_with_invalid_message_type() { 382 $tool = $this->tool; 383 384 $proxy = [ 385 'tool_profile' => [ 386 'resource_handler' => [ 387 [ 388 'message' => [ 389 [ 390 'message_type' => 'ContentItemSelectionRequest', 391 'path' => '/enrol/lti/tool.php?id=' . $tool->id 392 ] 393 ] 394 ] 395 ] 396 ] 397 ]; 398 $tp = $this->build_dummy_tp($this->tool->secret, $proxy); 399 400 // Suppress session header errors. 401 @$tp->onLaunch(); 402 403 $this->assertFalse($tp->ok); 404 $this->assertEquals(get_string('invalidrequest', 'enrol_lti'), $tp->message); 405 } 406 407 /** 408 * Test for tool_provider::onLaunch() to verify that a user image can be set from the resource link's custom_user_image setting. 409 */ 410 public function test_on_launch_with_user_image_from_resource_link() { 411 global $DB; 412 413 $userimageurl = $this->getExternalTestFileUrl('test.jpg'); 414 $resourcelinksettings = [ 415 'custom_user_image' => $userimageurl 416 ]; 417 $tp = $this->build_dummy_tp($this->tool->secret, null, $resourcelinksettings); 418 419 // Suppress output and session header errors. 420 ob_start(); 421 @$tp->onLaunch(); 422 ob_end_clean(); 423 424 $this->assertEquals($userimageurl, $tp->resourceLink->getSetting('custom_user_image')); 425 426 $username = helper::create_username($tp->consumer->getKey(), $tp->user->ltiUserId); 427 $user = $DB->get_record('user', ['username' => $username]); 428 // User was found. 429 $this->assertNotFalse($user); 430 // User picture was set. 431 $this->assertNotEmpty($user->picture); 432 } 433 434 /** 435 * Test for tool_provider::onLaunch() to verify that a LTI user has been enrolled. 436 */ 437 public function test_on_launch_user_enrolment() { 438 global $DB; 439 440 $tp = $this->build_dummy_tp($this->tool->secret); 441 442 // Suppress output and session header errors. 443 ob_start(); 444 @$tp->onLaunch(); 445 ob_end_clean(); 446 447 $username = helper::create_username($tp->consumer->getKey(), $tp->user->ltiUserId); 448 $user = $DB->get_record('user', ['username' => $username]); 449 // User was found. 450 $this->assertNotFalse($user); 451 // User picture was not set. 452 $this->assertEmpty($user->picture); 453 454 // Check user enrolment. 455 $enrolled = $DB->record_exists('user_enrolments', ['enrolid' => $this->tool->enrolid, 'userid' => $user->id]); 456 $this->assertTrue($enrolled); 457 } 458 459 /** 460 * Test for tool_provider::onLaunch() when the consumer object has not been set. 461 */ 462 public function test_on_launch_no_consumer() { 463 global $DB; 464 465 $tool = $this->tool; 466 467 $tp = new dummy_tool_provider($tool->id); 468 $tp->onLaunch(); 469 $this->assertFalse($tp->ok); 470 $this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message); 471 472 // Check published tool and tool consumer has not yet been mapped due to failure. 473 $mappingparams = [ 474 'toolid' => $tool->id 475 ]; 476 $this->assertFalse($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams)); 477 } 478 479 /** 480 * Test for tool_provider::onLaunch() when we have a non-existent consumer data. 481 */ 482 public function test_on_launch_invalid_consumer() { 483 $tool = $this->tool; 484 485 $dataconnector = new data_connector(); 486 // Build consumer object but don't save it. 487 $consumer = new dummy_tool_consumer('testkey', $dataconnector); 488 489 $tp = new dummy_tool_provider($tool->id); 490 $tp->consumer = $consumer; 491 $tp->onLaunch(); 492 $this->assertFalse($tp->ok); 493 $this->assertEquals(get_string('invalidtoolconsumer', 'enrol_lti'), $tp->message); 494 } 495 496 /** 497 * Test for tool_provider::map_tool_to_consumer(). 498 */ 499 public function test_map_tool_to_consumer() { 500 global $DB; 501 502 $tp = $this->build_dummy_tp(); 503 $tp->map_tool_to_consumer(); 504 505 // Check published tool and tool consumer mapping. 506 $mappingparams = [ 507 'toolid' => $this->tool->id, 508 'consumerid' => $tp->consumer->getRecordId() 509 ]; 510 $this->assertTrue($DB->record_exists('enrol_lti_tool_consumer_map', $mappingparams)); 511 } 512 513 /** 514 * Test for tool_provider::map_tool_to_consumer(). 515 */ 516 public function test_map_tool_to_consumer_no_consumer() { 517 $tp = new dummy_tool_provider($this->tool->id); 518 $this->expectException('moodle_exception'); 519 $tp->map_tool_to_consumer(); 520 } 521 522 /** 523 * Builds a dummy tool provider object. 524 * 525 * @param string $secret Consumer secret. 526 * @param array|\stdClass $proxy Tool proxy data. 527 * @param null $resourcelinksettings Key-value array for resource link settings. 528 * @return dummy_tool_provider 529 */ 530 protected function build_dummy_tp($secret = null, $proxy = null, $resourcelinksettings = null) { 531 $tool = $this->tool; 532 533 $dataconnector = new data_connector(); 534 $consumer = new ToolConsumer('testkey', $dataconnector); 535 536 $ltiversion = ToolProvider::LTI_VERSION2; 537 if ($secret === null && $proxy === null) { 538 $consumer->secret = $tool->secret; 539 $ltiversion = ToolProvider::LTI_VERSION1; 540 } else { 541 $consumer->secret = $secret; 542 } 543 $consumer->ltiVersion = $ltiversion; 544 545 $consumer->name = 'TEST CONSUMER NAME'; 546 $consumer->consumerName = 'TEST CONSUMER INSTANCE NAME'; 547 $consumer->consumerGuid = 'TEST CONSUMER INSTANCE GUID'; 548 $consumer->consumerVersion = 'TEST CONSUMER INFO VERSION'; 549 $consumer->enabled = true; 550 $consumer->protected = true; 551 if ($proxy !== null) { 552 $consumer->toolProxy = json_encode($proxy); 553 } 554 $consumer->save(); 555 556 $resourcelink = ResourceLink::fromConsumer($consumer, 'testresourcelinkid'); 557 if (!empty($resourcelinksettings)) { 558 foreach ($resourcelinksettings as $setting => $value) { 559 $resourcelink->setSetting($setting, $value); 560 } 561 } 562 $resourcelink->save(); 563 564 $ltiuser = User::fromResourceLink($resourcelink, ''); 565 $ltiuser->ltiResultSourcedId = 'testLtiResultSourcedId'; 566 $ltiuser->ltiUserId = 'testuserid'; 567 $ltiuser->email = 'user1@example.com'; 568 $ltiuser->save(); 569 570 $tp = new dummy_tool_provider($tool->id); 571 $tp->user = $ltiuser; 572 $tp->resourceLink = $resourcelink; 573 $tp->consumer = $consumer; 574 575 return $tp; 576 } 577 } 578 579 /** 580 * Class dummy_tool_provider. 581 * 582 * A class that extends tool_provider so that we can expose the protected methods that we have overridden. 583 * 584 * @copyright 2016 Jun Pataleta <jun@moodle.com> 585 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 586 */ 587 class dummy_tool_provider extends tool_provider { 588 589 /** 590 * Exposes tool_provider::onError(). 591 */ 592 public function onError() { 593 parent::onError(); 594 } 595 596 /** 597 * Exposes tool_provider::onLaunch(). 598 */ 599 public function onLaunch() { 600 parent::onLaunch(); 601 } 602 603 /** 604 * Exposes tool_provider::onRegister(). 605 */ 606 public function onRegister() { 607 parent::onRegister(); 608 } 609 610 /** 611 * Expose protected variable errorOutput. 612 * 613 * @return string 614 */ 615 public function get_error_output() { 616 return $this->errorOutput; 617 } 618 } 619 620 /** 621 * Class dummy_tool_consumer 622 * 623 * A class that extends ToolConsumer in order to override and simulate sending and receiving data to tool consumer endpoint. 624 * 625 * @copyright 2016 Jun Pataleta <jun@moodle.com> 626 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 627 */ 628 class dummy_tool_consumer extends ToolConsumer { 629 630 /** 631 * @var bool Flag to indicate whether to send an OK response or a failed response. 632 */ 633 protected $success = false; 634 635 /** 636 * dummy_tool_consumer constructor. 637 * 638 * @param null|string $key 639 * @param mixed|null $dataconnector 640 * @param bool $autoenable 641 * @param bool $success 642 */ 643 public function __construct($key = null, $dataconnector = null, $autoenable = false, $success = false) { 644 parent::__construct($key, $dataconnector, $autoenable); 645 $this->success = $success; 646 } 647 648 /** 649 * Override ToolConsumer::doServiceRequest() to simulate sending/receiving data to and from the tool consumer. 650 * 651 * @param object $service 652 * @param string $method 653 * @param string $format 654 * @param mixed $data 655 * @return HTTPMessage 656 */ 657 public function doServiceRequest($service, $method, $format, $data) { 658 $response = (object)['tool_proxy_guid' => 1]; 659 $header = ToolConsumer::addSignature($service->endpoint, $this->getKey(), $this->secret, $data, $method, $format); 660 $http = new HTTPMessage($service->endpoint, $method, $data, $header); 661 662 if ($this->success) { 663 $http->responseJson = $response; 664 $http->ok = true; 665 $http->status = 201; 666 } 667 668 return $http; 669 } 670 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body