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 * This file contains tests for scorm events.
21 * @copyright 2013 onwards Ankit Agarwal
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 require_once($CFG->dirroot
. '/mod/scorm/locallib.php');
27 require_once($CFG->dirroot
. '/mod/scorm/lib.php');
30 * Test class for various events related to Scorm.
33 * @copyright 2013 onwards Ankit Agarwal
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 class mod_scorm_event_testcase
extends advanced_testcase
{
38 /** @var stdClass store course object */
39 protected $eventcourse;
41 /** @var stdClass store user object */
44 /** @var stdClass store scorm object */
45 protected $eventscorm;
47 /** @var stdClass store course module object */
50 protected function setUp() {
51 $this->setAdminUser();
52 $this->eventcourse
= $this->getDataGenerator()->create_course();
53 $this->eventuser
= $this->getDataGenerator()->create_user();
54 $record = new stdClass();
55 $record->course
= $this->eventcourse
->id
;
56 $this->eventscorm
= $this->getDataGenerator()->create_module('scorm', $record);
57 $this->eventcm
= get_coursemodule_from_instance('scorm', $this->eventscorm
->id
);
61 * Tests for attempt deleted event
63 * @expectedException coding_exception
65 public function test_attempt_deleted_event() {
69 $this->resetAfterTest();
70 scorm_insert_track(2, $this->eventscorm
->id
, 1, 4, 'cmi.core.score.raw', 10);
71 $sink = $this->redirectEvents();
72 scorm_delete_attempt(2, $this->eventscorm
, 4);
73 $events = $sink->get_events();
75 $event = reset($events);
78 $this->assertCount(3, $events);
79 $this->assertInstanceOf('\mod_scorm\event\attempt_deleted', $event);
80 $this->assertEquals($USER->id
, $event->userid
);
81 $this->assertEquals(context_module
::instance($this->eventcm
->id
), $event->get_context());
82 $this->assertEquals(4, $event->other
['attemptid']);
83 $this->assertEquals(2, $event->relateduserid
);
84 $expected = array($this->eventcourse
->id
, 'scorm', 'delete attempts', 'report.php?id=' . $this->eventcm
->id
,
85 4, $this->eventcm
->id
);
86 $this->assertEventLegacyLogData($expected, $events[0]);
87 $this->assertEventContextNotUsed($event);
89 // Test event validations.
90 \mod_scorm\event\attempt_deleted
::create(array(
94 $this->fail('event \\mod_scorm\\event\\attempt_deleted is not validating events properly');
98 * Tests for course module viewed event.
100 * There is no api involved so the best we can do is test legacy data by triggering event manually.
102 public function test_course_module_viewed_event() {
103 $this->resetAfterTest();
104 $event = \mod_scorm\event\course_module_viewed
::create(array(
105 'objectid' => $this->eventscorm
->id
,
106 'context' => context_module
::instance($this->eventcm
->id
),
107 'courseid' => $this->eventcourse
->id
110 // Trigger and capture the event.
111 $sink = $this->redirectEvents();
113 $events = $sink->get_events();
114 $event = reset($events);
116 // Check that the legacy log data is valid.
117 $expected = array($this->eventcourse
->id
, 'scorm', 'pre-view', 'view.php?id=' . $this->eventcm
->id
,
118 $this->eventscorm
->id
, $this->eventcm
->id
);
119 $this->assertEventLegacyLogData($expected, $event);
120 $this->assertEventContextNotUsed($event);
124 * Tests for instance list viewed event.
126 * There is no api involved so the best we can do is test legacy data by triggering event manually.
128 public function test_course_module_instance_list_viewed_event() {
129 $this->resetAfterTest();
130 $event = \mod_scorm\event\course_module_instance_list_viewed
::create(array(
131 'context' => context_course
::instance($this->eventcourse
->id
),
132 'courseid' => $this->eventcourse
->id
135 // Trigger and capture the event.
136 $sink = $this->redirectEvents();
138 $events = $sink->get_events();
139 $event = reset($events);
141 // Check that the legacy log data is valid.
142 $expected = array($this->eventcourse
->id
, 'scorm', 'view all', 'index.php?id=' . $this->eventcourse
->id
, '');
143 $this->assertEventLegacyLogData($expected, $event);
144 $this->assertEventContextNotUsed($event);
148 * Tests for interactions viewed.
150 * There is no api involved so the best we can do is test legacy data by triggering event manually and test validations.
152 public function test_interactions_viewed_event() {
153 $this->resetAfterTest();
154 $event = \mod_scorm\event\interactions_viewed
::create(array(
155 'relateduserid' => 5,
156 'context' => context_module
::instance($this->eventcm
->id
),
157 'courseid' => $this->eventcourse
->id
,
158 'other' => array('attemptid' => 2, 'instanceid' => $this->eventscorm
->id
)
161 // Trigger and capture the event.
162 $sink = $this->redirectEvents();
164 $events = $sink->get_events();
165 $event = reset($events);
167 // Check that the legacy log data is valid.
168 $expected = array($this->eventcourse
->id
, 'scorm', 'userreportinteractions', 'report/userreportinteractions.php?id=' .
169 $this->eventcm
->id
. '&user=5&attempt=' . 2, $this->eventscorm
->id
, $this->eventcm
->id
);
170 $this->assertEventLegacyLogData($expected, $event);
171 $this->assertEventContextNotUsed($event);
175 * Tests for interactions viewed validations.
177 public function test_interactions_viewed_event_validations() {
178 $this->resetAfterTest();
180 \mod_scorm\event\interactions_viewed
::create(array(
181 'context' => context_module
::instance($this->eventcm
->id
),
182 'courseid' => $this->eventcourse
->id
,
183 'other' => array('attemptid' => 2)
185 $this->fail("Event validation should not allow \\mod_scorm\\event\\interactions_viewed to be triggered without
186 other['instanceid']");
187 } catch (Exception
$e) {
188 $this->assertInstanceOf('coding_exception', $e);
191 \mod_scorm\event\interactions_viewed
::create(array(
192 'context' => context_module
::instance($this->eventcm
->id
),
193 'courseid' => $this->eventcourse
->id
,
194 'other' => array('instanceid' => 2)
196 $this->fail("Event validation should not allow \\mod_scorm\\event\\interactions_viewed to be triggered without
197 other['attemptid']");
198 } catch (Exception
$e) {
199 $this->assertInstanceOf('coding_exception', $e);
203 /** Tests for report viewed.
205 * There is no api involved so the best we can do is test legacy data and validations by triggering event manually.
207 public function test_report_viewed_event() {
208 $this->resetAfterTest();
209 $event = \mod_scorm\event\report_viewed
::create(array(
210 'context' => context_module
::instance($this->eventcm
->id
),
211 'courseid' => $this->eventcourse
->id
,
213 'scormid' => $this->eventscorm
->id
,
218 // Trigger and capture the event.
219 $sink = $this->redirectEvents();
221 $events = $sink->get_events();
222 $event = reset($events);
224 // Check that the legacy log data is valid.
225 $expected = array($this->eventcourse
->id
, 'scorm', 'report', 'report.php?id=' . $this->eventcm
->id
. '&mode=basic',
226 $this->eventscorm
->id
, $this->eventcm
->id
);
227 $this->assertEventLegacyLogData($expected, $event);
228 $this->assertEventContextNotUsed($event);
231 /** Tests for sco launched event.
233 * There is no api involved so the best we can do is test legacy data and validations by triggering event manually.
235 * @expectedException coding_exception
237 public function test_sco_launched_event() {
238 $this->resetAfterTest();
239 $event = \mod_scorm\event\sco_launched
::create(array(
241 'context' => context_module
::instance($this->eventcm
->id
),
242 'courseid' => $this->eventcourse
->id
,
243 'other' => array('loadedcontent' => 'url_to_content_that_was_laoded.php')
246 // Trigger and capture the event.
247 $sink = $this->redirectEvents();
249 $events = $sink->get_events();
250 $event = reset($events);
252 // Check that the legacy log data is valid.
253 $expected = array($this->eventcourse
->id
, 'scorm', 'launch', 'view.php?id=' . $this->eventcm
->id
,
254 'url_to_content_that_was_laoded.php', $this->eventcm
->id
);
255 $this->assertEventLegacyLogData($expected, $event);
256 $this->assertEventContextNotUsed($event);
259 \mod_scorm\event\sco_launched
::create(array(
260 'objectid' => $this->eventscorm
->id
,
261 'context' => context_module
::instance($this->eventcm
->id
),
262 'courseid' => $this->eventcourse
->id
,
264 $this->fail('Event \\mod_scorm\\event\\sco_launched is not validating "loadedcontent" properly');
268 * Tests for tracks viewed event.
270 * There is no api involved so the best we can do is test validations by triggering event manually.
272 public function test_tracks_viewed_event() {
273 $this->resetAfterTest();
274 $event = \mod_scorm\event\tracks_viewed
::create(array(
275 'relateduserid' => 5,
276 'context' => context_module
::instance($this->eventcm
->id
),
277 'courseid' => $this->eventcourse
->id
,
278 'other' => array('attemptid' => 2, 'instanceid' => $this->eventscorm
->id
, 'scoid' => 3)
281 // Trigger and capture the event.
282 $sink = $this->redirectEvents();
284 $events = $sink->get_events();
285 $event = reset($events);
287 // Check that the legacy log data is valid.
288 $expected = array($this->eventcourse
->id
, 'scorm', 'userreporttracks', 'report/userreporttracks.php?id=' .
289 $this->eventcm
->id
. '&user=5&attempt=' . 2 . '&scoid=3', $this->eventscorm
->id
, $this->eventcm
->id
);
290 $this->assertEventLegacyLogData($expected, $event);
291 $this->assertEventContextNotUsed($event);
295 * Tests for tracks viewed event validations.
297 public function test_tracks_viewed_event_validations() {
298 $this->resetAfterTest();
300 \mod_scorm\event\tracks_viewed
::create(array(
301 'context' => context_module
::instance($this->eventcm
->id
),
302 'courseid' => $this->eventcourse
->id
,
303 'other' => array('attemptid' => 2, 'scoid' => 2)
305 $this->fail("Event validation should not allow \\mod_scorm\\event\\tracks_viewed to be triggered without
306 other['instanceid']");
307 } catch (Exception
$e) {
308 $this->assertInstanceOf('coding_exception', $e);
311 \mod_scorm\event\tracks_viewed
::create(array(
312 'context' => context_module
::instance($this->eventcm
->id
),
313 'courseid' => $this->eventcourse
->id
,
314 'other' => array('instanceid' => 2, 'scoid' => 2)
316 $this->fail("Event validation should not allow \\mod_scorm\\event\\tracks_viewed to be triggered without
317 other['attemptid']");
318 } catch (Exception
$e) {
319 $this->assertInstanceOf('coding_exception', $e);
323 \mod_scorm\event\tracks_viewed
::create(array(
324 'context' => context_module
::instance($this->eventcm
->id
),
325 'courseid' => $this->eventcourse
->id
,
326 'other' => array('attemptid' => 2, 'instanceid' => 2)
328 $this->fail("Event validation should not allow \\mod_scorm\\event\\tracks_viewed to be triggered without
330 } catch (Exception
$e) {
331 $this->assertInstanceOf('coding_exception', $e);
336 * Tests for userreport viewed event.
338 * There is no api involved so the best we can do is test validations and legacy log by triggering event manually.
340 public function test_user_report_viewed_event() {
341 $this->resetAfterTest();
342 $event = \mod_scorm\event\user_report_viewed
::create(array(
343 'relateduserid' => 5,
344 'context' => context_module
::instance($this->eventcm
->id
),
345 'courseid' => $this->eventcourse
->id
,
346 'other' => array('attemptid' => 2, 'instanceid' => $this->eventscorm
->id
)
349 // Trigger and capture the event.
350 $sink = $this->redirectEvents();
352 $events = $sink->get_events();
353 $event = reset($events);
355 // Check that the legacy log data is valid.
356 $expected = array($this->eventcourse
->id
, 'scorm', 'userreport', 'report/userreport.php?id=' .
357 $this->eventcm
->id
. '&user=5&attempt=' . 2, $this->eventscorm
->id
, $this->eventcm
->id
);
358 $this->assertEventLegacyLogData($expected, $event);
359 $this->assertEventContextNotUsed($event);
363 * Tests for userreport viewed event validations.
365 public function test_user_report_viewed_event_validations() {
366 $this->resetAfterTest();
368 \mod_scorm\event\user_report_viewed
::create(array(
369 'context' => context_module
::instance($this->eventcm
->id
),
370 'courseid' => $this->eventcourse
->id
,
371 'other' => array('attemptid' => 2)
373 $this->fail("Event validation should not allow \\mod_scorm\\event\\user_report_viewed to be triggered without
374 other['instanceid']");
375 } catch (Exception
$e) {
376 $this->assertInstanceOf('coding_exception', $e);
379 \mod_scorm\event\user_report_viewed
::create(array(
380 'context' => context_module
::instance($this->eventcm
->id
),
381 'courseid' => $this->eventcourse
->id
,
382 'other' => array('instanceid' => 2)
384 $this->fail("Event validation should not allow \\mod_scorm\\event\\user_report_viewed to be triggered without
385 other['attemptid']");
386 } catch (Exception
$e) {
387 $this->assertInstanceOf('coding_exception', $e);
392 * dataProvider for test_scoreraw_submitted_event().
394 public function get_scoreraw_submitted_event_provider() {
397 // - cmi.core.score.raw.
398 'cmi.core.score.raw => 100' => array('cmi.core.score.raw', '100'),
399 'cmi.core.score.raw => 90' => array('cmi.core.score.raw', '90'),
400 'cmi.core.score.raw => 50' => array('cmi.core.score.raw', '50'),
401 'cmi.core.score.raw => 10' => array('cmi.core.score.raw', '10'),
402 // Check an edge case (PHP empty() vs isset()): score value equals to '0'.
403 'cmi.core.score.raw => 0' => array('cmi.core.score.raw', '0'),
404 // SCORM 1.3 AKA 2004.
406 'cmi.score.raw => 100' => array('cmi.score.raw', '100'),
407 'cmi.score.raw => 90' => array('cmi.score.raw', '90'),
408 'cmi.score.raw => 50' => array('cmi.score.raw', '50'),
409 'cmi.score.raw => 10' => array('cmi.score.raw', '10'),
410 // Check an edge case (PHP empty() vs isset()): score value equals to '0'.
411 'cmi.score.raw => 0' => array('cmi.score.raw', '0'),
416 * Tests for score submitted event.
418 * There is no api involved so the best we can do is test data by triggering event manually.
420 * @dataProvider get_scoreraw_submitted_event_provider
422 * @param string $cmielement a valid CMI raw score element
423 * @param string $cmivalue a valid CMI raw score value
425 public function test_scoreraw_submitted_event($cmielement, $cmivalue) {
426 $this->resetAfterTest();
427 $event = \mod_scorm\event\scoreraw_submitted
::create(array(
428 'other' => array('attemptid' => '2', 'cmielement' => $cmielement, 'cmivalue' => $cmivalue),
429 'objectid' => $this->eventscorm
->id
,
430 'context' => context_module
::instance($this->eventcm
->id
),
431 'relateduserid' => $this->eventuser
->id
434 // Trigger and capture the event.
435 $sink = $this->redirectEvents();
437 $events = $sink->get_events();
439 $event = reset($events);
440 $this->assertEquals(2, $event->other
['attemptid']);
441 $this->assertEquals($cmielement, $event->other
['cmielement']);
442 $this->assertEquals($cmivalue, $event->other
['cmivalue']);
444 // Check that no legacy log data is provided.
445 $this->assertEventLegacyLogData(null, $event);
446 $this->assertEventContextNotUsed($event);
450 * dataProvider for test_scoreraw_submitted_event_validations().
452 public function get_scoreraw_submitted_event_validations() {
454 'scoreraw_submitted => missing cmielement' => array(
456 "Event validation should not allow \\mod_scorm\\event\\scoreraw_submitted " .
457 "to be triggered without other['cmielement']",
458 'Coding error detected, it must be fixed by a programmer: ' .
459 "The 'cmielement' must be set in other."
461 'scoreraw_submitted => missing cmivalue' => array(
462 'cmi.core.score.raw', null,
463 "Event validation should not allow \\mod_scorm\\event\\scoreraw_submitted " .
464 "to be triggered without other['cmivalue']",
465 'Coding error detected, it must be fixed by a programmer: ' .
466 "The 'cmivalue' must be set in other."
468 'scoreraw_submitted => wrong CMI element' => array(
469 'cmi.core.lesson_status', '50',
470 "Event validation should not allow \\mod_scorm\\event\\scoreraw_submitted " .
471 'to be triggered with a CMI element not representing a raw score',
472 'Coding error detected, it must be fixed by a programmer: ' .
473 "The 'cmielement' must represents a valid CMI raw score (cmi.core.lesson_status)."
479 * Tests for score submitted event validations.
481 * @dataProvider get_scoreraw_submitted_event_validations
483 * @param string $cmielement a valid CMI raw score element
484 * @param string $cmivalue a valid CMI raw score value
485 * @param string $failmessage the message used to fail the test in case of missing to violate a validation rule
486 * @param string $excmessage the exception message when violating the validations rules
488 public function test_scoreraw_submitted_event_validations($cmielement, $cmivalue, $failmessage, $excmessage) {
489 $this->resetAfterTest();
492 'context' => context_module
::instance($this->eventcm
->id
),
493 'courseid' => $this->eventcourse
->id
,
494 'other' => array('attemptid' => 2)
496 if ($cmielement != null) {
497 $data['other']['cmielement'] = $cmielement;
499 if ($cmivalue != null) {
500 $data['other']['cmivalue'] = $cmivalue;
502 \mod_scorm\event\scoreraw_submitted
::create($data);
503 $this->fail($failmessage);
504 } catch (Exception
$e) {
505 $this->assertInstanceOf('coding_exception', $e);
506 $this->assertEquals($excmessage, $e->getMessage());
511 * dataProvider for test_status_submitted_event().
513 public function get_status_submitted_event_provider() {
516 // 1. Status: cmi.core.lesson_status.
517 'cmi.core.lesson_status => passed' => array('cmi.core.lesson_status', 'passed'),
518 'cmi.core.lesson_status => completed' => array('cmi.core.lesson_status', 'completed'),
519 'cmi.core.lesson_status => failed' => array('cmi.core.lesson_status', 'failed'),
520 'cmi.core.lesson_status => incomplete' => array('cmi.core.lesson_status', 'incomplete'),
521 'cmi.core.lesson_status => browsed' => array('cmi.core.lesson_status', 'browsed'),
522 'cmi.core.lesson_status => not attempted' => array('cmi.core.lesson_status', 'not attempted'),
523 // SCORM 1.3 AKA 2004.
524 // 1. Completion status: cmi.completion_status.
525 'cmi.completion_status => completed' => array('cmi.completion_status', 'completed'),
526 'cmi.completion_status => incomplete' => array('cmi.completion_status', 'incomplete'),
527 'cmi.completion_status => not attempted' => array('cmi.completion_status', 'not attempted'),
528 'cmi.completion_status => unknown' => array('cmi.completion_status', 'unknown'),
529 // 2. Success status: cmi.success_status.
530 'cmi.success_status => passed' => array('cmi.success_status', 'passed'),
531 'cmi.success_status => failed' => array('cmi.success_status', 'failed'),
532 'cmi.success_status => unknown' => array('cmi.success_status', 'unknown')
537 * Tests for status submitted event.
539 * There is no api involved so the best we can do is test data by triggering event manually.
541 * @dataProvider get_status_submitted_event_provider
543 * @param string $cmielement a valid CMI status element
544 * @param string $cmivalue a valid CMI status value
546 public function test_status_submitted_event($cmielement, $cmivalue) {
547 $this->resetAfterTest();
548 $event = \mod_scorm\event\status_submitted
::create(array(
549 'other' => array('attemptid' => '2', 'cmielement' => $cmielement, 'cmivalue' => $cmivalue),
550 'objectid' => $this->eventscorm
->id
,
551 'context' => context_module
::instance($this->eventcm
->id
),
552 'relateduserid' => $this->eventuser
->id
555 // Trigger and capture the event.
556 $sink = $this->redirectEvents();
558 $events = $sink->get_events();
560 $event = reset($events);
561 $this->assertEquals(2, $event->other
['attemptid']);
562 $this->assertEquals($cmielement, $event->other
['cmielement']);
563 $this->assertEquals($cmivalue, $event->other
['cmivalue']);
565 // Check that no legacy log data is provided.
566 $this->assertEventLegacyLogData(null, $event);
567 $this->assertEventContextNotUsed($event);
571 * dataProvider for test_status_submitted_event_validations().
573 public function get_status_submitted_event_validations() {
575 'status_submitted => missing cmielement' => array(
577 "Event validation should not allow \\mod_scorm\\event\\status_submitted " .
578 "to be triggered without other['cmielement']",
579 'Coding error detected, it must be fixed by a programmer: ' .
580 "The 'cmielement' must be set in other."
582 'status_submitted => missing cmivalue' => array(
583 'cmi.core.lesson_status', null,
584 "Event validation should not allow \\mod_scorm\\event\\status_submitted " .
585 "to be triggered without other['cmivalue']",
586 'Coding error detected, it must be fixed by a programmer: ' .
587 "The 'cmivalue' must be set in other."
589 'status_submitted => wrong CMI element' => array(
590 'cmi.core.score.raw', 'passed',
591 "Event validation should not allow \\mod_scorm\\event\\status_submitted " .
592 'to be triggered with a CMI element not representing a valid CMI status element',
593 'Coding error detected, it must be fixed by a programmer: ' .
594 "The 'cmielement' must represents a valid CMI status element (cmi.core.score.raw)."
596 'status_submitted => wrong CMI value' => array(
597 'cmi.core.lesson_status', 'blahblahblah',
598 "Event validation should not allow \\mod_scorm\\event\\status_submitted " .
599 'to be triggered with a CMI element not representing a valid CMI status',
600 'Coding error detected, it must be fixed by a programmer: ' .
601 "The 'cmivalue' must represents a valid CMI status value (blahblahblah)."
607 * Tests for status submitted event validations.
609 * @dataProvider get_status_submitted_event_validations
611 * @param string $cmielement a valid CMI status element
612 * @param string $cmivalue a valid CMI status value
613 * @param string $failmessage the message used to fail the test in case of missing to violate a validation rule
614 * @param string $excmessage the exception message when violating the validations rules
616 public function test_status_submitted_event_validations($cmielement, $cmivalue, $failmessage, $excmessage) {
617 $this->resetAfterTest();
620 'context' => context_module
::instance($this->eventcm
->id
),
621 'courseid' => $this->eventcourse
->id
,
622 'other' => array('attemptid' => 2)
624 if ($cmielement != null) {
625 $data['other']['cmielement'] = $cmielement;
627 if ($cmivalue != null) {
628 $data['other']['cmivalue'] = $cmivalue;
630 \mod_scorm\event\status_submitted
::create($data);
631 $this->fail($failmessage);
632 } catch (Exception
$e) {
633 $this->assertInstanceOf('coding_exception', $e);
634 $this->assertEquals($excmessage, $e->getMessage());