Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.0.x will end 8 May 2023 (12 months).
  • Bug fixes for security issues in 4.0.x will end 13 November 2023 (18 months).
  • PHP version: minimum PHP 7.3.0 Note: the minimum PHP version has increased since Moodle 3.10. PHP 7.4.x is also supported.

Differences Between: [Versions 310 and 400] [Versions 311 and 400] [Versions 39 and 400] [Versions 400 and 401] [Versions 400 and 402] [Versions 400 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  namespace mod_lti;
  18  
  19  use mod_lti_external;
  20  use mod_lti_testcase;
  21  
  22  defined('MOODLE_INTERNAL') || die();
  23  
  24  global $CFG;
  25  
  26  require_once($CFG->dirroot . '/webservice/tests/helpers.php');
  27  require_once($CFG->dirroot . '/mod/lti/lib.php');
  28  require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php');
  29  
  30  /**
  31   * External tool module external functions tests
  32   *
  33   * @package    mod_lti
  34   * @category   external
  35   * @copyright  2015 Juan Leyva <juan@moodle.com>
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   * @since      Moodle 3.0
  38   */
  39  class externallib_test extends mod_lti_testcase {
  40  
  41      /**
  42       * Set up for every test
  43       */
  44      public function setUp(): void {
  45          $this->resetAfterTest();
  46      }
  47  
  48      /**
  49       * Sets up some basic test data including course, users, roles, and an lti instance, for use in some tests.
  50       * @return array
  51       */
  52      protected function setup_test_data() {
  53          global $DB;
  54          $this->setAdminUser();
  55  
  56          // Setup test data.
  57          $course = $this->getDataGenerator()->create_course();
  58          $lti = $this->getDataGenerator()->create_module(
  59              'lti',
  60              ['course' => $course->id, 'toolurl' => 'http://localhost/not/real/tool.php']
  61          );
  62          $context = \context_module::instance($lti->cmid);
  63          $cm = get_coursemodule_from_instance('lti', $lti->id);
  64  
  65          // Create users.
  66          $student = self::getDataGenerator()->create_user();
  67          $teacher = self::getDataGenerator()->create_user();
  68  
  69          // Users enrolments.
  70          $studentrole = $DB->get_record('role', array('shortname' => 'student'));
  71          $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
  72          $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
  73          $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
  74  
  75          return [
  76              'course' => $course,
  77              'lti' => $lti,
  78              'context' => $context,
  79              'cm' => $cm,
  80              'student' => $student,
  81              'teacher' => $teacher,
  82              'studentrole' => $studentrole,
  83              'teacherrole' => $teacherrole
  84          ];
  85      }
  86  
  87      /**
  88       * Test get_tool_proxies.
  89       */
  90      public function test_mod_lti_get_tool_proxies() {
  91          // Create two tool proxies. One to associate with tool, and one to leave orphaned.
  92          $this->setAdminUser();
  93          $proxy = $this->generate_tool_proxy("1");
  94          $orphanedproxy = $this->generate_tool_proxy("2");
  95          $this->generate_tool_type("1", $proxy->id); // Associate proxy 1 with tool type.
  96  
  97          // Fetch all proxies.
  98          $proxies = mod_lti_external::get_tool_proxies(false);
  99          $proxies = \external_api::clean_returnvalue(mod_lti_external::get_tool_proxies_returns(), $proxies);
 100  
 101          $this->assertCount(2, $proxies);
 102          $this->assertEqualsCanonicalizing([(array) $proxy, (array) $orphanedproxy], $proxies);
 103      }
 104  
 105      /**
 106       * Test get_tool_proxies with orphaned proxies only.
 107       */
 108      public function test_mod_lti_get_orphaned_tool_proxies() {
 109          // Create two tool proxies. One to associate with tool, and one to leave orphaned.
 110          $this->setAdminUser();
 111          $proxy = $this->generate_tool_proxy("1");
 112          $orphanedproxy = $this->generate_tool_proxy("2");
 113          $this->generate_tool_type("1", $proxy->id); // Associate proxy 1 with tool type.
 114  
 115          // Fetch all proxies.
 116          $proxies = mod_lti_external::get_tool_proxies(true);
 117          $proxies = \external_api::clean_returnvalue(mod_lti_external::get_tool_proxies_returns(), $proxies);
 118  
 119          $this->assertCount(1, $proxies);
 120          $this->assertEqualsCanonicalizing([(array) $orphanedproxy], $proxies);
 121      }
 122  
 123      /**
 124       * Test get_tool_launch_data.
 125       */
 126      public function test_get_tool_launch_data() {
 127          global $USER;
 128  
 129          [
 130              'course' => $course,
 131              'lti' => $lti
 132          ] = $this->setup_test_data();
 133  
 134          $result = mod_lti_external::get_tool_launch_data($lti->id);
 135          $result = \external_api::clean_returnvalue(mod_lti_external::get_tool_launch_data_returns(), $result);
 136  
 137          // Basic test, the function returns what it's expected.
 138          self::assertEquals($lti->toolurl, $result['endpoint']);
 139          self::assertCount(36, $result['parameters']);
 140  
 141          // Check some parameters.
 142          $parameters = array();
 143          foreach ($result['parameters'] as $param) {
 144              $parameters[$param['name']] = $param['value'];
 145          }
 146          self::assertEquals($lti->resourcekey, $parameters['oauth_consumer_key']);
 147          self::assertEquals($course->fullname, $parameters['context_title']);
 148          self::assertEquals($course->shortname, $parameters['context_label']);
 149          self::assertEquals($USER->id, $parameters['user_id']);
 150          self::assertEquals($USER->firstname, $parameters['lis_person_name_given']);
 151          self::assertEquals($USER->lastname, $parameters['lis_person_name_family']);
 152          self::assertEquals(fullname($USER), $parameters['lis_person_name_full']);
 153          self::assertEquals($USER->username, $parameters['ext_user_username']);
 154          self::assertEquals("phpunit", $parameters['tool_consumer_instance_name']);
 155          self::assertEquals("PHPUnit test site", $parameters['tool_consumer_instance_description']);
 156      }
 157  
 158      /**
 159       * Test get_ltis_by_courses.
 160       */
 161      public function test_mod_lti_get_ltis_by_courses() {
 162          [
 163              'course' => $course,
 164              'lti' => $lti,
 165              'student' => $student,
 166              'teacher' => $teacher,
 167              'studentrole' => $studentrole,
 168          ] = $this->setup_test_data();
 169  
 170          // Create additional course.
 171          $course2 = self::getDataGenerator()->create_course();
 172  
 173          // Second lti.
 174          $record = new \stdClass();
 175          $record->course = $course2->id;
 176          $lti2 = self::getDataGenerator()->create_module('lti', $record);
 177  
 178          // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
 179          $enrol = enrol_get_plugin('manual');
 180          $enrolinstances = enrol_get_instances($course2->id, true);
 181          foreach ($enrolinstances as $courseenrolinstance) {
 182              if ($courseenrolinstance->enrol == "manual") {
 183                  $instance2 = $courseenrolinstance;
 184                  break;
 185              }
 186          }
 187          $enrol->enrol_user($instance2, $student->id, $studentrole->id);
 188  
 189          self::setUser($student);
 190  
 191          $returndescription = mod_lti_external::get_ltis_by_courses_returns();
 192  
 193          // Create what we expect to be returned when querying the two courses.
 194          // First for the student user.
 195          $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles',
 196              'launchcontainer', 'showtitlelaunch', 'showdescriptionlaunch', 'icon', 'secureicon');
 197  
 198          // Add expected coursemodule and data.
 199          $lti1 = $lti;
 200          $lti1->coursemodule = $lti1->cmid;
 201          $lti1->introformat = 1;
 202          $lti1->section = 0;
 203          $lti1->visible = true;
 204          $lti1->groupmode = 0;
 205          $lti1->groupingid = 0;
 206          $lti1->introfiles = [];
 207  
 208          $lti2->coursemodule = $lti2->cmid;
 209          $lti2->introformat = 1;
 210          $lti2->section = 0;
 211          $lti2->visible = true;
 212          $lti2->groupmode = 0;
 213          $lti2->groupingid = 0;
 214          $lti2->introfiles = [];
 215  
 216          foreach ($expectedfields as $field) {
 217              $expected1[$field] = $lti1->{$field};
 218              $expected2[$field] = $lti2->{$field};
 219          }
 220  
 221          $expectedltis = array($expected2, $expected1);
 222  
 223          // Call the external function passing course ids.
 224          $result = mod_lti_external::get_ltis_by_courses(array($course2->id, $course->id));
 225          $result = \external_api::clean_returnvalue($returndescription, $result);
 226  
 227          $this->assertEquals($expectedltis, $result['ltis']);
 228          $this->assertCount(0, $result['warnings']);
 229  
 230          // Call the external function without passing course id.
 231          $result = mod_lti_external::get_ltis_by_courses();
 232          $result = \external_api::clean_returnvalue($returndescription, $result);
 233          $this->assertEquals($expectedltis, $result['ltis']);
 234          $this->assertCount(0, $result['warnings']);
 235  
 236          // Unenrol user from second course and alter expected ltis.
 237          $enrol->unenrol_user($instance2, $student->id);
 238          array_shift($expectedltis);
 239  
 240          // Call the external function without passing course id.
 241          $result = mod_lti_external::get_ltis_by_courses();
 242          $result = \external_api::clean_returnvalue($returndescription, $result);
 243          $this->assertEquals($expectedltis, $result['ltis']);
 244  
 245          // Call for the second course we unenrolled the user from, expected warning.
 246          $result = mod_lti_external::get_ltis_by_courses(array($course2->id));
 247          $result = \external_api::clean_returnvalue($returndescription, $result);
 248          $this->assertCount(1, $result['warnings']);
 249          $this->assertEquals('1', $result['warnings'][0]['warningcode']);
 250          $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
 251  
 252          // Now, try as a teacher for getting all the additional fields.
 253          self::setUser($teacher);
 254  
 255          $additionalfields = array('timecreated', 'timemodified', 'typeid', 'toolurl', 'securetoolurl',
 256              'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
 257              'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
 258              'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid');
 259  
 260          foreach ($additionalfields as $field) {
 261              $expectedltis[0][$field] = $lti1->{$field};
 262          }
 263  
 264          $result = mod_lti_external::get_ltis_by_courses();
 265          $result = \external_api::clean_returnvalue($returndescription, $result);
 266          $this->assertEquals($expectedltis, $result['ltis']);
 267  
 268          // Admin also should get all the information.
 269          self::setAdminUser();
 270  
 271          $result = mod_lti_external::get_ltis_by_courses(array($course->id));
 272          $result = \external_api::clean_returnvalue($returndescription, $result);
 273          $this->assertEquals($expectedltis, $result['ltis']);
 274  
 275          // Now, prohibit capabilities.
 276          $this->setUser($student);
 277          $contextcourse1 = \context_course::instance($course->id);
 278          // Prohibit capability = mod:lti:view on Course1 for students.
 279          assign_capability('mod/lti:view', CAP_PROHIBIT, $studentrole->id, $contextcourse1->id);
 280          // Empty all the caches that may be affected by this change.
 281          accesslib_clear_all_caches_for_unit_testing();
 282          \course_modinfo::clear_instance_cache();
 283  
 284          $ltis = mod_lti_external::get_ltis_by_courses(array($course->id));
 285          $ltis = \external_api::clean_returnvalue(mod_lti_external::get_ltis_by_courses_returns(), $ltis);
 286          $this->assertCount(0, $ltis['ltis']);
 287      }
 288  
 289      /**
 290       * Test view_lti with an invalid instance id.
 291       */
 292      public function test_view_lti_invalid_instanceid() {
 293          $this->expectException(\moodle_exception::class);
 294          mod_lti_external::view_lti(0);
 295      }
 296  
 297      /**
 298       * Test view_lti as a user who is not enrolled in the course.
 299       */
 300      public function test_view_lti_no_enrolment() {
 301          [
 302              'lti' => $lti
 303          ] = $this->setup_test_data();
 304  
 305          // Test not-enrolled user.
 306          $usernotenrolled = self::getDataGenerator()->create_user();
 307          $this->setUser($usernotenrolled);
 308  
 309          $this->expectException(\moodle_exception::class);
 310          mod_lti_external::view_lti($lti->id);
 311      }
 312  
 313      /**
 314       * Test view_lti for a user without the mod/lti:view capability.
 315       */
 316      public function test_view_lti_no_capability() {
 317          [
 318              'lti' => $lti,
 319              'student' => $student,
 320              'studentrole' => $studentrole,
 321              'context' => $context,
 322          ] = $this->setup_test_data();
 323  
 324          $this->setUser($student);
 325  
 326          // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
 327          assign_capability('mod/lti:view', CAP_PROHIBIT, $studentrole->id, $context->id);
 328          // Empty all the caches that may be affected by this change.
 329          accesslib_clear_all_caches_for_unit_testing();
 330          \course_modinfo::clear_instance_cache();
 331  
 332          $this->expectException(\moodle_exception::class);
 333          mod_lti_external::view_lti($lti->id);
 334      }
 335  
 336      /**
 337       * Test view_lti for a user with the mod/lti:view capability in the course.
 338       */
 339      public function test_view_lti() {
 340          [
 341              'lti' => $lti,
 342              'context' => $context,
 343              'cm' => $cm,
 344              'student' => $student,
 345          ] = $this->setup_test_data();
 346  
 347          // Test user with full capabilities.
 348          $this->setUser($student);
 349  
 350          // Trigger and capture the event.
 351          $sink = $this->redirectEvents();
 352  
 353          $result = mod_lti_external::view_lti($lti->id);
 354          // The value of the result isn't needed but validation is.
 355          \external_api::clean_returnvalue(mod_lti_external::view_lti_returns(), $result);
 356  
 357          $events = $sink->get_events();
 358          $this->assertCount(1, $events);
 359          $event = array_shift($events);
 360  
 361          // Checking that the event contains the expected values.
 362          $this->assertInstanceOf('\mod_lti\event\course_module_viewed', $event);
 363          $this->assertEquals($context, $event->get_context());
 364          $moodlelti = new \moodle_url('/mod/lti/view.php', array('id' => $cm->id));
 365          $this->assertEquals($moodlelti, $event->get_url());
 366          $this->assertEventContextNotUsed($event);
 367          $this->assertNotEmpty($event->get_name());
 368      }
 369  
 370      /**
 371       * Test create_tool_proxy.
 372       */
 373      public function test_mod_lti_create_tool_proxy() {
 374          $this->setAdminUser();
 375          $capabilities = ['AA', 'BB'];
 376          $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), $capabilities, []);
 377          $proxy = (object) \external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
 378  
 379          $this->assertEquals('Test proxy', $proxy->name);
 380          $this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
 381          $this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
 382          $this->assertEquals(implode("\n", $capabilities), $proxy->capabilityoffered);
 383      }
 384  
 385      /**
 386       * Test create_tool_proxy with a duplicate url.
 387       */
 388      public function test_mod_lti_create_tool_proxy_duplicateurl() {
 389          $this->setAdminUser();
 390          mod_lti_external::create_tool_proxy('Test proxy 1', $this->getExternalTestFileUrl('/test.html'), array(), array());
 391  
 392          $this->expectException(\moodle_exception::class);
 393          mod_lti_external::create_tool_proxy('Test proxy 2', $this->getExternalTestFileUrl('/test.html'), array(), array());
 394      }
 395  
 396      /**
 397       * Test create_tool_proxy for a user without the required capability.
 398       */
 399      public function test_mod_lti_create_tool_proxy_without_capability() {
 400          $course = $this->getDataGenerator()->create_course();
 401          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 402          $this->setUser($teacher);
 403          $this->expectException(\required_capability_exception::class);
 404          mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
 405      }
 406  
 407      /**
 408       * Test delete_tool_proxy.
 409       */
 410      public function test_mod_lti_delete_tool_proxy() {
 411          $this->setAdminUser();
 412          $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
 413          $proxy = (object) \external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
 414          $this->assertNotEmpty(lti_get_tool_proxy($proxy->id));
 415  
 416          $proxy = mod_lti_external::delete_tool_proxy($proxy->id);
 417          $proxy = (object) \external_api::clean_returnvalue(mod_lti_external::delete_tool_proxy_returns(), $proxy);
 418  
 419          $this->assertEquals('Test proxy', $proxy->name);
 420          $this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
 421          $this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
 422          $this->assertEmpty(lti_get_tool_proxy($proxy->id));
 423      }
 424  
 425      /**
 426       * Test get_tool_proxy_registration_request.
 427       */
 428      public function test_mod_lti_get_tool_proxy_registration_request() {
 429          $this->setAdminUser();
 430          $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
 431          $proxy = (object) \external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
 432  
 433          $request = mod_lti_external::get_tool_proxy_registration_request($proxy->id);
 434          $request = \external_api::clean_returnvalue(mod_lti_external::get_tool_proxy_registration_request_returns(),
 435              $request);
 436  
 437          $this->assertEquals('ToolProxyRegistrationRequest', $request['lti_message_type']);
 438          $this->assertEquals('LTI-2p0', $request['lti_version']);
 439      }
 440  
 441      /**
 442       * Test get_tool_types.
 443       */
 444      public function test_mod_lti_get_tool_types() {
 445          $this->setAdminUser();
 446          $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
 447          $proxy = (object) \external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
 448  
 449          // Create a tool type, associated with that proxy.
 450          $type = new \stdClass();
 451          $data = new \stdClass();
 452          $type->state = LTI_TOOL_STATE_CONFIGURED;
 453          $type->name = "Test tool";
 454          $type->description = "Example description";
 455          $type->toolproxyid = $proxy->id;
 456          $type->baseurl = $this->getExternalTestFileUrl('/test.html');
 457          lti_add_type($type, $data);
 458  
 459          $types = mod_lti_external::get_tool_types($proxy->id);
 460          $types = \external_api::clean_returnvalue(mod_lti_external::get_tool_types_returns(), $types);
 461  
 462          $this->assertCount(1, $types);
 463          $type = $types[0];
 464          $this->assertEquals('Test tool', $type['name']);
 465          $this->assertEquals('Example description', $type['description']);
 466      }
 467  
 468      /**
 469       * Test create_tool_type.
 470       */
 471      public function test_mod_lti_create_tool_type() {
 472          $this->setAdminUser();
 473          $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
 474          $type = \external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
 475  
 476          $this->assertEquals('Example tool', $type['name']);
 477          $this->assertEquals('Example tool description', $type['description']);
 478          $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type['urls']['icon']);
 479          $typeentry = lti_get_type($type['id']);
 480          $this->assertEquals('http://www.example.com/lti/provider.php', $typeentry->baseurl);
 481          $config = lti_get_type_config($type['id']);
 482          $this->assertTrue(isset($config['sendname']));
 483          $this->assertTrue(isset($config['sendemailaddr']));
 484          $this->assertTrue(isset($config['acceptgrades']));
 485          $this->assertTrue(isset($config['forcessl']));
 486      }
 487  
 488      /**
 489       * Test create_tool_type failure from non existent file.
 490       */
 491      public function test_mod_lti_create_tool_type_nonexistant_file() {
 492          $this->expectException(\moodle_exception::class);
 493          mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/doesntexist.xml'), '', '');
 494      }
 495  
 496      /**
 497       * Test create_tool_type failure from xml that is not a cartridge.
 498       */
 499      public function test_mod_lti_create_tool_type_bad_file() {
 500          $this->expectException(\moodle_exception::class);
 501          mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/rsstest.xml'), '', '');
 502      }
 503  
 504      /**
 505       * Test create_tool_type as a user without the required capability.
 506       */
 507      public function test_mod_lti_create_tool_type_without_capability() {
 508          $course = $this->getDataGenerator()->create_course();
 509          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 510          $this->setUser($teacher);
 511          $this->expectException(\required_capability_exception::class);
 512          mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
 513      }
 514  
 515      /**
 516       * Test update_tool_type.
 517       */
 518      public function test_mod_lti_update_tool_type() {
 519          $this->setAdminUser();
 520          $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
 521          $type = \external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
 522  
 523          $type = mod_lti_external::update_tool_type($type['id'], 'New name', 'New description', LTI_TOOL_STATE_PENDING);
 524          $type = \external_api::clean_returnvalue(mod_lti_external::update_tool_type_returns(), $type);
 525  
 526          $this->assertEquals('New name', $type['name']);
 527          $this->assertEquals('New description', $type['description']);
 528          $this->assertEquals('Pending', $type['state']['text']);
 529      }
 530  
 531      /**
 532       * Test delete_tool_type for a user with the required capability.
 533       */
 534      public function test_mod_lti_delete_tool_type() {
 535          $this->setAdminUser();
 536          $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
 537          $type = \external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
 538          $this->assertNotEmpty(lti_get_type($type['id']));
 539  
 540          $type = mod_lti_external::delete_tool_type($type['id']);
 541          $type = \external_api::clean_returnvalue(mod_lti_external::delete_tool_type_returns(), $type);
 542          $this->assertEmpty(lti_get_type($type['id']));
 543      }
 544  
 545      /**
 546       * Test delete_tool_type for a user without the required capability.
 547       */
 548      public function test_mod_lti_delete_tool_type_without_capability() {
 549          $this->setAdminUser();
 550          $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
 551          $type = \external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
 552          $this->assertNotEmpty(lti_get_type($type['id']));
 553  
 554          $course = $this->getDataGenerator()->create_course();
 555          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
 556          $this->setUser($teacher);
 557          $this->expectException(\required_capability_exception::class);
 558          mod_lti_external::delete_tool_type($type['id']);
 559      }
 560  
 561      /**
 562       * Test is_cartridge.
 563       */
 564      public function test_mod_lti_is_cartridge() {
 565          $this->setAdminUser();
 566          $result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'));
 567          $result = \external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
 568          $this->assertTrue($result['iscartridge']);
 569  
 570          $result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/test.html'));
 571          $result = \external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
 572          $this->assertFalse($result['iscartridge']);
 573      }
 574  }