MDL-50557 mod_scorm: New WS mod_scorm_get_scorm_sco_tracks
[moodle.git] / mod / scorm / 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  * SCORM module external functions tests
19  *
20  * @package    mod_scorm
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/scorm/lib.php');
34 /**
35  * SCORM module external functions tests
36  *
37  * @package    mod_scorm
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_scorm_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->scorm = $this->getDataGenerator()->create_module('scorm', array('course' => $this->course->id));
56         $this->context = context_module::instance($this->scorm->cmid);
57         $this->cm = get_coursemodule_from_instance('scorm', $this->scorm->id);
59         // Create users.
60         $this->student = self::getDataGenerator()->create_user();
61         $this->teacher = self::getDataGenerator()->create_user();
63         // Users enrolments.
64         $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
65         $this->teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
66         $this->getDataGenerator()->enrol_user($this->student->id, $this->course->id, $this->studentrole->id, 'manual');
67         $this->getDataGenerator()->enrol_user($this->teacher->id, $this->course->id, $this->teacherrole->id, 'manual');
68     }
70     /**
71      * Test view_scorm
72      */
73     public function test_view_scorm() {
74         global $DB;
76         // Test invalid instance id.
77         try {
78             mod_scorm_external::view_scorm(0);
79             $this->fail('Exception expected due to invalid mod_scorm instance id.');
80         } catch (moodle_exception $e) {
81             $this->assertEquals('invalidrecord', $e->errorcode);
82         }
84         // Test not-enrolled user.
85         $user = self::getDataGenerator()->create_user();
86         $this->setUser($user);
87         try {
88             mod_scorm_external::view_scorm($this->scorm->id);
89             $this->fail('Exception expected due to not enrolled user.');
90         } catch (moodle_exception $e) {
91             $this->assertEquals('requireloginerror', $e->errorcode);
92         }
94         // Test user with full capabilities.
95         $this->studentrole = $DB->get_record('role', array('shortname' => 'student'));
96         $this->getDataGenerator()->enrol_user($user->id, $this->course->id, $this->studentrole->id);
98         // Trigger and capture the event.
99         $sink = $this->redirectEvents();
101         $result = mod_scorm_external::view_scorm($this->scorm->id);
102         $result = external_api::clean_returnvalue(mod_scorm_external::view_scorm_returns(), $result);
104         $events = $sink->get_events();
105         $this->assertCount(1, $events);
106         $event = array_shift($events);
108         // Checking that the event contains the expected values.
109         $this->assertInstanceOf('\mod_scorm\event\course_module_viewed', $event);
110         $this->assertEquals($this->context, $event->get_context());
111         $moodleurl = new \moodle_url('/mod/scorm/view.php', array('id' => $this->cm->id));
112         $this->assertEquals($moodleurl, $event->get_url());
113         $this->assertEventContextNotUsed($event);
114         $this->assertNotEmpty($event->get_name());
115     }
117     /**
118      * Test get scorm attempt count
119      */
120     public function test_mod_scorm_get_scorm_attempt_count_own_empty() {
121         // Set to the student user.
122         self::setUser($this->student);
124         // Retrieve my attempts (should be 0).
125         $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
126         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
127         $this->assertEquals(0, $result['attemptscount']);
128     }
130     public function test_mod_scorm_get_scorm_attempt_count_own_with_complete() {
131         // Set to the student user.
132         self::setUser($this->student);
134         // Create attempts.
135         $scoes = scorm_get_scoes($this->scorm->id);
136         $sco = array_shift($scoes);
137         scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
138         scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
140         $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
141         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
142         $this->assertEquals(2, $result['attemptscount']);
143     }
145     public function test_mod_scorm_get_scorm_attempt_count_own_incomplete() {
146         // Set to the student user.
147         self::setUser($this->student);
149         // Create a complete attempt, and an incomplete attempt.
150         $scoes = scorm_get_scoes($this->scorm->id);
151         $sco = array_shift($scoes);
152         scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
153         scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 2, 'cmi.core.credit', '0');
155         $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id, true);
156         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
157         $this->assertEquals(1, $result['attemptscount']);
158     }
160     public function test_mod_scorm_get_scorm_attempt_count_others_as_teacher() {
161         // As a teacher.
162         self::setUser($this->teacher);
164         // Create a completed attempt for student.
165         $scoes = scorm_get_scoes($this->scorm->id);
166         $sco = array_shift($scoes);
167         scorm_insert_track($this->student->id, $this->scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
169         // I should be able to view the attempts for my students.
170         $result = mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
171         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_attempt_count_returns(), $result);
172         $this->assertEquals(1, $result['attemptscount']);
173     }
175     public function test_mod_scorm_get_scorm_attempt_count_others_as_student() {
176         // Create a second student.
177         $student2 = self::getDataGenerator()->create_user();
178         $this->getDataGenerator()->enrol_user($student2->id, $this->course->id, $this->studentrole->id, 'manual');
180         // As a student.
181         self::setUser($student2);
183         // I should not be able to view the attempts of another student.
184         $this->setExpectedException('required_capability_exception');
185         mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
186     }
188     public function test_mod_scorm_get_scorm_attempt_count_invalid_instanceid() {
189         // As student.
190         self::setUser($this->student);
192         // Test invalid instance id.
193         $this->setExpectedException('moodle_exception');
194         mod_scorm_external::get_scorm_attempt_count(0, $this->student->id);
195     }
197     public function test_mod_scorm_get_scorm_attempt_count_invalid_userid() {
198         // As student.
199         self::setUser($this->student);
201         $this->setExpectedException('moodle_exception');
202         mod_scorm_external::get_scorm_attempt_count($this->scorm->id, -1);
203     }
205     /**
206      * Test get scorm scoes
207      */
208     public function test_mod_scorm_get_scorm_scoes() {
209         global $DB;
211         $this->resetAfterTest(true);
213         // Create users.
214         $student = self::getDataGenerator()->create_user();
215         $teacher = self::getDataGenerator()->create_user();
217         // Set to the student user.
218         self::setUser($student);
220         // Create courses to add the modules.
221         $course = self::getDataGenerator()->create_course();
223         // First scorm, dates restriction.
224         $record = new stdClass();
225         $record->course = $course->id;
226         $record->timeopen = time() + DAYSECS;
227         $record->timeclose = $record->timeopen + DAYSECS;
228         $scorm = self::getDataGenerator()->create_module('scorm', $record);
230         // Users enrolments.
231         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
232         $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
233         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
234         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
236         // Retrieve my scoes, warning!.
237         try {
238              mod_scorm_external::get_scorm_scoes($scorm->id);
239             $this->fail('Exception expected due to invalid dates.');
240         } catch (moodle_exception $e) {
241             $this->assertEquals('notopenyet', $e->errorcode);
242         }
244         $scorm->timeopen = time() - DAYSECS;
245         $scorm->timeclose = time() - HOURSECS;
246         $DB->update_record('scorm', $scorm);
248         try {
249              mod_scorm_external::get_scorm_scoes($scorm->id);
250             $this->fail('Exception expected due to invalid dates.');
251         } catch (moodle_exception $e) {
252             $this->assertEquals('expired', $e->errorcode);
253         }
255         // Retrieve my scoes, user with permission.
256         self::setUser($teacher);
257         $result = mod_scorm_external::get_scorm_scoes($scorm->id);
258         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
259         $this->assertCount(2, $result['scoes']);
260         $this->assertCount(0, $result['warnings']);
262         $scoes = scorm_get_scoes($scorm->id);
263         $sco = array_shift($scoes);
264         $this->assertEquals((array) $sco, $result['scoes'][0]);
266         $sco = array_shift($scoes);
267         // Remove specific sco data.
268         unset($sco->isvisible);
269         unset($sco->parameters);
270         $this->assertEquals((array) $sco, $result['scoes'][1]);
272         // Use organization.
273         $organization = 'golf_sample_default_org';
274         $result = mod_scorm_external::get_scorm_scoes($scorm->id, $organization);
275         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
276         $this->assertCount(1, $result['scoes']);
277         $this->assertEquals($organization, $result['scoes'][0]['organization']);
278         $this->assertCount(0, $result['warnings']);
280         // Test invalid instance id.
281         try {
282              mod_scorm_external::get_scorm_scoes(0);
283             $this->fail('Exception expected due to invalid instance id.');
284         } catch (moodle_exception $e) {
285             $this->assertEquals('invalidrecord', $e->errorcode);
286         }
287     }
289     /*
290      * Test get scorm user data
291      */
292     public function test_mod_scorm_get_scorm_user_data() {
293         global $DB;
295         $this->resetAfterTest(true);
297         // Create users.
298         $student1 = self::getDataGenerator()->create_user();
299         $teacher = self::getDataGenerator()->create_user();
301         // Set to the student user.
302         self::setUser($student1);
304         // Create courses to add the modules.
305         $course = self::getDataGenerator()->create_course();
307         // First scorm.
308         $record = new stdClass();
309         $record->course = $course->id;
310         $scorm = self::getDataGenerator()->create_module('scorm', $record);
312         // Users enrolments.
313         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
314         $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
315         $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id, 'manual');
316         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
318         // Create attempts.
319         $scoes = scorm_get_scoes($scorm->id);
320         $sco = array_shift($scoes);
321         scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
322         scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80');
323         scorm_insert_track($student1->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
325         $result = mod_scorm_external::get_scorm_user_data($scorm->id, 1);
326         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_user_data_returns(), $result);
327         $this->assertCount(2, $result['data']);
328         // Find our tracking data.
329         $found = 0;
330         foreach ($result['data'] as $scodata) {
331             foreach ($scodata['userdata'] as $userdata) {
332                 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
333                     $found++;
334                 }
335                 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
336                     $found++;
337                 }
338             }
339         }
340         $this->assertEquals(2, $found);
342         // Test invalid instance id.
343         try {
344              mod_scorm_external::get_scorm_user_data(0, 1);
345             $this->fail('Exception expected due to invalid instance id.');
346         } catch (moodle_exception $e) {
347             $this->assertEquals('invalidrecord', $e->errorcode);
348         }
349     }
351     /**
352      * Test insert scorm tracks
353      */
354     public function test_mod_scorm_insert_scorm_tracks() {
355         global $DB;
357         $this->resetAfterTest(true);
359         // Create users.
360         $student = self::getDataGenerator()->create_user();
362         // Set to the student user.
363         self::setUser($student);
365         // Create courses to add the modules.
366         $course = self::getDataGenerator()->create_course();
368         // First scorm, dates restriction.
369         $record = new stdClass();
370         $record->course = $course->id;
371         $record->timeopen = time() + DAYSECS;
372         $record->timeclose = $record->timeopen + DAYSECS;
373         $scorm = self::getDataGenerator()->create_module('scorm', $record);
375         // Get a SCO.
376         $scoes = scorm_get_scoes($scorm->id);
377         $sco = array_shift($scoes);
379         // Tracks.
380         $tracks = array();
381         $tracks[] = array(
382             'element' => 'cmi.core.lesson_status',
383             'value' => 'completed'
384         );
385         $tracks[] = array(
386             'element' => 'cmi.core.score.raw',
387             'value' => '80'
388         );
390         // Users enrolments.
391         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
392         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
394         // Exceptions first.
395         try {
396             mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
397             $this->fail('Exception expected due to dates');
398         } catch (moodle_exception $e) {
399             $this->assertEquals('notopenyet', $e->errorcode);
400         }
402         $scorm->timeopen = time() - DAYSECS;
403         $scorm->timeclose = time() - HOURSECS;
404         $DB->update_record('scorm', $scorm);
406         try {
407             mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
408             $this->fail('Exception expected due to dates');
409         } catch (moodle_exception $e) {
410             $this->assertEquals('expired', $e->errorcode);
411         }
413         // Test invalid instance id.
414         try {
415              mod_scorm_external::insert_scorm_tracks(0, 1, $tracks);
416             $this->fail('Exception expected due to invalid sco id.');
417         } catch (moodle_exception $e) {
418             $this->assertEquals('cannotfindsco', $e->errorcode);
419         }
421         $scorm->timeopen = 0;
422         $scorm->timeclose = 0;
423         $DB->update_record('scorm', $scorm);
425         // Retrieve my tracks.
426         $result = mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
427         $result = external_api::clean_returnvalue(mod_scorm_external::insert_scorm_tracks_returns(), $result);
428         $this->assertCount(0, $result['warnings']);
430         $trackids = $DB->get_records('scorm_scoes_track', array('userid' => $student->id, 'scoid' => $sco->id,
431                                                                 'scormid' => $scorm->id, 'attempt' => 1));
432         // We use asort here to prevent problems with ids ordering.
433         $expectedkeys = array_keys($trackids);
434         $this->assertEquals(asort($expectedkeys), asort($result['trackids']));
435     }
437     /**
438      * Test get scorm sco tracks
439      */
440     public function test_mod_scorm_get_scorm_sco_tracks() {
441         global $DB;
443         $this->resetAfterTest(true);
445         // Create users.
446         $student = self::getDataGenerator()->create_user();
447         $otherstudent = self::getDataGenerator()->create_user();
448         $teacher = self::getDataGenerator()->create_user();
450         // Set to the student user.
451         self::setUser($student);
453         // Create courses to add the modules.
454         $course = self::getDataGenerator()->create_course();
456         // First scorm.
457         $record = new stdClass();
458         $record->course = $course->id;
459         $scorm = self::getDataGenerator()->create_module('scorm', $record);
461         // Users enrolments.
462         $studentrole = $DB->get_record('role', array('shortname' => 'student'));
463         $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
464         $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
465         $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
467         // Create attempts.
468         $scoes = scorm_get_scoes($scorm->id);
469         $sco = array_shift($scoes);
470         scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
471         scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80');
472         scorm_insert_track($student->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
474         $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 1);
475         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
476         // 7 default elements + 2 custom ones.
477         $this->assertCount(9, $result['data']);
478         // Find our tracking data.
479         $found = 0;
480         foreach ($result['data'] as $userdata) {
481             if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
482                 $found++;
483             }
484             if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
485                 $found++;
486             }
487         }
488         $this->assertEquals(2, $found);
490         // Capabilities check.
491         try {
492              mod_scorm_external::get_scorm_sco_tracks($sco->id, $otherstudent->id);
493             $this->fail('Exception expected due to invalid instance id.');
494         } catch (required_capability_exception $e) {
495             $this->assertEquals('nopermissions', $e->errorcode);
496         }
498         self::setUser($teacher);
499         $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id);
500         $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
501         // 7 default elements + 1 custom one.
502         $this->assertCount(8, $result['data']);
504         // Test invalid instance id.
505         try {
506              mod_scorm_external::get_scorm_sco_tracks(0, 1);
507             $this->fail('Exception expected due to invalid instance id.');
508         } catch (moodle_exception $e) {
509             $this->assertEquals('cannotfindsco', $e->errorcode);
510         }
511         // Invalid user.
512         try {
513              mod_scorm_external::get_scorm_sco_tracks($sco->id, 0);
514             $this->fail('Exception expected due to invalid instance id.');
515         } catch (moodle_exception $e) {
516             $this->assertEquals('invaliduser', $e->errorcode);
517         }
518     }