MDL-63660 tool_dataprivacy: Increase expected export file size
[moodle.git] / mod / scorm / tests / externallib_test.php
blob00c5cdd865be8804906a6d4a64653d98b98b3f9f
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.
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
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
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
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
43 class mod_scorm_external_testcase extends externallib_advanced_testcase {
45 /**
46 * Set up for every test
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');
70 /**
71 * Test view_scorm
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);
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);
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());
118 * Test get scorm attempt count
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']);
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']);
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']);
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']);
176 * @expectedException required_capability_exception
178 public function test_mod_scorm_get_scorm_attempt_count_others_as_student() {
179 // Create a second student.
180 $student2 = self::getDataGenerator()->create_user();
181 $this->getDataGenerator()->enrol_user($student2->id, $this->course->id, $this->studentrole->id, 'manual');
183 // As a student.
184 self::setUser($student2);
186 // I should not be able to view the attempts of another student.
187 mod_scorm_external::get_scorm_attempt_count($this->scorm->id, $this->student->id);
191 * @expectedException moodle_exception
193 public function test_mod_scorm_get_scorm_attempt_count_invalid_instanceid() {
194 // As student.
195 self::setUser($this->student);
197 // Test invalid instance id.
198 mod_scorm_external::get_scorm_attempt_count(0, $this->student->id);
202 * @expectedException moodle_exception
204 public function test_mod_scorm_get_scorm_attempt_count_invalid_userid() {
205 // As student.
206 self::setUser($this->student);
208 mod_scorm_external::get_scorm_attempt_count($this->scorm->id, -1);
212 * Test get scorm scoes
214 public function test_mod_scorm_get_scorm_scoes() {
215 global $DB;
217 $this->resetAfterTest(true);
219 // Create users.
220 $student = self::getDataGenerator()->create_user();
221 $teacher = self::getDataGenerator()->create_user();
223 // Create courses to add the modules.
224 $course = self::getDataGenerator()->create_course();
226 // First scorm, dates restriction.
227 $record = new stdClass();
228 $record->course = $course->id;
229 $record->timeopen = time() + DAYSECS;
230 $record->timeclose = $record->timeopen + DAYSECS;
231 $scorm = self::getDataGenerator()->create_module('scorm', $record);
233 // Set to the student user.
234 self::setUser($student);
236 // Users enrolments.
237 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
238 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
239 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
240 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
242 // Retrieve my scoes, warning!.
243 try {
244 mod_scorm_external::get_scorm_scoes($scorm->id);
245 $this->fail('Exception expected due to invalid dates.');
246 } catch (moodle_exception $e) {
247 $this->assertEquals('notopenyet', $e->errorcode);
250 $scorm->timeopen = time() - DAYSECS;
251 $scorm->timeclose = time() - HOURSECS;
252 $DB->update_record('scorm', $scorm);
254 try {
255 mod_scorm_external::get_scorm_scoes($scorm->id);
256 $this->fail('Exception expected due to invalid dates.');
257 } catch (moodle_exception $e) {
258 $this->assertEquals('expired', $e->errorcode);
261 // Retrieve my scoes, user with permission.
262 self::setUser($teacher);
263 $result = mod_scorm_external::get_scorm_scoes($scorm->id);
264 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
265 $this->assertCount(2, $result['scoes']);
266 $this->assertCount(0, $result['warnings']);
268 $scoes = scorm_get_scoes($scorm->id);
269 $sco = array_shift($scoes);
270 $sco->extradata = array();
271 $this->assertEquals((array) $sco, $result['scoes'][0]);
273 $sco = array_shift($scoes);
274 $sco->extradata = array();
275 $sco->extradata[] = array(
276 'element' => 'isvisible',
277 'value' => $sco->isvisible
279 $sco->extradata[] = array(
280 'element' => 'parameters',
281 'value' => $sco->parameters
283 unset($sco->isvisible);
284 unset($sco->parameters);
286 // Sort the array (if we don't sort tests will fails for Postgres).
287 usort($result['scoes'][1]['extradata'], function($a, $b) {
288 return strcmp($a['element'], $b['element']);
291 $this->assertEquals((array) $sco, $result['scoes'][1]);
293 // Use organization.
294 $organization = 'golf_sample_default_org';
295 $result = mod_scorm_external::get_scorm_scoes($scorm->id, $organization);
296 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
297 $this->assertCount(1, $result['scoes']);
298 $this->assertEquals($organization, $result['scoes'][0]['organization']);
299 $this->assertCount(0, $result['warnings']);
301 // Test invalid instance id.
302 try {
303 mod_scorm_external::get_scorm_scoes(0);
304 $this->fail('Exception expected due to invalid instance id.');
305 } catch (moodle_exception $e) {
306 $this->assertEquals('invalidrecord', $e->errorcode);
312 * Test get scorm scoes (with a complex SCORM package)
314 public function test_mod_scorm_get_scorm_scoes_complex_package() {
315 global $CFG;
317 // As student.
318 self::setUser($this->student);
320 $record = new stdClass();
321 $record->course = $this->course->id;
322 $record->packagefilepath = $CFG->dirroot.'/mod/scorm/tests/packages/complexscorm.zip';
323 $scorm = self::getDataGenerator()->create_module('scorm', $record);
325 $result = mod_scorm_external::get_scorm_scoes($scorm->id);
326 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_scoes_returns(), $result);
327 $this->assertCount(9, $result['scoes']);
328 $this->assertCount(0, $result['warnings']);
330 $expectedscoes = array();
331 $scoreturnstructure = mod_scorm_external::get_scorm_scoes_returns();
332 $scoes = scorm_get_scoes($scorm->id);
333 foreach ($scoes as $sco) {
334 $sco->extradata = array();
335 foreach ($sco as $element => $value) {
336 // Add the extra data to the extradata array and remove the object element.
337 if (!isset($scoreturnstructure->keys['scoes']->content->keys[$element])) {
338 $sco->extradata[] = array(
339 'element' => $element,
340 'value' => $value
342 unset($sco->{$element});
345 $expectedscoes[] = (array) $sco;
348 $this->assertEquals($expectedscoes, $result['scoes']);
352 * Test get scorm user data
354 public function test_mod_scorm_get_scorm_user_data() {
355 global $DB;
357 $this->resetAfterTest(true);
359 // Create users.
360 $student1 = self::getDataGenerator()->create_user();
361 $teacher = self::getDataGenerator()->create_user();
363 // Set to the student user.
364 self::setUser($student1);
366 // Create courses to add the modules.
367 $course = self::getDataGenerator()->create_course();
369 // First scorm.
370 $record = new stdClass();
371 $record->course = $course->id;
372 $scorm = self::getDataGenerator()->create_module('scorm', $record);
374 // Users enrolments.
375 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
376 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
377 $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id, 'manual');
378 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
380 // Create attempts.
381 $scoes = scorm_get_scoes($scorm->id);
382 $sco = array_shift($scoes);
383 scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
384 scorm_insert_track($student1->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80');
385 scorm_insert_track($student1->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
387 $result = mod_scorm_external::get_scorm_user_data($scorm->id, 1);
388 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_user_data_returns(), $result);
389 $this->assertCount(2, $result['data']);
390 // Find our tracking data.
391 $found = 0;
392 foreach ($result['data'] as $scodata) {
393 foreach ($scodata['userdata'] as $userdata) {
394 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
395 $found++;
397 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
398 $found++;
402 $this->assertEquals(2, $found);
404 // Test invalid instance id.
405 try {
406 mod_scorm_external::get_scorm_user_data(0, 1);
407 $this->fail('Exception expected due to invalid instance id.');
408 } catch (moodle_exception $e) {
409 $this->assertEquals('invalidrecord', $e->errorcode);
414 * Test insert scorm tracks
416 public function test_mod_scorm_insert_scorm_tracks() {
417 global $DB;
419 $this->resetAfterTest(true);
421 // Create users.
422 $student = self::getDataGenerator()->create_user();
424 // Create courses to add the modules.
425 $course = self::getDataGenerator()->create_course();
427 // First scorm, dates restriction.
428 $record = new stdClass();
429 $record->course = $course->id;
430 $record->timeopen = time() + DAYSECS;
431 $record->timeclose = $record->timeopen + DAYSECS;
432 $scorm = self::getDataGenerator()->create_module('scorm', $record);
434 // Get a SCO.
435 $scoes = scorm_get_scoes($scorm->id);
436 $sco = array_shift($scoes);
438 // Tracks.
439 $tracks = array();
440 $tracks[] = array(
441 'element' => 'cmi.core.lesson_status',
442 'value' => 'completed'
444 $tracks[] = array(
445 'element' => 'cmi.core.score.raw',
446 'value' => '80'
449 // Set to the student user.
450 self::setUser($student);
452 // Users enrolments.
453 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
454 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
456 // Exceptions first.
457 try {
458 mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
459 $this->fail('Exception expected due to dates');
460 } catch (moodle_exception $e) {
461 $this->assertEquals('notopenyet', $e->errorcode);
464 $scorm->timeopen = time() - DAYSECS;
465 $scorm->timeclose = time() - HOURSECS;
466 $DB->update_record('scorm', $scorm);
468 try {
469 mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
470 $this->fail('Exception expected due to dates');
471 } catch (moodle_exception $e) {
472 $this->assertEquals('expired', $e->errorcode);
475 // Test invalid instance id.
476 try {
477 mod_scorm_external::insert_scorm_tracks(0, 1, $tracks);
478 $this->fail('Exception expected due to invalid sco id.');
479 } catch (moodle_exception $e) {
480 $this->assertEquals('cannotfindsco', $e->errorcode);
483 $scorm->timeopen = 0;
484 $scorm->timeclose = 0;
485 $DB->update_record('scorm', $scorm);
487 // Retrieve my tracks.
488 $result = mod_scorm_external::insert_scorm_tracks($sco->id, 1, $tracks);
489 $result = external_api::clean_returnvalue(mod_scorm_external::insert_scorm_tracks_returns(), $result);
490 $this->assertCount(0, $result['warnings']);
492 $trackids = $DB->get_records('scorm_scoes_track', array('userid' => $student->id, 'scoid' => $sco->id,
493 'scormid' => $scorm->id, 'attempt' => 1));
494 // We use asort here to prevent problems with ids ordering.
495 $expectedkeys = array_keys($trackids);
496 $this->assertEquals(asort($expectedkeys), asort($result['trackids']));
500 * Test get scorm sco tracks
502 public function test_mod_scorm_get_scorm_sco_tracks() {
503 global $DB;
505 $this->resetAfterTest(true);
507 // Create users.
508 $student = self::getDataGenerator()->create_user();
509 $otherstudent = self::getDataGenerator()->create_user();
510 $teacher = self::getDataGenerator()->create_user();
512 // Set to the student user.
513 self::setUser($student);
515 // Create courses to add the modules.
516 $course = self::getDataGenerator()->create_course();
518 // First scorm.
519 $record = new stdClass();
520 $record->course = $course->id;
521 $scorm = self::getDataGenerator()->create_module('scorm', $record);
523 // Users enrolments.
524 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
525 $teacherrole = $DB->get_record('role', array('shortname' => 'teacher'));
526 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
527 $this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
529 // Create attempts.
530 $scoes = scorm_get_scoes($scorm->id);
531 $sco = array_shift($scoes);
532 scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.lesson_status', 'completed');
533 scorm_insert_track($student->id, $scorm->id, $sco->id, 1, 'cmi.core.score.raw', '80');
534 scorm_insert_track($student->id, $scorm->id, $sco->id, 2, 'cmi.core.lesson_status', 'completed');
536 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 1);
537 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
538 // 7 default elements + 2 custom ones.
539 $this->assertCount(9, $result['data']['tracks']);
540 $this->assertEquals(1, $result['data']['attempt']);
541 $this->assertCount(0, $result['warnings']);
542 // Find our tracking data.
543 $found = 0;
544 foreach ($result['data']['tracks'] as $userdata) {
545 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
546 $found++;
548 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
549 $found++;
552 $this->assertEquals(2, $found);
554 // Try invalid attempt.
555 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id, 10);
556 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
557 $this->assertCount(0, $result['data']['tracks']);
558 $this->assertEquals(10, $result['data']['attempt']);
559 $this->assertCount(1, $result['warnings']);
560 $this->assertEquals('notattempted', $result['warnings'][0]['warningcode']);
562 // Capabilities check.
563 try {
564 mod_scorm_external::get_scorm_sco_tracks($sco->id, $otherstudent->id);
565 $this->fail('Exception expected due to invalid instance id.');
566 } catch (required_capability_exception $e) {
567 $this->assertEquals('nopermissions', $e->errorcode);
570 self::setUser($teacher);
571 // Ommit the attempt parameter, the function should calculate the last attempt.
572 $result = mod_scorm_external::get_scorm_sco_tracks($sco->id, $student->id);
573 $result = external_api::clean_returnvalue(mod_scorm_external::get_scorm_sco_tracks_returns(), $result);
574 // 7 default elements + 1 custom one.
575 $this->assertCount(8, $result['data']['tracks']);
576 $this->assertEquals(2, $result['data']['attempt']);
578 // Test invalid instance id.
579 try {
580 mod_scorm_external::get_scorm_sco_tracks(0, 1);
581 $this->fail('Exception expected due to invalid instance id.');
582 } catch (moodle_exception $e) {
583 $this->assertEquals('cannotfindsco', $e->errorcode);
585 // Invalid user.
586 try {
587 mod_scorm_external::get_scorm_sco_tracks($sco->id, 0);
588 $this->fail('Exception expected due to invalid instance id.');
589 } catch (moodle_exception $e) {
590 $this->assertEquals('invaliduser', $e->errorcode);
595 * Test get scorms by courses
597 public function test_mod_scorm_get_scorms_by_courses() {
598 global $DB;
600 $this->resetAfterTest(true);
602 // Create users.
603 $student = self::getDataGenerator()->create_user();
604 $teacher = self::getDataGenerator()->create_user();
606 // Set to the student user.
607 self::setUser($student);
609 // Create courses to add the modules.
610 $course1 = self::getDataGenerator()->create_course();
611 $course2 = self::getDataGenerator()->create_course();
613 // First scorm.
614 $record = new stdClass();
615 $record->introformat = FORMAT_HTML;
616 $record->course = $course1->id;
617 $record->hidetoc = 2;
618 $record->displayattemptstatus = 2;
619 $record->skipview = 2;
620 $scorm1 = self::getDataGenerator()->create_module('scorm', $record);
622 // Second scorm.
623 $record = new stdClass();
624 $record->introformat = FORMAT_HTML;
625 $record->course = $course2->id;
626 $scorm2 = self::getDataGenerator()->create_module('scorm', $record);
628 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
629 $teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
631 // Users enrolments.
632 $this->getDataGenerator()->enrol_user($student->id, $course1->id, $studentrole->id, 'manual');
633 $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id, 'manual');
635 // Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
636 $enrol = enrol_get_plugin('manual');
637 $enrolinstances = enrol_get_instances($course2->id, true);
638 foreach ($enrolinstances as $courseenrolinstance) {
639 if ($courseenrolinstance->enrol == "manual") {
640 $instance2 = $courseenrolinstance;
641 break;
644 $enrol->enrol_user($instance2, $student->id, $studentrole->id);
646 $returndescription = mod_scorm_external::get_scorms_by_courses_returns();
648 // Test open/close dates.
650 $timenow = time();
651 $scorm1->timeopen = $timenow - DAYSECS;
652 $scorm1->timeclose = $timenow - HOURSECS;
653 $DB->update_record('scorm', $scorm1);
655 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id));
656 $result = external_api::clean_returnvalue($returndescription, $result);
657 $this->assertCount(1, $result['warnings']);
658 // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'.
659 $this->assertCount(7, $result['scorms'][0]);
660 $this->assertEquals('expired', $result['warnings'][0]['warningcode']);
662 $scorm1->timeopen = $timenow + DAYSECS;
663 $scorm1->timeclose = $scorm1->timeopen + DAYSECS;
664 $DB->update_record('scorm', $scorm1);
666 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id));
667 $result = external_api::clean_returnvalue($returndescription, $result);
668 $this->assertCount(1, $result['warnings']);
669 // Only 'id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles'.
670 $this->assertCount(7, $result['scorms'][0]);
671 $this->assertEquals('notopenyet', $result['warnings'][0]['warningcode']);
673 // Reset times.
674 $scorm1->timeopen = 0;
675 $scorm1->timeclose = 0;
676 $DB->update_record('scorm', $scorm1);
678 // Create what we expect to be returned when querying the two courses.
679 // First for the student user.
680 $expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'version', 'maxgrade',
681 'grademethod', 'whatgrade', 'maxattempt', 'forcecompleted', 'forcenewattempt', 'lastattemptlock',
682 'displayattemptstatus', 'displaycoursestructure', 'sha1hash', 'md5hash', 'revision', 'launch',
683 'skipview', 'hidebrowse', 'hidetoc', 'nav', 'navpositionleft', 'navpositiontop', 'auto',
684 'popup', 'width', 'height', 'timeopen', 'timeclose', 'displayactivityname', 'packagesize',
685 'packageurl', 'scormtype', 'reference');
687 // Add expected coursemodule and data.
688 $scorm1->coursemodule = $scorm1->cmid;
689 $scorm1->section = 0;
690 $scorm1->visible = true;
691 $scorm1->groupmode = 0;
692 $scorm1->groupingid = 0;
694 $scorm2->coursemodule = $scorm2->cmid;
695 $scorm2->section = 0;
696 $scorm2->visible = true;
697 $scorm2->groupmode = 0;
698 $scorm2->groupingid = 0;
700 // SCORM size. The same package is used in both SCORMs.
701 $scormcontext1 = context_module::instance($scorm1->cmid);
702 $scormcontext2 = context_module::instance($scorm2->cmid);
703 $fs = get_file_storage();
704 $packagefile = $fs->get_file($scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference);
705 $packagesize = $packagefile->get_filesize();
707 $packageurl1 = moodle_url::make_webservice_pluginfile_url(
708 $scormcontext1->id, 'mod_scorm', 'package', 0, '/', $scorm1->reference)->out(false);
709 $packageurl2 = moodle_url::make_webservice_pluginfile_url(
710 $scormcontext2->id, 'mod_scorm', 'package', 0, '/', $scorm2->reference)->out(false);
712 $scorm1->packagesize = $packagesize;
713 $scorm1->packageurl = $packageurl1;
714 $scorm2->packagesize = $packagesize;
715 $scorm2->packageurl = $packageurl2;
717 // Forced to boolean as it is returned as PARAM_BOOL.
718 $protectpackages = (bool)get_config('scorm', 'protectpackagedownloads');
719 $expected1 = array('protectpackagedownloads' => $protectpackages);
720 $expected2 = array('protectpackagedownloads' => $protectpackages);
721 foreach ($expectedfields as $field) {
723 // Since we return the fields used as boolean as PARAM_BOOL instead PARAM_INT we need to force casting here.
724 // From the returned fields definition we obtain the type expected for the field.
725 if (empty($returndescription->keys['scorms']->content->keys[$field]->type)) {
726 continue;
728 $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type;
729 if ($fieldtype == PARAM_BOOL) {
730 $expected1[$field] = (bool) $scorm1->{$field};
731 $expected2[$field] = (bool) $scorm2->{$field};
732 } else {
733 $expected1[$field] = $scorm1->{$field};
734 $expected2[$field] = $scorm2->{$field};
737 $expected1['introfiles'] = [];
738 $expected2['introfiles'] = [];
740 $expectedscorms = array();
741 $expectedscorms[] = $expected2;
742 $expectedscorms[] = $expected1;
744 // Call the external function passing course ids.
745 $result = mod_scorm_external::get_scorms_by_courses(array($course2->id, $course1->id));
746 $result = external_api::clean_returnvalue($returndescription, $result);
747 $this->assertEquals($expectedscorms, $result['scorms']);
749 // Call the external function without passing course id.
750 $result = mod_scorm_external::get_scorms_by_courses();
751 $result = external_api::clean_returnvalue($returndescription, $result);
752 $this->assertEquals($expectedscorms, $result['scorms']);
754 // Unenrol user from second course and alter expected scorms.
755 $enrol->unenrol_user($instance2, $student->id);
756 array_shift($expectedscorms);
758 // Call the external function without passing course id.
759 $result = mod_scorm_external::get_scorms_by_courses();
760 $result = external_api::clean_returnvalue($returndescription, $result);
761 $this->assertEquals($expectedscorms, $result['scorms']);
763 // Call for the second course we unenrolled the user from, expected warning.
764 $result = mod_scorm_external::get_scorms_by_courses(array($course2->id));
765 $this->assertCount(1, $result['warnings']);
766 $this->assertEquals('1', $result['warnings'][0]['warningcode']);
767 $this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
769 // Now, try as a teacher for getting all the additional fields.
770 self::setUser($teacher);
772 $additionalfields = array('updatefreq', 'timemodified', 'options',
773 'completionstatusrequired', 'completionscorerequired', 'completionstatusallscos',
774 'autocommit', 'section', 'visible', 'groupmode', 'groupingid');
776 foreach ($additionalfields as $field) {
777 $fieldtype = $returndescription->keys['scorms']->content->keys[$field]->type;
779 if ($fieldtype == PARAM_BOOL) {
780 $expectedscorms[0][$field] = (bool) $scorm1->{$field};
781 } else {
782 $expectedscorms[0][$field] = $scorm1->{$field};
786 $result = mod_scorm_external::get_scorms_by_courses();
787 $result = external_api::clean_returnvalue($returndescription, $result);
788 $this->assertEquals($expectedscorms, $result['scorms']);
790 // Even with the SCORM closed in time teacher should retrieve the info.
791 $scorm1->timeopen = $timenow - DAYSECS;
792 $scorm1->timeclose = $timenow - HOURSECS;
793 $DB->update_record('scorm', $scorm1);
795 $expectedscorms[0]['timeopen'] = $scorm1->timeopen;
796 $expectedscorms[0]['timeclose'] = $scorm1->timeclose;
798 $result = mod_scorm_external::get_scorms_by_courses();
799 $result = external_api::clean_returnvalue($returndescription, $result);
800 $this->assertEquals($expectedscorms, $result['scorms']);
802 // Admin also should get all the information.
803 self::setAdminUser();
805 $result = mod_scorm_external::get_scorms_by_courses(array($course1->id));
806 $result = external_api::clean_returnvalue($returndescription, $result);
807 $this->assertEquals($expectedscorms, $result['scorms']);
811 * Test launch_sco
813 public function test_launch_sco() {
814 global $DB;
816 // Test invalid instance id.
817 try {
818 mod_scorm_external::launch_sco(0);
819 $this->fail('Exception expected due to invalid mod_scorm instance id.');
820 } catch (moodle_exception $e) {
821 $this->assertEquals('invalidrecord', $e->errorcode);
824 // Test not-enrolled user.
825 $user = self::getDataGenerator()->create_user();
826 $this->setUser($user);
827 try {
828 mod_scorm_external::launch_sco($this->scorm->id);
829 $this->fail('Exception expected due to not enrolled user.');
830 } catch (moodle_exception $e) {
831 $this->assertEquals('requireloginerror', $e->errorcode);
834 // Test user with full capabilities.
835 $this->setUser($this->student);
837 // Trigger and capture the event.
838 $sink = $this->redirectEvents();
840 $scoes = scorm_get_scoes($this->scorm->id);
841 foreach ($scoes as $sco) {
842 // Find launchable SCO.
843 if ($sco->launch != '') {
844 break;
848 $result = mod_scorm_external::launch_sco($this->scorm->id, $sco->id);
849 $result = external_api::clean_returnvalue(mod_scorm_external::launch_sco_returns(), $result);
851 $events = $sink->get_events();
852 $this->assertCount(1, $events);
853 $event = array_shift($events);
855 // Checking that the event contains the expected values.
856 $this->assertInstanceOf('\mod_scorm\event\sco_launched', $event);
857 $this->assertEquals($this->context, $event->get_context());
858 $moodleurl = new \moodle_url('/mod/scorm/player.php', array('cm' => $this->cm->id, 'scoid' => $sco->id));
859 $this->assertEquals($moodleurl, $event->get_url());
860 $this->assertEventContextNotUsed($event);
861 $this->assertNotEmpty($event->get_name());
863 // Invalid SCO.
864 try {
865 mod_scorm_external::launch_sco($this->scorm->id, -1);
866 $this->fail('Exception expected due to invalid SCO id.');
867 } catch (moodle_exception $e) {
868 $this->assertEquals('cannotfindsco', $e->errorcode);