2 // This file is part of Moodle - http://moodle.org/
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.
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/>.
18 * SCORM module external functions tests
22 * @copyright 2015 Juan Leyva <juan@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') ||
die();
31 require_once($CFG->dirroot
. '/webservice/tests/helpers.php');
32 require_once($CFG->dirroot
. '/mod/scorm/lib.php');
35 * SCORM module external functions tests
39 * @copyright 2015 Juan Leyva <juan@moodle.com>
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
43 class mod_scorm_external_testcase
extends externallib_advanced_testcase
{
46 * Set up for every test
48 public function setUp() {
50 $this->resetAfterTest();
51 $this->setAdminUser();
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
);
60 $this->student
= self
::getDataGenerator()->create_user();
61 $this->teacher
= self
::getDataGenerator()->create_user();
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');
73 public function test_view_scorm() {
76 // Test invalid instance id.
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);
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
);
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() {
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');
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() {
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() {
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() {
217 $this->resetAfterTest(true);
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);
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!.
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);
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]);
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.
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() {
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,
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() {
357 $this->resetAfterTest(true);
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();
370 $record = new stdClass();
371 $record->course
= $course->id
;
372 $scorm = self
::getDataGenerator()->create_module('scorm', $record);
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');
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.
392 foreach ($result['data'] as $scodata) {
393 foreach ($scodata['userdata'] as $userdata) {
394 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
397 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
402 $this->assertEquals(2, $found);
404 // Test invalid instance id.
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() {
419 $this->resetAfterTest(true);
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);
435 $scoes = scorm_get_scoes($scorm->id
);
436 $sco = array_shift($scoes);
441 'element' => 'cmi.core.lesson_status',
442 'value' => 'completed'
445 'element' => 'cmi.core.score.raw',
449 // Set to the student user.
450 self
::setUser($student);
453 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
454 $this->getDataGenerator()->enrol_user($student->id
, $course->id
, $studentrole->id
, 'manual');
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);
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.
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() {
505 $this->resetAfterTest(true);
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();
519 $record = new stdClass();
520 $record->course
= $course->id
;
521 $scorm = self
::getDataGenerator()->create_module('scorm', $record);
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');
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.
544 foreach ($result['data']['tracks'] as $userdata) {
545 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
548 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
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.
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.
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
);
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() {
600 $this->resetAfterTest(true);
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();
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);
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'));
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;
644 $enrol->enrol_user($instance2, $student->id
, $studentrole->id
);
646 $returndescription = mod_scorm_external
::get_scorms_by_courses_returns();
648 // Test open/close dates.
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']);
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
)) {
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};
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};
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']);
813 public function test_launch_sco() {
816 // Test invalid instance id.
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);
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
!= '') {
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());
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
);