MDL-69319 mod_lti: call clean_returnvalue in external 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         global $DB;
50         $this->resetAfterTest();
51         $this->setAdminUser();
53         // Setup test data.
54         $this->course = $this->getDataGenerator()->create_course();
55         $this->lti = $this->getDataGenerator()->create_module('lti',
56             array('course' => $this->course->id, 'toolurl' => 'http://localhost/not/real/tool.php'));
57         $this->context = context_module::instance($this->lti->cmid);
58         $this->cm = get_coursemodule_from_instance('lti', $this->lti->id);
60         // Create users.
61         $this->student = self::getDataGenerator()->create_user();
62         $this->teacher = self::getDataGenerator()->create_user();
64         // Users enrolments.
65         $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
66         $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
67         $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
68         $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
69     }
71     /**
72      * Test view_lti
73      */
74     public function test_get_tool_launch_data() {
75         global $USER, $SITE;
77         $result = mod_lti_external::get_tool_launch_data($this->lti->id);
78         $result = external_api::clean_returnvalue(mod_lti_external::get_tool_launch_data_returns(), $result);
80         // Basic test, the function returns what it's expected.
81         self::assertEquals($this->lti->toolurl, $result['endpoint']);
82         self::assertCount(36, $result['parameters']);
84         // Check some parameters.
85         $parameters = array();
86         foreach ($result['parameters'] as $param) {
87             $parameters[$param['name']] = $param['value'];
88         }
89         self::assertEquals($this->lti->resourcekey, $parameters['oauth_consumer_key']);
90         self::assertEquals($this->course->fullname, $parameters['context_title']);
91         self::assertEquals($this->course->shortname, $parameters['context_label']);
92         self::assertEquals($USER->id, $parameters['user_id']);
93         self::assertEquals($USER->firstname, $parameters['lis_person_name_given']);
94         self::assertEquals($USER->lastname, $parameters['lis_person_name_family']);
95         self::assertEquals(fullname($USER), $parameters['lis_person_name_full']);
96         self::assertEquals($USER->username, $parameters['ext_user_username']);
97         self::assertEquals("phpunit", $parameters['tool_consumer_instance_name']);
98         self::assertEquals("PHPUnit test site", $parameters['tool_consumer_instance_description']);
100     }
102     /*
103      * Test get ltis by courses
104      */
105     public function test_mod_lti_get_ltis_by_courses() {
106         global $DB;
108         // Create additional course.
109         $course2 = self::getDataGenerator()->create_course();
111         // Second lti.
112         $record = new stdClass();
113         $record->course = $course2->id;
114         $lti2 = self::getDataGenerator()->create_module('lti', $record);
116         // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
117         $enrol = enrol_get_plugin('manual');
118         $enrolinstances = enrol_get_instances($course2->id, true);
119         foreach ($enrolinstances as $courseenrolinstance) {
120             if ($courseenrolinstance->enrol == "manual") {
121                 $instance2 = $courseenrolinstance;
122                 break;
123             }
124         }
125         $enrol->enrol_user($instance2, $this->student->id, $this->studentrole->id);
127         self::setUser($this->student);
129         $returndescription = mod_lti_external::get_ltis_by_courses_returns();
131         // Create what we expect to be returned when querying the two courses.
132         // First for the student user.
133         $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'launchcontainer',
134                                 'showtitlelaunch', 'showdescriptionlaunch', 'icon', 'secureicon');
136         // Add expected coursemodule and data.
137         $lti1 = $this->lti;
138         $lti1->coursemodule = $lti1->cmid;
139         $lti1->introformat = 1;
140         $lti1->section = 0;
141         $lti1->visible = true;
142         $lti1->groupmode = 0;
143         $lti1->groupingid = 0;
144         $lti1->introfiles = [];
146         $lti2->coursemodule = $lti2->cmid;
147         $lti2->introformat = 1;
148         $lti2->section = 0;
149         $lti2->visible = true;
150         $lti2->groupmode = 0;
151         $lti2->groupingid = 0;
152         $lti2->introfiles = [];
154         foreach ($expectedfields as $field) {
155                 $expected1[$field] = $lti1->{$field};
156                 $expected2[$field] = $lti2->{$field};
157         }
159         $expectedltis = array($expected2, $expected1);
161         // Call the external function passing course ids.
162         $result = mod_lti_external::get_ltis_by_courses(array($course2->id, $this->course->id));
163         $result = external_api::clean_returnvalue($returndescription, $result);
165         $this->assertEquals($expectedltis, $result['ltis']);
166         $this->assertCount(0, $result['warnings']);
168         // Call the external function without passing course id.
169         $result = mod_lti_external::get_ltis_by_courses();
170         $result = external_api::clean_returnvalue($returndescription, $result);
171         $this->assertEquals($expectedltis, $result['ltis']);
172         $this->assertCount(0, $result['warnings']);
174         // Unenrol user from second course and alter expected ltis.
175         $enrol->unenrol_user($instance2, $this->student->id);
176         array_shift($expectedltis);
178         // Call the external function without passing course id.
179         $result = mod_lti_external::get_ltis_by_courses();
180         $result = external_api::clean_returnvalue($returndescription, $result);
181         $this->assertEquals($expectedltis, $result['ltis']);
183         // Call for the second course we unenrolled the user from, expected warning.
184         $result = mod_lti_external::get_ltis_by_courses(array($course2->id));
185         $result = external_api::clean_returnvalue($returndescription, $result);
186         $this->assertCount(1, $result['warnings']);
187         $this->assertEquals('1', $result['warnings'][0]['warningcode']);
188         $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
190         // Now, try as a teacher for getting all the additional fields.
191         self::setUser($this->teacher);
193         $additionalfields = array('timecreated', 'timemodified', 'typeid', 'toolurl', 'securetoolurl',
194                         'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
195                         'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
196                         'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid');
198         foreach ($additionalfields as $field) {
199                 $expectedltis[0][$field] = $lti1->{$field};
200         }
202         $result = mod_lti_external::get_ltis_by_courses();
203         $result = external_api::clean_returnvalue($returndescription, $result);
204         $this->assertEquals($expectedltis, $result['ltis']);
206         // Admin also should get all the information.
207         self::setAdminUser();
209         $result = mod_lti_external::get_ltis_by_courses(array($this->course->id));
210         $result = external_api::clean_returnvalue($returndescription, $result);
211         $this->assertEquals($expectedltis, $result['ltis']);
213         // Now, prohibit capabilities.
214         $this->setUser($this->student);
215         $contextcourse1 = context_course::instance($this->course->id);
216         // Prohibit capability = mod:lti:view on Course1 for students.
217         assign_capability('mod/lti:view', CAP_PROHIBIT, $this->studentrole->id, $contextcourse1->id);
218         // Empty all the caches that may be affected by this change.
219         accesslib_clear_all_caches_for_unit_testing();
220         course_modinfo::clear_instance_cache();
222         $ltis = mod_lti_external::get_ltis_by_courses(array($this->course->id));
223         $ltis = external_api::clean_returnvalue(mod_lti_external::get_ltis_by_courses_returns(), $ltis);
224         $this->assertCount(0, $ltis['ltis']);
225     }
227     /**
228      * Test view_lti
229      */
230     public function test_view_lti() {
231         global $DB;
233         // Test invalid instance id.
234         try {
235             mod_lti_external::view_lti(0);
236             $this->fail('Exception expected due to invalid mod_lti instance id.');
237         } catch (moodle_exception $e) {
238             $this->assertEquals('invalidrecord', $e->errorcode);
239         }
241         // Test not-enrolled user.
242         $usernotenrolled = self::getDataGenerator()->create_user();
243         $this->setUser($usernotenrolled);
244         try {
245             mod_lti_external::view_lti($this->lti->id);
246             $this->fail('Exception expected due to not enrolled user.');
247         } catch (moodle_exception $e) {
248             $this->assertEquals('requireloginerror', $e->errorcode);
249         }
251         // Test user with full capabilities.
252         $this->setUser($this->student);
254         // Trigger and capture the event.
255         $sink = $this->redirectEvents();
257         $result = mod_lti_external::view_lti($this->lti->id);
258         $result = external_api::clean_returnvalue(mod_lti_external::view_lti_returns(), $result);
260         $events = $sink->get_events();
261         $this->assertCount(1, $events);
262         $event = array_shift($events);
264         // Checking that the event contains the expected values.
265         $this->assertInstanceOf('\mod_lti\event\course_module_viewed', $event);
266         $this->assertEquals($this->context, $event->get_context());
267         $moodlelti = new \moodle_url('/mod/lti/view.php', array('id' => $this->cm->id));
268         $this->assertEquals($moodlelti, $event->get_url());
269         $this->assertEventContextNotUsed($event);
270         $this->assertNotEmpty($event->get_name());
272         // Test user with no capabilities.
273         // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
274         assign_capability('mod/lti:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
275         // Empty all the caches that may be affected by this change.
276         accesslib_clear_all_caches_for_unit_testing();
277         course_modinfo::clear_instance_cache();
279         try {
280             mod_lti_external::view_lti($this->lti->id);
281             $this->fail('Exception expected due to missing capability.');
282         } catch (moodle_exception $e) {
283             $this->assertEquals('requireloginerror', $e->errorcode);
284         }
286     }
288     /*
289      * Test create tool proxy
290      */
291     public function test_mod_lti_create_tool_proxy() {
292         $capabilities = ['AA', 'BB'];
293         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), $capabilities, []);
294         $proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
296         $this->assertEquals('Test proxy', $proxy->name);
297         $this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
298         $this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
299         $this->assertEquals(implode("\n", $capabilities), $proxy->capabilityoffered);
300     }
302     /*
303      * Test create tool proxy with duplicate url
304      */
305     public function test_mod_lti_create_tool_proxy_duplicateurl() {
306         $this->expectException('moodle_exception');
307         $proxy = mod_lti_external::create_tool_proxy('Test proxy 1', $this->getExternalTestFileUrl('/test.html'), array(), array());
308         $proxy = mod_lti_external::create_tool_proxy('Test proxy 2', $this->getExternalTestFileUrl('/test.html'), array(), array());
309     }
311     /*
312      * Test create tool proxy without sufficient capability
313      */
314     public function test_mod_lti_create_tool_proxy_without_capability() {
315         self::setUser($this->teacher);
316         $this->expectException('required_capability_exception');
317         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
318     }
320     /*
321      * Test delete tool proxy
322      */
323     public function test_mod_lti_delete_tool_proxy() {
324         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
325         $proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
326         $this->assertNotEmpty(lti_get_tool_proxy($proxy->id));
328         $proxy = mod_lti_external::delete_tool_proxy($proxy->id);
329         $proxy = (object) external_api::clean_returnvalue(mod_lti_external::delete_tool_proxy_returns(), $proxy);
331         $this->assertEquals('Test proxy', $proxy->name);
332         $this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
333         $this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
334         $this->assertEmpty(lti_get_tool_proxy($proxy->id));
335     }
337     /*
338      * Test get tool proxy registration request
339      */
340     public function test_mod_lti_get_tool_proxy_registration_request() {
341         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
342         $proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
344         $request = mod_lti_external::get_tool_proxy_registration_request($proxy->id);
345         $request = external_api::clean_returnvalue(mod_lti_external::get_tool_proxy_registration_request_returns(),
346             $request);
348         $this->assertEquals('ToolProxyRegistrationRequest', $request['lti_message_type']);
349         $this->assertEquals('LTI-2p0', $request['lti_version']);
350     }
352     /*
353      * Test get tool types
354      */
355     public function test_mod_lti_get_tool_types() {
356         // Create a tool proxy.
357         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
358         $proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
360         // Create a tool type, associated with that proxy.
361         $type = new stdClass();
362         $data = new stdClass();
363         $type->state = LTI_TOOL_STATE_CONFIGURED;
364         $type->name = "Test tool";
365         $type->description = "Example description";
366         $type->toolproxyid = $proxy->id;
367         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
368         $typeid = lti_add_type($type, $data);
370         $types = mod_lti_external::get_tool_types($proxy->id);
371         $types = external_api::clean_returnvalue(mod_lti_external::get_tool_types_returns(), $types);
373         $this->assertEquals(1, count($types));
374         $type = $types[0];
375         $this->assertEquals('Test tool', $type['name']);
376         $this->assertEquals('Example description', $type['description']);
377     }
379     /*
380      * Test create tool type
381      */
382     public function test_mod_lti_create_tool_type() {
383         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
384         $type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
386         $this->assertEquals('Example tool', $type['name']);
387         $this->assertEquals('Example tool description', $type['description']);
388         $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type['urls']['icon']);
389         $typeentry = lti_get_type($type['id']);
390         $this->assertEquals('http://www.example.com/lti/provider.php', $typeentry->baseurl);
391         $config = lti_get_type_config($type['id']);
392         $this->assertTrue(isset($config['sendname']));
393         $this->assertTrue(isset($config['sendemailaddr']));
394         $this->assertTrue(isset($config['acceptgrades']));
395         $this->assertTrue(isset($config['forcessl']));
396     }
398     /*
399      * Test create tool type failure from non existant file
400      */
401     public function test_mod_lti_create_tool_type_nonexistant_file() {
402         $this->expectException('moodle_exception');
403         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/doesntexist.xml'), '', '');
404     }
406     /*
407      * Test create tool type failure from xml that is not a cartridge
408      */
409     public function test_mod_lti_create_tool_type_bad_file() {
410         $this->expectException('moodle_exception');
411         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/rsstest.xml'), '', '');
412     }
414     /*
415      * Test creating of tool types without sufficient capability
416      */
417     public function test_mod_lti_create_tool_type_without_capability() {
418         self::setUser($this->teacher);
419         $this->expectException('required_capability_exception');
420         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
421     }
423     /*
424      * Test update tool type
425      */
426     public function test_mod_lti_update_tool_type() {
427         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
428         $type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
430         $type = mod_lti_external::update_tool_type($type['id'], 'New name', 'New description', LTI_TOOL_STATE_PENDING);
431         $type = external_api::clean_returnvalue(mod_lti_external::update_tool_type_returns(), $type);
433         $this->assertEquals('New name', $type['name']);
434         $this->assertEquals('New description', $type['description']);
435         $this->assertEquals('Pending', $type['state']['text']);
436     }
438     /*
439      * Test delete tool type
440      */
441     public function test_mod_lti_delete_tool_type() {
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);
444         $this->assertNotEmpty(lti_get_type($type['id']));
446         $type = mod_lti_external::delete_tool_type($type['id']);
447         $type = external_api::clean_returnvalue(mod_lti_external::delete_tool_type_returns(), $type);
448         $this->assertEmpty(lti_get_type($type['id']));
449     }
451     /*
452      * Test delete tool type without sufficient capability
453      */
454     public function test_mod_lti_delete_tool_type_without_capability() {
455         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
456         $type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
457         $this->assertNotEmpty(lti_get_type($type['id']));
458         $this->expectException('required_capability_exception');
459         self::setUser($this->teacher);
460         $type = mod_lti_external::delete_tool_type($type['id']);
461     }
463     /*
464      * Test is cartridge
465      */
466     public function test_mod_lti_is_cartridge() {
467         $result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'));
468         $result = external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
469         $this->assertTrue($result['iscartridge']);
471         $result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/test.html'));
472         $result = external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
473         $this->assertFalse($result['iscartridge']);
474     }