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