b37cf4e430e06a753eda0e978b288b24d2e48358
[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         $this->assertCount(1, $result['warnings']);
186         $this->assertEquals('1', $result['warnings'][0]['warningcode']);
187         $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
189         // Now, try as a teacher for getting all the additional fields.
190         self::setUser($this->teacher);
192         $additionalfields = array('timecreated', 'timemodified', 'typeid', 'toolurl', 'securetoolurl',
193                         'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
194                         'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
195                         'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid');
197         foreach ($additionalfields as $field) {
198                 $expectedltis[0][$field] = $lti1->{$field};
199         }
201         $result = mod_lti_external::get_ltis_by_courses();
202         $result = external_api::clean_returnvalue($returndescription, $result);
203         $this->assertEquals($expectedltis, $result['ltis']);
205         // Admin also should get all the information.
206         self::setAdminUser();
208         $result = mod_lti_external::get_ltis_by_courses(array($this->course->id));
209         $result = external_api::clean_returnvalue($returndescription, $result);
210         $this->assertEquals($expectedltis, $result['ltis']);
212         // Now, prohibit capabilities.
213         $this->setUser($this->student);
214         $contextcourse1 = context_course::instance($this->course->id);
215         // Prohibit capability = mod:lti:view on Course1 for students.
216         assign_capability('mod/lti:view', CAP_PROHIBIT, $this->studentrole->id, $contextcourse1->id);
217         // Empty all the caches that may be affected by this change.
218         accesslib_clear_all_caches_for_unit_testing();
219         course_modinfo::clear_instance_cache();
221         $ltis = mod_lti_external::get_ltis_by_courses(array($this->course->id));
222         $ltis = external_api::clean_returnvalue(mod_lti_external::get_ltis_by_courses_returns(), $ltis);
223         $this->assertCount(0, $ltis['ltis']);
224     }
226     /**
227      * Test view_lti
228      */
229     public function test_view_lti() {
230         global $DB;
232         // Test invalid instance id.
233         try {
234             mod_lti_external::view_lti(0);
235             $this->fail('Exception expected due to invalid mod_lti instance id.');
236         } catch (moodle_exception $e) {
237             $this->assertEquals('invalidrecord', $e->errorcode);
238         }
240         // Test not-enrolled user.
241         $usernotenrolled = self::getDataGenerator()->create_user();
242         $this->setUser($usernotenrolled);
243         try {
244             mod_lti_external::view_lti($this->lti->id);
245             $this->fail('Exception expected due to not enrolled user.');
246         } catch (moodle_exception $e) {
247             $this->assertEquals('requireloginerror', $e->errorcode);
248         }
250         // Test user with full capabilities.
251         $this->setUser($this->student);
253         // Trigger and capture the event.
254         $sink = $this->redirectEvents();
256         $result = mod_lti_external::view_lti($this->lti->id);
257         $result = external_api::clean_returnvalue(mod_lti_external::view_lti_returns(), $result);
259         $events = $sink->get_events();
260         $this->assertCount(1, $events);
261         $event = array_shift($events);
263         // Checking that the event contains the expected values.
264         $this->assertInstanceOf('\mod_lti\event\course_module_viewed', $event);
265         $this->assertEquals($this->context, $event->get_context());
266         $moodlelti = new \moodle_url('/mod/lti/view.php', array('id' => $this->cm->id));
267         $this->assertEquals($moodlelti, $event->get_url());
268         $this->assertEventContextNotUsed($event);
269         $this->assertNotEmpty($event->get_name());
271         // Test user with no capabilities.
272         // We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
273         assign_capability('mod/lti:view', CAP_PROHIBIT, $this->studentrole->id, $this->context->id);
274         // Empty all the caches that may be affected by this change.
275         accesslib_clear_all_caches_for_unit_testing();
276         course_modinfo::clear_instance_cache();
278         try {
279             mod_lti_external::view_lti($this->lti->id);
280             $this->fail('Exception expected due to missing capability.');
281         } catch (moodle_exception $e) {
282             $this->assertEquals('requireloginerror', $e->errorcode);
283         }
285     }
287     /*
288      * Test create tool proxy
289      */
290     public function test_mod_lti_create_tool_proxy() {
291         $capabilities = ['AA', 'BB'];
292         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), $capabilities, []);
293         $this->assertEquals('Test proxy', $proxy->name);
294         $this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
295         $this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
296         $this->assertEquals(implode("\n", $capabilities), $proxy->capabilityoffered);
297     }
299     /*
300      * Test create tool proxy with duplicate url
301      */
302     public function test_mod_lti_create_tool_proxy_duplicateurl() {
303         $this->expectException('moodle_exception');
304         $proxy = mod_lti_external::create_tool_proxy('Test proxy 1', $this->getExternalTestFileUrl('/test.html'), array(), array());
305         $proxy = mod_lti_external::create_tool_proxy('Test proxy 2', $this->getExternalTestFileUrl('/test.html'), array(), array());
306     }
308     /*
309      * Test create tool proxy without sufficient capability
310      */
311     public function test_mod_lti_create_tool_proxy_without_capability() {
312         self::setUser($this->teacher);
313         $this->expectException('required_capability_exception');
314         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
315     }
317     /*
318      * Test delete tool proxy
319      */
320     public function test_mod_lti_delete_tool_proxy() {
321         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
322         $this->assertNotEmpty(lti_get_tool_proxy($proxy->id));
324         $proxy = mod_lti_external::delete_tool_proxy($proxy->id);
325         $this->assertEquals('Test proxy', $proxy->name);
326         $this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
327         $this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
328         $this->assertEmpty(lti_get_tool_proxy($proxy->id));
329     }
331     /*
332      * Test get tool proxy registration request
333      */
334     public function test_mod_lti_get_tool_proxy_registration_request() {
335         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
336         $request = mod_lti_external::get_tool_proxy_registration_request($proxy->id);
337         $this->assertEquals('ToolProxyRegistrationRequest', $request['lti_message_type']);
338         $this->assertEquals('LTI-2p0', $request['lti_version']);
339     }
341     /*
342      * Test get tool types
343      */
344     public function test_mod_lti_get_tool_types() {
345         // Create a tool proxy.
346         $proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
348         // Create a tool type, associated with that proxy.
349         $type = new stdClass();
350         $data = new stdClass();
351         $type->state = LTI_TOOL_STATE_CONFIGURED;
352         $type->name = "Test tool";
353         $type->description = "Example description";
354         $type->toolproxyid = $proxy->id;
355         $type->baseurl = $this->getExternalTestFileUrl('/test.html');
356         $typeid = lti_add_type($type, $data);
358         $types = mod_lti_external::get_tool_types($proxy->id);
359         $this->assertEquals(1, count($types));
360         $type = $types[0];
361         $this->assertEquals('Test tool', $type['name']);
362         $this->assertEquals('Example description', $type['description']);
363     }
365     /*
366      * Test create tool type
367      */
368     public function test_mod_lti_create_tool_type() {
369         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
370         $this->assertEquals('Example tool', $type['name']);
371         $this->assertEquals('Example tool description', $type['description']);
372         $this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type['urls']['icon']);
373         $typeentry = lti_get_type($type['id']);
374         $this->assertEquals('http://www.example.com/lti/provider.php', $typeentry->baseurl);
375         $config = lti_get_type_config($type['id']);
376         $this->assertTrue(isset($config['sendname']));
377         $this->assertTrue(isset($config['sendemailaddr']));
378         $this->assertTrue(isset($config['acceptgrades']));
379         $this->assertTrue(isset($config['forcessl']));
380     }
382     /*
383      * Test create tool type failure from non existant file
384      */
385     public function test_mod_lti_create_tool_type_nonexistant_file() {
386         $this->expectException('moodle_exception');
387         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/doesntexist.xml'), '', '');
388     }
390     /*
391      * Test create tool type failure from xml that is not a cartridge
392      */
393     public function test_mod_lti_create_tool_type_bad_file() {
394         $this->expectException('moodle_exception');
395         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/rsstest.xml'), '', '');
396     }
398     /*
399      * Test creating of tool types without sufficient capability
400      */
401     public function test_mod_lti_create_tool_type_without_capability() {
402         self::setUser($this->teacher);
403         $this->expectException('required_capability_exception');
404         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
405     }
407     /*
408      * Test update tool type
409      */
410     public function test_mod_lti_update_tool_type() {
411         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
412         $type = mod_lti_external::update_tool_type($type['id'], 'New name', 'New description', LTI_TOOL_STATE_PENDING);
413         $this->assertEquals('New name', $type['name']);
414         $this->assertEquals('New description', $type['description']);
415         $this->assertEquals('Pending', $type['state']['text']);
416     }
418     /*
419      * Test delete tool type
420      */
421     public function test_mod_lti_delete_tool_type() {
422         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
423         $this->assertNotEmpty(lti_get_type($type['id']));
424         $type = mod_lti_external::delete_tool_type($type['id']);
425         $this->assertEmpty(lti_get_type($type['id']));
426     }
428     /*
429      * Test delete tool type without sufficient capability
430      */
431     public function test_mod_lti_delete_tool_type_without_capability() {
432         $type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
433         $this->assertNotEmpty(lti_get_type($type['id']));
434         $this->expectException('required_capability_exception');
435         self::setUser($this->teacher);
436         $type = mod_lti_external::delete_tool_type($type['id']);
437     }
439     /*
440      * Test is cartridge
441      */
442     public function test_mod_lti_is_cartridge() {
443         $result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'));
444         $this->assertTrue($result['iscartridge']);
445         $result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/test.html'));
446         $this->assertFalse($result['iscartridge']);
447     }