MDL-74610 quiz: web service to update grade item for a slot
[moodle.git] / mod / quiz / tests / event / events_test.php
blobaa0bb8b1ed640def29966be54da3b7f10bc0a5ff
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 * Quiz events tests.
20 * @package mod_quiz
21 * @category phpunit
22 * @copyright 2013 Adrian Greeve
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 namespace mod_quiz\event;
28 use core\event\base;
29 use mod_quiz\quiz_attempt;
30 use mod_quiz\quiz_settings;
31 use context_module;
32 use moodle_url;
34 /**
35 * Unit tests for quiz events.
37 * @package mod_quiz
38 * @category phpunit
39 * @copyright 2013 Adrian Greeve
40 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 class events_test extends \advanced_testcase {
44 /**
45 * Set up a quiz.
47 * The quiz contains two questions, a short-answer one and a numerical one.
49 * @return quiz_settings the generated quiz.
51 protected function prepare_quiz() {
53 $this->resetAfterTest(true);
55 // Create a course
56 $course = $this->getDataGenerator()->create_course();
58 // Make a quiz.
59 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
61 $quiz = $quizgenerator->create_instance(['course' => $course->id, 'questionsperpage' => 0,
62 'grade' => 100.0, 'sumgrades' => 2]);
64 get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
66 // Create a couple of questions.
67 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
69 $cat = $questiongenerator->create_question_category();
70 $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
71 $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
73 // Add them to the quiz.
74 quiz_add_quiz_question($saq->id, $quiz);
75 quiz_add_quiz_question($numq->id, $quiz);
77 // Make a user to do the quiz.
78 $user1 = $this->getDataGenerator()->create_user();
79 $this->setUser($user1);
81 return quiz_settings::create($quiz->id, $user1->id);
84 /**
85 * Setup a quiz attempt at the quiz created by {@link prepare_quiz()}.
87 * @param \mod_quiz\quiz_settings $quizobj the generated quiz.
88 * @param bool $ispreview Make the attempt a preview attempt when true.
89 * @return array with three elements, array($quizobj, $quba, $attempt)
91 protected function prepare_quiz_attempt($quizobj, $ispreview = false) {
92 // Start the attempt.
93 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
94 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
96 $timenow = time();
97 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, $ispreview);
98 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
99 quiz_attempt_save_started($quizobj, $quba, $attempt);
101 return [$quizobj, $quba, $attempt];
105 * Setup some convenience test data with a single attempt.
107 * @param bool $ispreview Make the attempt a preview attempt when true.
108 * @return array with three elements, array($quizobj, $quba, $attempt)
110 protected function prepare_quiz_data($ispreview = false) {
111 $quizobj = $this->prepare_quiz();
112 return $this->prepare_quiz_attempt($quizobj, $ispreview);
115 public function test_attempt_submitted() {
117 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
118 $attemptobj = quiz_attempt::create($attempt->id);
120 // Catch the event.
121 $sink = $this->redirectEvents();
123 $timefinish = time();
124 $attemptobj->process_finish($timefinish, false);
125 $events = $sink->get_events();
126 $sink->close();
128 // Validate the event.
129 $this->assertCount(3, $events);
130 $event = $events[2];
131 $this->assertInstanceOf('\mod_quiz\event\attempt_submitted', $event);
132 $this->assertEquals('quiz_attempts', $event->objecttable);
133 $this->assertEquals($quizobj->get_context(), $event->get_context());
134 $this->assertEquals($attempt->userid, $event->relateduserid);
135 $this->assertEquals(null, $event->other['submitterid']); // Should be the user, but PHP Unit complains...
136 $this->assertEventContextNotUsed($event);
139 public function test_attempt_becameoverdue() {
141 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
142 $attemptobj = quiz_attempt::create($attempt->id);
144 // Catch the event.
145 $sink = $this->redirectEvents();
146 $timefinish = time();
147 $attemptobj->process_going_overdue($timefinish, false);
148 $events = $sink->get_events();
149 $sink->close();
151 $this->assertCount(1, $events);
152 $event = $events[0];
153 $this->assertInstanceOf('\mod_quiz\event\attempt_becameoverdue', $event);
154 $this->assertEquals('quiz_attempts', $event->objecttable);
155 $this->assertEquals($quizobj->get_context(), $event->get_context());
156 $this->assertEquals($attempt->userid, $event->relateduserid);
157 $this->assertNotEmpty($event->get_description());
158 // Submitterid should be the user, but as we are in PHP Unit, CLI_SCRIPT is set to true which sets null in submitterid.
159 $this->assertEquals(null, $event->other['submitterid']);
160 $this->assertEventContextNotUsed($event);
163 public function test_attempt_abandoned() {
165 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
166 $attemptobj = quiz_attempt::create($attempt->id);
168 // Catch the event.
169 $sink = $this->redirectEvents();
170 $timefinish = time();
171 $attemptobj->process_abandon($timefinish, false);
172 $events = $sink->get_events();
173 $sink->close();
175 $this->assertCount(1, $events);
176 $event = $events[0];
177 $this->assertInstanceOf('\mod_quiz\event\attempt_abandoned', $event);
178 $this->assertEquals('quiz_attempts', $event->objecttable);
179 $this->assertEquals($quizobj->get_context(), $event->get_context());
180 $this->assertEquals($attempt->userid, $event->relateduserid);
181 // Submitterid should be the user, but as we are in PHP Unit, CLI_SCRIPT is set to true which sets null in submitterid.
182 $this->assertEquals(null, $event->other['submitterid']);
183 $this->assertEventContextNotUsed($event);
186 public function test_attempt_started() {
187 $quizobj = $this->prepare_quiz();
189 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
190 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
192 $timenow = time();
193 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow);
194 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
196 // Trigger and capture the event.
197 $sink = $this->redirectEvents();
198 quiz_attempt_save_started($quizobj, $quba, $attempt);
199 $events = $sink->get_events();
200 $event = reset($events);
202 // Check that the event data is valid.
203 $this->assertInstanceOf('\mod_quiz\event\attempt_started', $event);
204 $this->assertEquals('quiz_attempts', $event->objecttable);
205 $this->assertEquals($attempt->id, $event->objectid);
206 $this->assertEquals($attempt->userid, $event->relateduserid);
207 $this->assertEquals($quizobj->get_context(), $event->get_context());
208 $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
212 * Test the attempt question restarted event.
214 * There is no external API for replacing a question, so the unit test will simply
215 * create and trigger the event and ensure the event data is returned as expected.
217 public function test_attempt_question_restarted() {
218 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
220 $params = [
221 'objectid' => 1,
222 'relateduserid' => 2,
223 'courseid' => $quizobj->get_courseid(),
224 'context' => \context_module::instance($quizobj->get_cmid()),
225 'other' => [
226 'quizid' => $quizobj->get_quizid(),
227 'page' => 2,
228 'slot' => 3,
229 'newquestionid' => 2
232 $event = \mod_quiz\event\attempt_question_restarted::create($params);
234 // Trigger and capture the event.
235 $sink = $this->redirectEvents();
236 $event->trigger();
237 $events = $sink->get_events();
238 $event = reset($events);
240 // Check that the event data is valid.
241 $this->assertInstanceOf('\mod_quiz\event\attempt_question_restarted', $event);
242 $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
243 $this->assertEventContextNotUsed($event);
247 * Test the attempt updated event.
249 * There is no external API for updating an attempt, so the unit test will simply
250 * create and trigger the event and ensure the event data is returned as expected.
252 public function test_attempt_updated() {
253 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
255 $params = [
256 'objectid' => 1,
257 'relateduserid' => 2,
258 'courseid' => $quizobj->get_courseid(),
259 'context' => \context_module::instance($quizobj->get_cmid()),
260 'other' => [
261 'quizid' => $quizobj->get_quizid(),
262 'page' => 0
265 $event = \mod_quiz\event\attempt_updated::create($params);
267 // Trigger and capture the event.
268 $sink = $this->redirectEvents();
269 $event->trigger();
270 $events = $sink->get_events();
271 $event = reset($events);
273 // Check that the event data is valid.
274 $this->assertInstanceOf('\mod_quiz\event\attempt_updated', $event);
275 $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
276 $this->assertEventContextNotUsed($event);
280 * Test the attempt auto-saved event.
282 * There is no external API for auto-saving an attempt, so the unit test will simply
283 * create and trigger the event and ensure the event data is returned as expected.
285 public function test_attempt_autosaved() {
286 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
288 $params = [
289 'objectid' => 1,
290 'relateduserid' => 2,
291 'courseid' => $quizobj->get_courseid(),
292 'context' => \context_module::instance($quizobj->get_cmid()),
293 'other' => [
294 'quizid' => $quizobj->get_quizid(),
295 'page' => 0
299 $event = \mod_quiz\event\attempt_autosaved::create($params);
301 // Trigger and capture the event.
302 $sink = $this->redirectEvents();
303 $event->trigger();
304 $events = $sink->get_events();
305 $event = reset($events);
307 // Check that the event data is valid.
308 $this->assertInstanceOf('\mod_quiz\event\attempt_autosaved', $event);
309 $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
310 $this->assertEventContextNotUsed($event);
314 * Test the edit page viewed event.
316 * There is no external API for updating a quiz, so the unit test will simply
317 * create and trigger the event and ensure the event data is returned as expected.
319 public function test_edit_page_viewed() {
320 $this->resetAfterTest();
322 $this->setAdminUser();
323 $course = $this->getDataGenerator()->create_course();
324 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
326 $params = [
327 'courseid' => $course->id,
328 'context' => \context_module::instance($quiz->cmid),
329 'other' => [
330 'quizid' => $quiz->id
333 $event = \mod_quiz\event\edit_page_viewed::create($params);
335 // Trigger and capture the event.
336 $sink = $this->redirectEvents();
337 $event->trigger();
338 $events = $sink->get_events();
339 $event = reset($events);
341 // Check that the event data is valid.
342 $this->assertInstanceOf('\mod_quiz\event\edit_page_viewed', $event);
343 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
344 $this->assertEventContextNotUsed($event);
348 * Test the attempt deleted event.
350 public function test_attempt_deleted() {
351 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
353 // Trigger and capture the event.
354 $sink = $this->redirectEvents();
355 quiz_delete_attempt($attempt, $quizobj->get_quiz());
356 $events = $sink->get_events();
357 $event = reset($events);
359 // Check that the event data is valid.
360 $this->assertInstanceOf('\mod_quiz\event\attempt_deleted', $event);
361 $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
362 $this->assertEventContextNotUsed($event);
366 * Test that preview attempt deletions are not logged.
368 public function test_preview_attempt_deleted() {
369 // Create quiz with preview attempt.
370 list($quizobj, $quba, $previewattempt) = $this->prepare_quiz_data(true);
372 // Delete a preview attempt, capturing events.
373 $sink = $this->redirectEvents();
374 quiz_delete_attempt($previewattempt, $quizobj->get_quiz());
376 // Verify that no events were generated.
377 $this->assertEmpty($sink->get_events());
381 * Test the report viewed event.
383 * There is no external API for viewing reports, so the unit test will simply
384 * create and trigger the event and ensure the event data is returned as expected.
386 public function test_report_viewed() {
387 $this->resetAfterTest();
389 $this->setAdminUser();
390 $course = $this->getDataGenerator()->create_course();
391 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
393 $params = [
394 'context' => $context = \context_module::instance($quiz->cmid),
395 'other' => [
396 'quizid' => $quiz->id,
397 'reportname' => 'overview'
400 $event = \mod_quiz\event\report_viewed::create($params);
402 // Trigger and capture the event.
403 $sink = $this->redirectEvents();
404 $event->trigger();
405 $events = $sink->get_events();
406 $event = reset($events);
408 // Check that the event data is valid.
409 $this->assertInstanceOf('\mod_quiz\event\report_viewed', $event);
410 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
411 $this->assertEventContextNotUsed($event);
415 * Test the attempt reviewed event.
417 * There is no external API for reviewing attempts, so the unit test will simply
418 * create and trigger the event and ensure the event data is returned as expected.
420 public function test_attempt_reviewed() {
421 $this->resetAfterTest();
423 $this->setAdminUser();
424 $course = $this->getDataGenerator()->create_course();
425 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
427 $params = [
428 'objectid' => 1,
429 'relateduserid' => 2,
430 'courseid' => $course->id,
431 'context' => \context_module::instance($quiz->cmid),
432 'other' => [
433 'quizid' => $quiz->id
436 $event = \mod_quiz\event\attempt_reviewed::create($params);
438 // Trigger and capture the event.
439 $sink = $this->redirectEvents();
440 $event->trigger();
441 $events = $sink->get_events();
442 $event = reset($events);
444 // Check that the event data is valid.
445 $this->assertInstanceOf('\mod_quiz\event\attempt_reviewed', $event);
446 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
447 $this->assertEventContextNotUsed($event);
451 * Test the attempt summary viewed event.
453 * There is no external API for viewing the attempt summary, so the unit test will simply
454 * create and trigger the event and ensure the event data is returned as expected.
456 public function test_attempt_summary_viewed() {
457 $this->resetAfterTest();
459 $this->setAdminUser();
460 $course = $this->getDataGenerator()->create_course();
461 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
463 $params = [
464 'objectid' => 1,
465 'relateduserid' => 2,
466 'courseid' => $course->id,
467 'context' => \context_module::instance($quiz->cmid),
468 'other' => [
469 'quizid' => $quiz->id
472 $event = \mod_quiz\event\attempt_summary_viewed::create($params);
474 // Trigger and capture the event.
475 $sink = $this->redirectEvents();
476 $event->trigger();
477 $events = $sink->get_events();
478 $event = reset($events);
480 // Check that the event data is valid.
481 $this->assertInstanceOf('\mod_quiz\event\attempt_summary_viewed', $event);
482 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
483 $this->assertEventContextNotUsed($event);
487 * Test the user override created event.
489 * There is no external API for creating a user override, so the unit test will simply
490 * create and trigger the event and ensure the event data is returned as expected.
492 public function test_user_override_created() {
493 $this->resetAfterTest();
495 $this->setAdminUser();
496 $course = $this->getDataGenerator()->create_course();
497 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
499 $params = [
500 'objectid' => 1,
501 'relateduserid' => 2,
502 'context' => \context_module::instance($quiz->cmid),
503 'other' => [
504 'quizid' => $quiz->id
507 $event = \mod_quiz\event\user_override_created::create($params);
509 // Trigger and capture the event.
510 $sink = $this->redirectEvents();
511 $event->trigger();
512 $events = $sink->get_events();
513 $event = reset($events);
515 // Check that the event data is valid.
516 $this->assertInstanceOf('\mod_quiz\event\user_override_created', $event);
517 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
518 $this->assertEventContextNotUsed($event);
522 * Test the group override created event.
524 * There is no external API for creating a group override, so the unit test will simply
525 * create and trigger the event and ensure the event data is returned as expected.
527 public function test_group_override_created() {
528 $this->resetAfterTest();
530 $this->setAdminUser();
531 $course = $this->getDataGenerator()->create_course();
532 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
534 $params = [
535 'objectid' => 1,
536 'context' => \context_module::instance($quiz->cmid),
537 'other' => [
538 'quizid' => $quiz->id,
539 'groupid' => 2
542 $event = \mod_quiz\event\group_override_created::create($params);
544 // Trigger and capture the event.
545 $sink = $this->redirectEvents();
546 $event->trigger();
547 $events = $sink->get_events();
548 $event = reset($events);
550 // Check that the event data is valid.
551 $this->assertInstanceOf('\mod_quiz\event\group_override_created', $event);
552 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
553 $this->assertEventContextNotUsed($event);
557 * Test the user override updated event.
559 * There is no external API for updating a user override, so the unit test will simply
560 * create and trigger the event and ensure the event data is returned as expected.
562 public function test_user_override_updated() {
563 $this->resetAfterTest();
565 $this->setAdminUser();
566 $course = $this->getDataGenerator()->create_course();
567 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
569 $params = [
570 'objectid' => 1,
571 'relateduserid' => 2,
572 'context' => \context_module::instance($quiz->cmid),
573 'other' => [
574 'quizid' => $quiz->id
577 $event = \mod_quiz\event\user_override_updated::create($params);
579 // Trigger and capture the event.
580 $sink = $this->redirectEvents();
581 $event->trigger();
582 $events = $sink->get_events();
583 $event = reset($events);
585 // Check that the event data is valid.
586 $this->assertInstanceOf('\mod_quiz\event\user_override_updated', $event);
587 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
588 $this->assertEventContextNotUsed($event);
592 * Test the group override updated event.
594 * There is no external API for updating a group override, so the unit test will simply
595 * create and trigger the event and ensure the event data is returned as expected.
597 public function test_group_override_updated() {
598 $this->resetAfterTest();
600 $this->setAdminUser();
601 $course = $this->getDataGenerator()->create_course();
602 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
604 $params = [
605 'objectid' => 1,
606 'context' => \context_module::instance($quiz->cmid),
607 'other' => [
608 'quizid' => $quiz->id,
609 'groupid' => 2
612 $event = \mod_quiz\event\group_override_updated::create($params);
614 // Trigger and capture the event.
615 $sink = $this->redirectEvents();
616 $event->trigger();
617 $events = $sink->get_events();
618 $event = reset($events);
620 // Check that the event data is valid.
621 $this->assertInstanceOf('\mod_quiz\event\group_override_updated', $event);
622 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
623 $this->assertEventContextNotUsed($event);
627 * Test the user override deleted event.
629 public function test_user_override_deleted() {
630 global $DB;
632 $this->resetAfterTest();
634 $this->setAdminUser();
635 $course = $this->getDataGenerator()->create_course();
636 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
637 $quizsettings = quiz_settings::create($quiz->id);
639 // Create an override.
640 $override = new \stdClass();
641 $override->quiz = $quiz->id;
642 $override->userid = 2;
643 $override->id = $DB->insert_record('quiz_overrides', $override);
645 // Trigger and capture the event.
646 $sink = $this->redirectEvents();
647 $quizsettings->get_override_manager()->delete_overrides(overrides: [$override]);
648 $events = $sink->get_events();
649 $event = reset($events);
651 // Check that the event data is valid.
652 $this->assertInstanceOf('\mod_quiz\event\user_override_deleted', $event);
653 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
654 $this->assertEventContextNotUsed($event);
658 * Test the group override deleted event.
660 public function test_group_override_deleted() {
661 global $DB;
663 $this->resetAfterTest();
665 $this->setAdminUser();
666 $course = $this->getDataGenerator()->create_course();
667 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
668 $quizsettings = quiz_settings::create($quiz->id);
670 // Create an override.
671 $override = new \stdClass();
672 $override->quiz = $quiz->id;
673 $override->groupid = 2;
674 $override->id = $DB->insert_record('quiz_overrides', $override);
676 // Trigger and capture the event.
677 $sink = $this->redirectEvents();
678 $quizsettings->get_override_manager()->delete_overrides(overrides: [$override]);
679 $events = $sink->get_events();
680 $event = reset($events);
682 // Check that the event data is valid.
683 $this->assertInstanceOf('\mod_quiz\event\group_override_deleted', $event);
684 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
685 $this->assertEventContextNotUsed($event);
689 * Test the attempt viewed event.
691 * There is no external API for continuing an attempt, so the unit test will simply
692 * create and trigger the event and ensure the event data is returned as expected.
694 public function test_attempt_viewed() {
695 $this->resetAfterTest();
697 $this->setAdminUser();
698 $course = $this->getDataGenerator()->create_course();
699 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
701 $params = [
702 'objectid' => 1,
703 'relateduserid' => 2,
704 'courseid' => $course->id,
705 'context' => \context_module::instance($quiz->cmid),
706 'other' => [
707 'quizid' => $quiz->id,
708 'page' => 0
711 $event = \mod_quiz\event\attempt_viewed::create($params);
713 // Trigger and capture the event.
714 $sink = $this->redirectEvents();
715 $event->trigger();
716 $events = $sink->get_events();
717 $event = reset($events);
719 // Check that the event data is valid.
720 $this->assertInstanceOf('\mod_quiz\event\attempt_viewed', $event);
721 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
722 $this->assertEventContextNotUsed($event);
726 * Test the attempt previewed event.
728 public function test_attempt_preview_started() {
729 $quizobj = $this->prepare_quiz();
731 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
732 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
734 $timenow = time();
735 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, true);
736 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
738 // Trigger and capture the event.
739 $sink = $this->redirectEvents();
740 quiz_attempt_save_started($quizobj, $quba, $attempt);
741 $events = $sink->get_events();
742 $event = reset($events);
744 // Check that the event data is valid.
745 $this->assertInstanceOf('\mod_quiz\event\attempt_preview_started', $event);
746 $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
747 $this->assertEventContextNotUsed($event);
751 * Test the question manually graded event.
753 * There is no external API for manually grading a question, so the unit test will simply
754 * create and trigger the event and ensure the event data is returned as expected.
756 public function test_question_manually_graded() {
757 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
759 $params = [
760 'objectid' => 1,
761 'courseid' => $quizobj->get_courseid(),
762 'context' => \context_module::instance($quizobj->get_cmid()),
763 'other' => [
764 'quizid' => $quizobj->get_quizid(),
765 'attemptid' => 2,
766 'slot' => 3
769 $event = \mod_quiz\event\question_manually_graded::create($params);
771 // Trigger and capture the event.
772 $sink = $this->redirectEvents();
773 $event->trigger();
774 $events = $sink->get_events();
775 $event = reset($events);
777 // Check that the event data is valid.
778 $this->assertInstanceOf('\mod_quiz\event\question_manually_graded', $event);
779 $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
780 $this->assertEventContextNotUsed($event);
784 * Test the attempt regraded event.
786 * There is no external API for regrading attempts, so the unit test will simply
787 * create and trigger the event and ensure the event data is returned as expected.
789 public function test_attempt_regraded() {
790 $this->resetAfterTest();
792 $this->setAdminUser();
793 $course = $this->getDataGenerator()->create_course();
794 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
796 $params = [
797 'objectid' => 1,
798 'relateduserid' => 2,
799 'courseid' => $course->id,
800 'context' => \context_module::instance($quiz->cmid),
801 'other' => [
802 'quizid' => $quiz->id
805 $event = \mod_quiz\event\attempt_regraded::create($params);
807 // Trigger and capture the event.
808 $sink = $this->redirectEvents();
809 $event->trigger();
810 $events = $sink->get_events();
811 $event = reset($events);
813 // Check that the event data is valid.
814 $this->assertInstanceOf('\mod_quiz\event\attempt_regraded', $event);
815 $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
816 $this->assertEventContextNotUsed($event);
820 * Test the attempt notify manual graded event.
821 * There is no external API for notification email when manual grading of user's attempt is completed,
822 * so the unit test will simply create and trigger the event and ensure the event data is returned as expected.
824 public function test_attempt_manual_grading_completed() {
825 $this->resetAfterTest();
826 list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
827 $attemptobj = quiz_attempt::create($attempt->id);
829 $params = [
830 'objectid' => $attemptobj->get_attemptid(),
831 'relateduserid' => $attemptobj->get_userid(),
832 'courseid' => $attemptobj->get_course()->id,
833 'context' => \context_module::instance($attemptobj->get_cmid()),
834 'other' => [
835 'quizid' => $attemptobj->get_quizid()
838 $event = \mod_quiz\event\attempt_manual_grading_completed::create($params);
840 // Catch the event.
841 $sink = $this->redirectEvents();
842 $event->trigger();
843 $events = $sink->get_events();
844 $sink->close();
846 // Validate the event.
847 $this->assertCount(1, $events);
848 $event = reset($events);
849 $this->assertInstanceOf('\mod_quiz\event\attempt_manual_grading_completed', $event);
850 $this->assertEquals('quiz_attempts', $event->objecttable);
851 $this->assertEquals($quizobj->get_context(), $event->get_context());
852 $this->assertEquals($attempt->userid, $event->relateduserid);
853 $this->assertNotEmpty($event->get_description());
854 $this->assertEventContextNotUsed($event);
858 * Test the page break created event.
860 * There is no external API for creating page break, so the unit test will simply
861 * create and trigger the event and ensure the event data is returned as expected.
863 public function test_page_break_created() {
864 $quizobj = $this->prepare_quiz();
866 $params = [
867 'objectid' => 1,
868 'context' => context_module::instance($quizobj->get_cmid()),
869 'other' => [
870 'quizid' => $quizobj->get_quizid(),
871 'slotnumber' => 3,
874 $event = \mod_quiz\event\page_break_created::create($params);
876 // Trigger and capture the event.
877 $sink = $this->redirectEvents();
878 $event->trigger();
879 $events = $sink->get_events();
880 $event = reset($events);
882 // Check that the event data is valid.
883 $this->assertInstanceOf('\mod_quiz\event\page_break_created', $event);
884 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
885 $this->assertEventContextNotUsed($event);
889 * Test the page break deleted event.
891 * There is no external API for deleting page break, so the unit test will simply
892 * create and trigger the event and ensure the event data is returned as expected.
894 public function test_page_deleted_created() {
895 $quizobj = $this->prepare_quiz();
897 $params = [
898 'objectid' => 1,
899 'context' => context_module::instance($quizobj->get_cmid()),
900 'other' => [
901 'quizid' => $quizobj->get_quizid(),
902 'slotnumber' => 3,
905 $event = \mod_quiz\event\page_break_deleted::create($params);
907 // Trigger and capture the event.
908 $sink = $this->redirectEvents();
909 $event->trigger();
910 $events = $sink->get_events();
911 $event = reset($events);
913 // Check that the event data is valid.
914 $this->assertInstanceOf('\mod_quiz\event\page_break_deleted', $event);
915 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
916 $this->assertEventContextNotUsed($event);
920 * Test the quiz grade updated event.
922 * There is no external API for updating quiz grade, so the unit test will simply
923 * create and trigger the event and ensure the event data is returned as expected.
925 public function test_quiz_grade_updated() {
926 $quizobj = $this->prepare_quiz();
928 $params = [
929 'objectid' => $quizobj->get_quizid(),
930 'context' => context_module::instance($quizobj->get_cmid()),
931 'other' => [
932 'oldgrade' => 1,
933 'newgrade' => 3,
936 $event = \mod_quiz\event\quiz_grade_updated::create($params);
938 // Trigger and capture the event.
939 $sink = $this->redirectEvents();
940 $event->trigger();
941 $events = $sink->get_events();
942 $event = reset($events);
944 // Check that the event data is valid.
945 $this->assertInstanceOf('\mod_quiz\event\quiz_grade_updated', $event);
946 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
947 $this->assertEventContextNotUsed($event);
951 * Test the quiz re-paginated event.
953 * There is no external API for re-paginating quiz, so the unit test will simply
954 * create and trigger the event and ensure the event data is returned as expected.
956 public function test_quiz_repaginated() {
957 $quizobj = $this->prepare_quiz();
959 $params = [
960 'objectid' => $quizobj->get_quizid(),
961 'context' => context_module::instance($quizobj->get_cmid()),
962 'other' => [
963 'slotsperpage' => 3,
966 $event = \mod_quiz\event\quiz_repaginated::create($params);
968 // Trigger and capture the event.
969 $sink = $this->redirectEvents();
970 $event->trigger();
971 $events = $sink->get_events();
972 $event = reset($events);
974 // Check that the event data is valid.
975 $this->assertInstanceOf('\mod_quiz\event\quiz_repaginated', $event);
976 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
977 $this->assertEventContextNotUsed($event);
981 * Test the section break created event.
983 * There is no external API for creating section break, so the unit test will simply
984 * create and trigger the event and ensure the event data is returned as expected.
986 public function test_section_break_created() {
987 $quizobj = $this->prepare_quiz();
989 $params = [
990 'objectid' => 1,
991 'context' => context_module::instance($quizobj->get_cmid()),
992 'other' => [
993 'quizid' => $quizobj->get_quizid(),
994 'firstslotid' => 1,
995 'firstslotnumber' => 2,
996 'title' => 'New title'
999 $event = \mod_quiz\event\section_break_created::create($params);
1001 // Trigger and capture the event.
1002 $sink = $this->redirectEvents();
1003 $event->trigger();
1004 $events = $sink->get_events();
1005 $event = reset($events);
1007 // Check that the event data is valid.
1008 $this->assertInstanceOf('\mod_quiz\event\section_break_created', $event);
1009 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1010 $this->assertStringContainsString($params['other']['title'], $event->get_description());
1011 $this->assertEventContextNotUsed($event);
1015 * Test the section break deleted event.
1017 * There is no external API for deleting section break, so the unit test will simply
1018 * create and trigger the event and ensure the event data is returned as expected.
1020 public function test_section_break_deleted() {
1021 $quizobj = $this->prepare_quiz();
1023 $params = [
1024 'objectid' => 1,
1025 'context' => context_module::instance($quizobj->get_cmid()),
1026 'other' => [
1027 'quizid' => $quizobj->get_quizid(),
1028 'firstslotid' => 1,
1029 'firstslotnumber' => 2
1032 $event = \mod_quiz\event\section_break_deleted::create($params);
1034 // Trigger and capture the event.
1035 $sink = $this->redirectEvents();
1036 $event->trigger();
1037 $events = $sink->get_events();
1038 $event = reset($events);
1040 // Check that the event data is valid.
1041 $this->assertInstanceOf('\mod_quiz\event\section_break_deleted', $event);
1042 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1043 $this->assertEventContextNotUsed($event);
1047 * Test the section shuffle updated event.
1049 * There is no external API for updating section shuffle, so the unit test will simply
1050 * create and trigger the event and ensure the event data is returned as expected.
1052 public function test_section_shuffle_updated() {
1053 $quizobj = $this->prepare_quiz();
1055 $params = [
1056 'objectid' => 1,
1057 'context' => context_module::instance($quizobj->get_cmid()),
1058 'other' => [
1059 'quizid' => $quizobj->get_quizid(),
1060 'firstslotnumber' => 2,
1061 'shuffle' => true
1064 $event = \mod_quiz\event\section_shuffle_updated::create($params);
1066 // Trigger and capture the event.
1067 $sink = $this->redirectEvents();
1068 $event->trigger();
1069 $events = $sink->get_events();
1070 $event = reset($events);
1072 // Check that the event data is valid.
1073 $this->assertInstanceOf('\mod_quiz\event\section_shuffle_updated', $event);
1074 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1075 $this->assertEventContextNotUsed($event);
1079 * Test the section title updated event.
1081 * There is no external API for updating section title, so the unit test will simply
1082 * create and trigger the event and ensure the event data is returned as expected.
1084 public function test_section_title_updated() {
1085 $quizobj = $this->prepare_quiz();
1087 $params = [
1088 'objectid' => 1,
1089 'context' => context_module::instance($quizobj->get_cmid()),
1090 'other' => [
1091 'quizid' => $quizobj->get_quizid(),
1092 'firstslotid' => 1,
1093 'firstslotnumber' => 2,
1094 'newtitle' => 'New title'
1097 $event = \mod_quiz\event\section_title_updated::create($params);
1099 // Trigger and capture the event.
1100 $sink = $this->redirectEvents();
1101 $event->trigger();
1102 $events = $sink->get_events();
1103 $event = reset($events);
1105 // Check that the event data is valid.
1106 $this->assertInstanceOf('\mod_quiz\event\section_title_updated', $event);
1107 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1108 $this->assertStringContainsString($params['other']['newtitle'], $event->get_description());
1109 $this->assertEventContextNotUsed($event);
1113 * Test the slot created event.
1115 * There is no external API for creating slot, so the unit test will simply
1116 * create and trigger the event and ensure the event data is returned as expected.
1118 public function test_slot_created() {
1119 $quizobj = $this->prepare_quiz();
1121 $params = [
1122 'objectid' => 1,
1123 'context' => context_module::instance($quizobj->get_cmid()),
1124 'other' => [
1125 'quizid' => $quizobj->get_quizid(),
1126 'slotnumber' => 1,
1127 'page' => 1
1130 $event = \mod_quiz\event\slot_created::create($params);
1132 // Trigger and capture the event.
1133 $sink = $this->redirectEvents();
1134 $event->trigger();
1135 $events = $sink->get_events();
1136 $event = reset($events);
1138 // Check that the event data is valid.
1139 $this->assertInstanceOf('\mod_quiz\event\slot_created', $event);
1140 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1141 $this->assertEventContextNotUsed($event);
1145 * Test the slot deleted event.
1147 * There is no external API for deleting slot, so the unit test will simply
1148 * create and trigger the event and ensure the event data is returned as expected.
1150 public function test_slot_deleted() {
1151 $quizobj = $this->prepare_quiz();
1153 $params = [
1154 'objectid' => 1,
1155 'context' => context_module::instance($quizobj->get_cmid()),
1156 'other' => [
1157 'quizid' => $quizobj->get_quizid(),
1158 'slotnumber' => 1,
1161 $event = \mod_quiz\event\slot_deleted::create($params);
1163 // Trigger and capture the event.
1164 $sink = $this->redirectEvents();
1165 $event->trigger();
1166 $events = $sink->get_events();
1167 $event = reset($events);
1169 // Check that the event data is valid.
1170 $this->assertInstanceOf('\mod_quiz\event\slot_deleted', $event);
1171 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1172 $this->assertEventContextNotUsed($event);
1176 * Test the slot mark updated event.
1178 * There is no external API for updating slot mark, so the unit test will simply
1179 * create and trigger the event and ensure the event data is returned as expected.
1181 public function test_slot_mark_updated() {
1182 $quizobj = $this->prepare_quiz();
1184 $params = [
1185 'objectid' => 1,
1186 'context' => context_module::instance($quizobj->get_cmid()),
1187 'other' => [
1188 'quizid' => $quizobj->get_quizid(),
1189 'previousmaxmark' => 1,
1190 'newmaxmark' => 2,
1193 $event = \mod_quiz\event\slot_mark_updated::create($params);
1195 // Trigger and capture the event.
1196 $sink = $this->redirectEvents();
1197 $event->trigger();
1198 $events = $sink->get_events();
1199 $event = reset($events);
1201 // Check that the event data is valid.
1202 $this->assertInstanceOf('\mod_quiz\event\slot_mark_updated', $event);
1203 $this->assertEquals($quizobj->get_context(), $event->get_context());
1204 $this->assertEventContextNotUsed($event);
1208 * Test slot_grade_item_updated.
1210 * @covers \mod_quiz\event\slot_grade_item_updated
1212 public function test_slot_grade_item_updated() {
1213 global $USER;
1215 $quizobj = $this->prepare_quiz();
1216 /** @var \mod_quiz_generator $quizgenerator */
1217 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
1218 $gradeitem = $quizgenerator->create_grade_item(
1219 ['quizid' => $quizobj->get_quizid(), 'name' => 'Awesomeness!']);
1220 $stucture = $quizobj->get_structure();
1221 $slot = $stucture->get_slot_by_number(1);
1223 // Trigger and capture the event.
1224 $sink = $this->redirectEvents();
1225 $stucture->update_slot_grade_item($slot, $gradeitem->id);
1226 $events = $sink->get_events();
1227 /** @var slot_grade_item_updated $event */
1228 $event = reset($events);
1230 // Check that the event data is valid.
1231 $this->assertInstanceOf(slot_grade_item_updated::class, $event);
1232 $this->assertEquals($quizobj->get_context(), $event->get_context());
1233 $this->assertEventContextNotUsed($event);
1234 $this->assertEquals(new \moodle_url('/mod/quiz/editgrading.php', ['cmid' => $quizobj->get_cmid()]),
1235 $event->get_url());
1236 $this->assertEquals("The user with id '$USER->id' updated the slot with id '{$slot->id}' " .
1237 "belonging to the quiz with course module id '{$quizobj->get_cmid()}'. " .
1238 "The grade item this slot contributes to was changed from '' to '$gradeitem->id'.",
1239 $event->get_description());
1241 // Check nothing logged if the value is not changed.
1242 $sink = $this->redirectEvents();
1243 $stucture->update_slot_grade_item($slot, $gradeitem->id);
1244 $events = $sink->get_events();
1245 $this->assertCount(0, $events);
1249 * Test the slot moved event.
1251 * There is no external API for moving slot, so the unit test will simply
1252 * create and trigger the event and ensure the event data is returned as expected.
1254 public function test_slot_moved() {
1255 $quizobj = $this->prepare_quiz();
1257 $params = [
1258 'objectid' => 1,
1259 'context' => context_module::instance($quizobj->get_cmid()),
1260 'other' => [
1261 'quizid' => $quizobj->get_quizid(),
1262 'previousslotnumber' => 1,
1263 'afterslotnumber' => 2,
1264 'page' => 1
1267 $event = \mod_quiz\event\slot_moved::create($params);
1269 // Trigger and capture the event.
1270 $sink = $this->redirectEvents();
1271 $event->trigger();
1272 $events = $sink->get_events();
1273 $event = reset($events);
1275 // Check that the event data is valid.
1276 $this->assertInstanceOf('\mod_quiz\event\slot_moved', $event);
1277 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1278 $this->assertEventContextNotUsed($event);
1282 * Test the slot require previous updated event.
1284 * There is no external API for updating slot require previous option, so the unit test will simply
1285 * create and trigger the event and ensure the event data is returned as expected.
1287 public function test_slot_requireprevious_updated() {
1288 $quizobj = $this->prepare_quiz();
1290 $params = [
1291 'objectid' => 1,
1292 'context' => context_module::instance($quizobj->get_cmid()),
1293 'other' => [
1294 'quizid' => $quizobj->get_quizid(),
1295 'requireprevious' => true
1298 $event = \mod_quiz\event\slot_requireprevious_updated::create($params);
1300 // Trigger and capture the event.
1301 $sink = $this->redirectEvents();
1302 $event->trigger();
1303 $events = $sink->get_events();
1304 $event = reset($events);
1306 // Check that the event data is valid.
1307 $this->assertInstanceOf('\mod_quiz\event\slot_requireprevious_updated', $event);
1308 $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1309 $this->assertEventContextNotUsed($event);