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