Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.

Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402]

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