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']);
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');
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);
188 public function test_mod_scorm_get_scorm_attempt_count_invalid_instanceid() {
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);
197 public function test_mod_scorm_get_scorm_attempt_count_invalid_userid() {
199 self::setUser($this->student);
201 $this->setExpectedException('moodle_exception');
202 mod_scorm_external::get_scorm_attempt_count($this->scorm->id, -1);
206 * Test get scorm scoes
208 public function test_mod_scorm_get_scorm_scoes() {
211 $this->resetAfterTest(true);
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);
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!.
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);
244 $scorm->timeopen = time() - DAYSECS;
245 $scorm->timeclose = time() - HOURSECS;
246 $DB->update_record('scorm', $scorm);
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);
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]);
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.
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);
290 * Test get scorm user data
292 public function test_mod_scorm_get_scorm_user_data() {
295 $this->resetAfterTest(true);
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();
308 $record = new stdClass();
309 $record->course = $course->id;
310 $scorm = self::getDataGenerator()->create_module('scorm', $record);
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');
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.
330 foreach ($result['data'] as $scodata) {
331 foreach ($scodata['userdata'] as $userdata) {
332 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
335 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
340 $this->assertEquals(2, $found);
342 // Test invalid instance id.
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);
352 * Test insert scorm tracks
354 public function test_mod_scorm_insert_scorm_tracks() {
357 $this->resetAfterTest(true);
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);
376 $scoes = scorm_get_scoes($scorm->id);
377 $sco = array_shift($scoes);
382 'element' => 'cmi.core.lesson_status',
383 'value' => 'completed'
386 'element' => 'cmi.core.score.raw',
391 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
392 $this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
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);
402 $scorm->timeopen = time() - DAYSECS;
403 $scorm->timeclose = time() - HOURSECS;
404 $DB->update_record('scorm', $scorm);
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);
413 // Test invalid instance id.
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);
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']));
438 * Test get scorm sco tracks
440 public function test_mod_scorm_get_scorm_sco_tracks() {
443 $this->resetAfterTest(true);
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();
457 $record = new stdClass();
458 $record->course = $course->id;
459 $scorm = self::getDataGenerator()->create_module('scorm', $record);
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');
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.
480 foreach ($result['data'] as $userdata) {
481 if ($userdata['element'] == 'cmi.core.lesson_status' and $userdata['value'] == 'completed') {
484 if ($userdata['element'] == 'cmi.core.score.raw' and $userdata['value'] == '80') {
488 $this->assertEquals(2, $found);
490 // Capabilities check.
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);
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.
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);
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);