Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 311 and 403] [Versions 400 and 403] [Versions 401 and 403]

   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.
  13  //
  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/>.
  16  
  17  /**
  18   * Quiz events tests.
  19   *
  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
  24   */
  25  
  26  namespace mod_quiz\event;
  27  
  28  use mod_quiz\quiz_attempt;
  29  use mod_quiz\quiz_settings;
  30  use context_module;
  31  
  32  /**
  33   * Unit tests for quiz events.
  34   *
  35   * @package    mod_quiz
  36   * @category   phpunit
  37   * @copyright  2013 Adrian Greeve
  38   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  39   */
  40  class events_test extends \advanced_testcase {
  41  
  42      /**
  43       * Setup a quiz.
  44       *
  45       * @return quiz_settings the generated quiz.
  46       */
  47      protected function prepare_quiz() {
  48  
  49          $this->resetAfterTest(true);
  50  
  51          // Create a course
  52          $course = $this->getDataGenerator()->create_course();
  53  
  54          // Make a quiz.
  55          $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
  56  
  57          $quiz = $quizgenerator->create_instance(['course' => $course->id, 'questionsperpage' => 0,
  58                  'grade' => 100.0, 'sumgrades' => 2]);
  59  
  60          $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
  61  
  62          // Create a couple of questions.
  63          $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
  64  
  65          $cat = $questiongenerator->create_question_category();
  66          $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
  67          $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
  68  
  69          // Add them to the quiz.
  70          quiz_add_quiz_question($saq->id, $quiz);
  71          quiz_add_quiz_question($numq->id, $quiz);
  72  
  73          // Make a user to do the quiz.
  74          $user1 = $this->getDataGenerator()->create_user();
  75          $this->setUser($user1);
  76  
  77          return quiz_settings::create($quiz->id, $user1->id);
  78      }
  79  
  80      /**
  81       * Setup a quiz attempt at the quiz created by {@link prepare_quiz()}.
  82       *
  83       * @param \mod_quiz\quiz_settings $quizobj the generated quiz.
  84       * @param bool $ispreview Make the attempt a preview attempt when true.
  85       * @return array with three elements, array($quizobj, $quba, $attempt)
  86       */
  87      protected function prepare_quiz_attempt($quizobj, $ispreview = false) {
  88          // Start the attempt.
  89          $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
  90          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
  91  
  92          $timenow = time();
  93          $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, $ispreview);
  94          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
  95          quiz_attempt_save_started($quizobj, $quba, $attempt);
  96  
  97          return [$quizobj, $quba, $attempt];
  98      }
  99  
 100      /**
 101       * Setup some convenience test data with a single attempt.
 102       *
 103       * @param bool $ispreview Make the attempt a preview attempt when true.
 104       * @return array with three elements, array($quizobj, $quba, $attempt)
 105       */
 106      protected function prepare_quiz_data($ispreview = false) {
 107          $quizobj = $this->prepare_quiz();
 108          return $this->prepare_quiz_attempt($quizobj, $ispreview);
 109      }
 110  
 111      public function test_attempt_submitted() {
 112  
 113          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 114          $attemptobj = quiz_attempt::create($attempt->id);
 115  
 116          // Catch the event.
 117          $sink = $this->redirectEvents();
 118  
 119          $timefinish = time();
 120          $attemptobj->process_finish($timefinish, false);
 121          $events = $sink->get_events();
 122          $sink->close();
 123  
 124          // Validate the event.
 125          $this->assertCount(3, $events);
 126          $event = $events[2];
 127          $this->assertInstanceOf('\mod_quiz\event\attempt_submitted', $event);
 128          $this->assertEquals('quiz_attempts', $event->objecttable);
 129          $this->assertEquals($quizobj->get_context(), $event->get_context());
 130          $this->assertEquals($attempt->userid, $event->relateduserid);
 131          $this->assertEquals(null, $event->other['submitterid']); // Should be the user, but PHP Unit complains...
 132          $this->assertEventContextNotUsed($event);
 133      }
 134  
 135      public function test_attempt_becameoverdue() {
 136  
 137          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 138          $attemptobj = quiz_attempt::create($attempt->id);
 139  
 140          // Catch the event.
 141          $sink = $this->redirectEvents();
 142          $timefinish = time();
 143          $attemptobj->process_going_overdue($timefinish, false);
 144          $events = $sink->get_events();
 145          $sink->close();
 146  
 147          $this->assertCount(1, $events);
 148          $event = $events[0];
 149          $this->assertInstanceOf('\mod_quiz\event\attempt_becameoverdue', $event);
 150          $this->assertEquals('quiz_attempts', $event->objecttable);
 151          $this->assertEquals($quizobj->get_context(), $event->get_context());
 152          $this->assertEquals($attempt->userid, $event->relateduserid);
 153          $this->assertNotEmpty($event->get_description());
 154          // Submitterid should be the user, but as we are in PHP Unit, CLI_SCRIPT is set to true which sets null in submitterid.
 155          $this->assertEquals(null, $event->other['submitterid']);
 156          $this->assertEventContextNotUsed($event);
 157      }
 158  
 159      public function test_attempt_abandoned() {
 160  
 161          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 162          $attemptobj = quiz_attempt::create($attempt->id);
 163  
 164          // Catch the event.
 165          $sink = $this->redirectEvents();
 166          $timefinish = time();
 167          $attemptobj->process_abandon($timefinish, false);
 168          $events = $sink->get_events();
 169          $sink->close();
 170  
 171          $this->assertCount(1, $events);
 172          $event = $events[0];
 173          $this->assertInstanceOf('\mod_quiz\event\attempt_abandoned', $event);
 174          $this->assertEquals('quiz_attempts', $event->objecttable);
 175          $this->assertEquals($quizobj->get_context(), $event->get_context());
 176          $this->assertEquals($attempt->userid, $event->relateduserid);
 177          // Submitterid should be the user, but as we are in PHP Unit, CLI_SCRIPT is set to true which sets null in submitterid.
 178          $this->assertEquals(null, $event->other['submitterid']);
 179          $this->assertEventContextNotUsed($event);
 180      }
 181  
 182      public function test_attempt_started() {
 183          $quizobj = $this->prepare_quiz();
 184  
 185          $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
 186          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
 187  
 188          $timenow = time();
 189          $attempt = quiz_create_attempt($quizobj, 1, false, $timenow);
 190          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
 191  
 192          // Trigger and capture the event.
 193          $sink = $this->redirectEvents();
 194          quiz_attempt_save_started($quizobj, $quba, $attempt);
 195          $events = $sink->get_events();
 196          $event = reset($events);
 197  
 198          // Check that the event data is valid.
 199          $this->assertInstanceOf('\mod_quiz\event\attempt_started', $event);
 200          $this->assertEquals('quiz_attempts', $event->objecttable);
 201          $this->assertEquals($attempt->id, $event->objectid);
 202          $this->assertEquals($attempt->userid, $event->relateduserid);
 203          $this->assertEquals($quizobj->get_context(), $event->get_context());
 204          $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
 205      }
 206  
 207      /**
 208       * Test the attempt question restarted event.
 209       *
 210       * There is no external API for replacing a question, so the unit test will simply
 211       * create and trigger the event and ensure the event data is returned as expected.
 212       */
 213      public function test_attempt_question_restarted() {
 214          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 215  
 216          $params = [
 217              'objectid' => 1,
 218              'relateduserid' => 2,
 219              'courseid' => $quizobj->get_courseid(),
 220              'context' => \context_module::instance($quizobj->get_cmid()),
 221              'other' => [
 222                  'quizid' => $quizobj->get_quizid(),
 223                  'page' => 2,
 224                  'slot' => 3,
 225                  'newquestionid' => 2
 226              ]
 227          ];
 228          $event = \mod_quiz\event\attempt_question_restarted::create($params);
 229  
 230          // Trigger and capture the event.
 231          $sink = $this->redirectEvents();
 232          $event->trigger();
 233          $events = $sink->get_events();
 234          $event = reset($events);
 235  
 236          // Check that the event data is valid.
 237          $this->assertInstanceOf('\mod_quiz\event\attempt_question_restarted', $event);
 238          $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
 239          $this->assertEventContextNotUsed($event);
 240      }
 241  
 242      /**
 243       * Test the attempt updated event.
 244       *
 245       * There is no external API for updating an attempt, so the unit test will simply
 246       * create and trigger the event and ensure the event data is returned as expected.
 247       */
 248      public function test_attempt_updated() {
 249          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 250  
 251          $params = [
 252              'objectid' => 1,
 253              'relateduserid' => 2,
 254              'courseid' => $quizobj->get_courseid(),
 255              'context' => \context_module::instance($quizobj->get_cmid()),
 256              'other' => [
 257                  'quizid' => $quizobj->get_quizid(),
 258                  'page' => 0
 259              ]
 260          ];
 261          $event = \mod_quiz\event\attempt_updated::create($params);
 262  
 263          // Trigger and capture the event.
 264          $sink = $this->redirectEvents();
 265          $event->trigger();
 266          $events = $sink->get_events();
 267          $event = reset($events);
 268  
 269          // Check that the event data is valid.
 270          $this->assertInstanceOf('\mod_quiz\event\attempt_updated', $event);
 271          $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
 272          $this->assertEventContextNotUsed($event);
 273      }
 274  
 275      /**
 276       * Test the attempt auto-saved event.
 277       *
 278       * There is no external API for auto-saving an attempt, so the unit test will simply
 279       * create and trigger the event and ensure the event data is returned as expected.
 280       */
 281      public function test_attempt_autosaved() {
 282          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 283  
 284          $params = [
 285              'objectid' => 1,
 286              'relateduserid' => 2,
 287              'courseid' => $quizobj->get_courseid(),
 288              'context' => \context_module::instance($quizobj->get_cmid()),
 289              'other' => [
 290                  'quizid' => $quizobj->get_quizid(),
 291                  'page' => 0
 292              ]
 293          ];
 294  
 295          $event = \mod_quiz\event\attempt_autosaved::create($params);
 296  
 297          // Trigger and capture the event.
 298          $sink = $this->redirectEvents();
 299          $event->trigger();
 300          $events = $sink->get_events();
 301          $event = reset($events);
 302  
 303          // Check that the event data is valid.
 304          $this->assertInstanceOf('\mod_quiz\event\attempt_autosaved', $event);
 305          $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
 306          $this->assertEventContextNotUsed($event);
 307      }
 308  
 309      /**
 310       * Test the edit page viewed event.
 311       *
 312       * There is no external API for updating a quiz, so the unit test will simply
 313       * create and trigger the event and ensure the event data is returned as expected.
 314       */
 315      public function test_edit_page_viewed() {
 316          $this->resetAfterTest();
 317  
 318          $this->setAdminUser();
 319          $course = $this->getDataGenerator()->create_course();
 320          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 321  
 322          $params = [
 323              'courseid' => $course->id,
 324              'context' => \context_module::instance($quiz->cmid),
 325              'other' => [
 326                  'quizid' => $quiz->id
 327              ]
 328          ];
 329          $event = \mod_quiz\event\edit_page_viewed::create($params);
 330  
 331          // Trigger and capture the event.
 332          $sink = $this->redirectEvents();
 333          $event->trigger();
 334          $events = $sink->get_events();
 335          $event = reset($events);
 336  
 337          // Check that the event data is valid.
 338          $this->assertInstanceOf('\mod_quiz\event\edit_page_viewed', $event);
 339          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 340          $this->assertEventContextNotUsed($event);
 341      }
 342  
 343      /**
 344       * Test the attempt deleted event.
 345       */
 346      public function test_attempt_deleted() {
 347          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 348  
 349          // Trigger and capture the event.
 350          $sink = $this->redirectEvents();
 351          quiz_delete_attempt($attempt, $quizobj->get_quiz());
 352          $events = $sink->get_events();
 353          $event = reset($events);
 354  
 355          // Check that the event data is valid.
 356          $this->assertInstanceOf('\mod_quiz\event\attempt_deleted', $event);
 357          $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
 358          $this->assertEventContextNotUsed($event);
 359      }
 360  
 361      /**
 362       * Test that preview attempt deletions are not logged.
 363       */
 364      public function test_preview_attempt_deleted() {
 365          // Create quiz with preview attempt.
 366          list($quizobj, $quba, $previewattempt) = $this->prepare_quiz_data(true);
 367  
 368          // Delete a preview attempt, capturing events.
 369          $sink = $this->redirectEvents();
 370          quiz_delete_attempt($previewattempt, $quizobj->get_quiz());
 371  
 372          // Verify that no events were generated.
 373          $this->assertEmpty($sink->get_events());
 374      }
 375  
 376      /**
 377       * Test the report viewed event.
 378       *
 379       * There is no external API for viewing reports, so the unit test will simply
 380       * create and trigger the event and ensure the event data is returned as expected.
 381       */
 382      public function test_report_viewed() {
 383          $this->resetAfterTest();
 384  
 385          $this->setAdminUser();
 386          $course = $this->getDataGenerator()->create_course();
 387          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 388  
 389          $params = [
 390              'context' => $context = \context_module::instance($quiz->cmid),
 391              'other' => [
 392                  'quizid' => $quiz->id,
 393                  'reportname' => 'overview'
 394              ]
 395          ];
 396          $event = \mod_quiz\event\report_viewed::create($params);
 397  
 398          // Trigger and capture the event.
 399          $sink = $this->redirectEvents();
 400          $event->trigger();
 401          $events = $sink->get_events();
 402          $event = reset($events);
 403  
 404          // Check that the event data is valid.
 405          $this->assertInstanceOf('\mod_quiz\event\report_viewed', $event);
 406          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 407          $this->assertEventContextNotUsed($event);
 408      }
 409  
 410      /**
 411       * Test the attempt reviewed event.
 412       *
 413       * There is no external API for reviewing attempts, so the unit test will simply
 414       * create and trigger the event and ensure the event data is returned as expected.
 415       */
 416      public function test_attempt_reviewed() {
 417          $this->resetAfterTest();
 418  
 419          $this->setAdminUser();
 420          $course = $this->getDataGenerator()->create_course();
 421          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 422  
 423          $params = [
 424              'objectid' => 1,
 425              'relateduserid' => 2,
 426              'courseid' => $course->id,
 427              'context' => \context_module::instance($quiz->cmid),
 428              'other' => [
 429                  'quizid' => $quiz->id
 430              ]
 431          ];
 432          $event = \mod_quiz\event\attempt_reviewed::create($params);
 433  
 434          // Trigger and capture the event.
 435          $sink = $this->redirectEvents();
 436          $event->trigger();
 437          $events = $sink->get_events();
 438          $event = reset($events);
 439  
 440          // Check that the event data is valid.
 441          $this->assertInstanceOf('\mod_quiz\event\attempt_reviewed', $event);
 442          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 443          $this->assertEventContextNotUsed($event);
 444      }
 445  
 446      /**
 447       * Test the attempt summary viewed event.
 448       *
 449       * There is no external API for viewing the attempt summary, so the unit test will simply
 450       * create and trigger the event and ensure the event data is returned as expected.
 451       */
 452      public function test_attempt_summary_viewed() {
 453          $this->resetAfterTest();
 454  
 455          $this->setAdminUser();
 456          $course = $this->getDataGenerator()->create_course();
 457          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 458  
 459          $params = [
 460              'objectid' => 1,
 461              'relateduserid' => 2,
 462              'courseid' => $course->id,
 463              'context' => \context_module::instance($quiz->cmid),
 464              'other' => [
 465                  'quizid' => $quiz->id
 466              ]
 467          ];
 468          $event = \mod_quiz\event\attempt_summary_viewed::create($params);
 469  
 470          // Trigger and capture the event.
 471          $sink = $this->redirectEvents();
 472          $event->trigger();
 473          $events = $sink->get_events();
 474          $event = reset($events);
 475  
 476          // Check that the event data is valid.
 477          $this->assertInstanceOf('\mod_quiz\event\attempt_summary_viewed', $event);
 478          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 479          $this->assertEventContextNotUsed($event);
 480      }
 481  
 482      /**
 483       * Test the user override created event.
 484       *
 485       * There is no external API for creating a user override, so the unit test will simply
 486       * create and trigger the event and ensure the event data is returned as expected.
 487       */
 488      public function test_user_override_created() {
 489          $this->resetAfterTest();
 490  
 491          $this->setAdminUser();
 492          $course = $this->getDataGenerator()->create_course();
 493          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 494  
 495          $params = [
 496              'objectid' => 1,
 497              'relateduserid' => 2,
 498              'context' => \context_module::instance($quiz->cmid),
 499              'other' => [
 500                  'quizid' => $quiz->id
 501              ]
 502          ];
 503          $event = \mod_quiz\event\user_override_created::create($params);
 504  
 505          // Trigger and capture the event.
 506          $sink = $this->redirectEvents();
 507          $event->trigger();
 508          $events = $sink->get_events();
 509          $event = reset($events);
 510  
 511          // Check that the event data is valid.
 512          $this->assertInstanceOf('\mod_quiz\event\user_override_created', $event);
 513          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 514          $this->assertEventContextNotUsed($event);
 515      }
 516  
 517      /**
 518       * Test the group override created event.
 519       *
 520       * There is no external API for creating a group override, so the unit test will simply
 521       * create and trigger the event and ensure the event data is returned as expected.
 522       */
 523      public function test_group_override_created() {
 524          $this->resetAfterTest();
 525  
 526          $this->setAdminUser();
 527          $course = $this->getDataGenerator()->create_course();
 528          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 529  
 530          $params = [
 531              'objectid' => 1,
 532              'context' => \context_module::instance($quiz->cmid),
 533              'other' => [
 534                  'quizid' => $quiz->id,
 535                  'groupid' => 2
 536              ]
 537          ];
 538          $event = \mod_quiz\event\group_override_created::create($params);
 539  
 540          // Trigger and capture the event.
 541          $sink = $this->redirectEvents();
 542          $event->trigger();
 543          $events = $sink->get_events();
 544          $event = reset($events);
 545  
 546          // Check that the event data is valid.
 547          $this->assertInstanceOf('\mod_quiz\event\group_override_created', $event);
 548          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 549          $this->assertEventContextNotUsed($event);
 550      }
 551  
 552      /**
 553       * Test the user override updated event.
 554       *
 555       * There is no external API for updating a user override, so the unit test will simply
 556       * create and trigger the event and ensure the event data is returned as expected.
 557       */
 558      public function test_user_override_updated() {
 559          $this->resetAfterTest();
 560  
 561          $this->setAdminUser();
 562          $course = $this->getDataGenerator()->create_course();
 563          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 564  
 565          $params = [
 566              'objectid' => 1,
 567              'relateduserid' => 2,
 568              'context' => \context_module::instance($quiz->cmid),
 569              'other' => [
 570                  'quizid' => $quiz->id
 571              ]
 572          ];
 573          $event = \mod_quiz\event\user_override_updated::create($params);
 574  
 575          // Trigger and capture the event.
 576          $sink = $this->redirectEvents();
 577          $event->trigger();
 578          $events = $sink->get_events();
 579          $event = reset($events);
 580  
 581          // Check that the event data is valid.
 582          $this->assertInstanceOf('\mod_quiz\event\user_override_updated', $event);
 583          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 584          $this->assertEventContextNotUsed($event);
 585      }
 586  
 587      /**
 588       * Test the group override updated event.
 589       *
 590       * There is no external API for updating a group override, so the unit test will simply
 591       * create and trigger the event and ensure the event data is returned as expected.
 592       */
 593      public function test_group_override_updated() {
 594          $this->resetAfterTest();
 595  
 596          $this->setAdminUser();
 597          $course = $this->getDataGenerator()->create_course();
 598          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 599  
 600          $params = [
 601              'objectid' => 1,
 602              'context' => \context_module::instance($quiz->cmid),
 603              'other' => [
 604                  'quizid' => $quiz->id,
 605                  'groupid' => 2
 606              ]
 607          ];
 608          $event = \mod_quiz\event\group_override_updated::create($params);
 609  
 610          // Trigger and capture the event.
 611          $sink = $this->redirectEvents();
 612          $event->trigger();
 613          $events = $sink->get_events();
 614          $event = reset($events);
 615  
 616          // Check that the event data is valid.
 617          $this->assertInstanceOf('\mod_quiz\event\group_override_updated', $event);
 618          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 619          $this->assertEventContextNotUsed($event);
 620      }
 621  
 622      /**
 623       * Test the user override deleted event.
 624       */
 625      public function test_user_override_deleted() {
 626          global $DB;
 627  
 628          $this->resetAfterTest();
 629  
 630          $this->setAdminUser();
 631          $course = $this->getDataGenerator()->create_course();
 632          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 633  
 634          // Create an override.
 635          $override = new \stdClass();
 636          $override->quiz = $quiz->id;
 637          $override->userid = 2;
 638          $override->id = $DB->insert_record('quiz_overrides', $override);
 639  
 640          // Trigger and capture the event.
 641          $sink = $this->redirectEvents();
 642          quiz_delete_override($quiz, $override->id);
 643          $events = $sink->get_events();
 644          $event = reset($events);
 645  
 646          // Check that the event data is valid.
 647          $this->assertInstanceOf('\mod_quiz\event\user_override_deleted', $event);
 648          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 649          $this->assertEventContextNotUsed($event);
 650      }
 651  
 652      /**
 653       * Test the group override deleted event.
 654       */
 655      public function test_group_override_deleted() {
 656          global $DB;
 657  
 658          $this->resetAfterTest();
 659  
 660          $this->setAdminUser();
 661          $course = $this->getDataGenerator()->create_course();
 662          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 663  
 664          // Create an override.
 665          $override = new \stdClass();
 666          $override->quiz = $quiz->id;
 667          $override->groupid = 2;
 668          $override->id = $DB->insert_record('quiz_overrides', $override);
 669  
 670          // Trigger and capture the event.
 671          $sink = $this->redirectEvents();
 672          quiz_delete_override($quiz, $override->id);
 673          $events = $sink->get_events();
 674          $event = reset($events);
 675  
 676          // Check that the event data is valid.
 677          $this->assertInstanceOf('\mod_quiz\event\group_override_deleted', $event);
 678          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 679          $this->assertEventContextNotUsed($event);
 680      }
 681  
 682      /**
 683       * Test the attempt viewed event.
 684       *
 685       * There is no external API for continuing an attempt, so the unit test will simply
 686       * create and trigger the event and ensure the event data is returned as expected.
 687       */
 688      public function test_attempt_viewed() {
 689          $this->resetAfterTest();
 690  
 691          $this->setAdminUser();
 692          $course = $this->getDataGenerator()->create_course();
 693          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 694  
 695          $params = [
 696              'objectid' => 1,
 697              'relateduserid' => 2,
 698              'courseid' => $course->id,
 699              'context' => \context_module::instance($quiz->cmid),
 700              'other' => [
 701                  'quizid' => $quiz->id,
 702                  'page' => 0
 703              ]
 704          ];
 705          $event = \mod_quiz\event\attempt_viewed::create($params);
 706  
 707          // Trigger and capture the event.
 708          $sink = $this->redirectEvents();
 709          $event->trigger();
 710          $events = $sink->get_events();
 711          $event = reset($events);
 712  
 713          // Check that the event data is valid.
 714          $this->assertInstanceOf('\mod_quiz\event\attempt_viewed', $event);
 715          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 716          $this->assertEventContextNotUsed($event);
 717      }
 718  
 719      /**
 720       * Test the attempt previewed event.
 721       */
 722      public function test_attempt_preview_started() {
 723          $quizobj = $this->prepare_quiz();
 724  
 725          $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
 726          $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
 727  
 728          $timenow = time();
 729          $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, true);
 730          quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
 731  
 732          // Trigger and capture the event.
 733          $sink = $this->redirectEvents();
 734          quiz_attempt_save_started($quizobj, $quba, $attempt);
 735          $events = $sink->get_events();
 736          $event = reset($events);
 737  
 738          // Check that the event data is valid.
 739          $this->assertInstanceOf('\mod_quiz\event\attempt_preview_started', $event);
 740          $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
 741          $this->assertEventContextNotUsed($event);
 742      }
 743  
 744      /**
 745       * Test the question manually graded event.
 746       *
 747       * There is no external API for manually grading a question, so the unit test will simply
 748       * create and trigger the event and ensure the event data is returned as expected.
 749       */
 750      public function test_question_manually_graded() {
 751          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 752  
 753          $params = [
 754              'objectid' => 1,
 755              'courseid' => $quizobj->get_courseid(),
 756              'context' => \context_module::instance($quizobj->get_cmid()),
 757              'other' => [
 758                  'quizid' => $quizobj->get_quizid(),
 759                  'attemptid' => 2,
 760                  'slot' => 3
 761              ]
 762          ];
 763          $event = \mod_quiz\event\question_manually_graded::create($params);
 764  
 765          // Trigger and capture the event.
 766          $sink = $this->redirectEvents();
 767          $event->trigger();
 768          $events = $sink->get_events();
 769          $event = reset($events);
 770  
 771          // Check that the event data is valid.
 772          $this->assertInstanceOf('\mod_quiz\event\question_manually_graded', $event);
 773          $this->assertEquals(\context_module::instance($quizobj->get_cmid()), $event->get_context());
 774          $this->assertEventContextNotUsed($event);
 775      }
 776  
 777      /**
 778       * Test the attempt regraded event.
 779       *
 780       * There is no external API for regrading attempts, so the unit test will simply
 781       * create and trigger the event and ensure the event data is returned as expected.
 782       */
 783      public function test_attempt_regraded() {
 784          $this->resetAfterTest();
 785  
 786          $this->setAdminUser();
 787          $course = $this->getDataGenerator()->create_course();
 788          $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 789  
 790          $params = [
 791              'objectid' => 1,
 792              'relateduserid' => 2,
 793              'courseid' => $course->id,
 794              'context' => \context_module::instance($quiz->cmid),
 795              'other' => [
 796                  'quizid' => $quiz->id
 797              ]
 798          ];
 799          $event = \mod_quiz\event\attempt_regraded::create($params);
 800  
 801          // Trigger and capture the event.
 802          $sink = $this->redirectEvents();
 803          $event->trigger();
 804          $events = $sink->get_events();
 805          $event = reset($events);
 806  
 807          // Check that the event data is valid.
 808          $this->assertInstanceOf('\mod_quiz\event\attempt_regraded', $event);
 809          $this->assertEquals(\context_module::instance($quiz->cmid), $event->get_context());
 810          $this->assertEventContextNotUsed($event);
 811      }
 812  
 813      /**
 814       * Test the attempt notify manual graded event.
 815       * There is no external API for notification email when manual grading of user's attempt is completed,
 816       * so the unit test will simply create and trigger the event and ensure the event data is returned as expected.
 817       */
 818      public function test_attempt_manual_grading_completed() {
 819          $this->resetAfterTest();
 820          list($quizobj, $quba, $attempt) = $this->prepare_quiz_data();
 821          $attemptobj = quiz_attempt::create($attempt->id);
 822  
 823          $params = [
 824              'objectid' => $attemptobj->get_attemptid(),
 825              'relateduserid' => $attemptobj->get_userid(),
 826              'courseid' => $attemptobj->get_course()->id,
 827              'context' => \context_module::instance($attemptobj->get_cmid()),
 828              'other' => [
 829                  'quizid' => $attemptobj->get_quizid()
 830              ]
 831          ];
 832          $event = \mod_quiz\event\attempt_manual_grading_completed::create($params);
 833  
 834          // Catch the event.
 835          $sink = $this->redirectEvents();
 836          $event->trigger();
 837          $events = $sink->get_events();
 838          $sink->close();
 839  
 840          // Validate the event.
 841          $this->assertCount(1, $events);
 842          $event = reset($events);
 843          $this->assertInstanceOf('\mod_quiz\event\attempt_manual_grading_completed', $event);
 844          $this->assertEquals('quiz_attempts', $event->objecttable);
 845          $this->assertEquals($quizobj->get_context(), $event->get_context());
 846          $this->assertEquals($attempt->userid, $event->relateduserid);
 847          $this->assertNotEmpty($event->get_description());
 848          $this->assertEventContextNotUsed($event);
 849      }
 850  
 851      /**
 852       * Test the page break created event.
 853       *
 854       * There is no external API for creating page break, so the unit test will simply
 855       * create and trigger the event and ensure the event data is returned as expected.
 856       */
 857      public function test_page_break_created() {
 858          $quizobj = $this->prepare_quiz();
 859  
 860          $params = [
 861              'objectid' => 1,
 862              'context' => context_module::instance($quizobj->get_cmid()),
 863              'other' => [
 864                  'quizid' => $quizobj->get_quizid(),
 865                  'slotnumber' => 3,
 866              ]
 867          ];
 868          $event = \mod_quiz\event\page_break_created::create($params);
 869  
 870          // Trigger and capture the event.
 871          $sink = $this->redirectEvents();
 872          $event->trigger();
 873          $events = $sink->get_events();
 874          $event = reset($events);
 875  
 876          // Check that the event data is valid.
 877          $this->assertInstanceOf('\mod_quiz\event\page_break_created', $event);
 878          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
 879          $this->assertEventContextNotUsed($event);
 880      }
 881  
 882      /**
 883       * Test the page break deleted event.
 884       *
 885       * There is no external API for deleting page break, so the unit test will simply
 886       * create and trigger the event and ensure the event data is returned as expected.
 887       */
 888      public function test_page_deleted_created() {
 889          $quizobj = $this->prepare_quiz();
 890  
 891          $params = [
 892              'objectid' => 1,
 893              'context' => context_module::instance($quizobj->get_cmid()),
 894              'other' => [
 895                  'quizid' => $quizobj->get_quizid(),
 896                  'slotnumber' => 3,
 897              ]
 898          ];
 899          $event = \mod_quiz\event\page_break_deleted::create($params);
 900  
 901          // Trigger and capture the event.
 902          $sink = $this->redirectEvents();
 903          $event->trigger();
 904          $events = $sink->get_events();
 905          $event = reset($events);
 906  
 907          // Check that the event data is valid.
 908          $this->assertInstanceOf('\mod_quiz\event\page_break_deleted', $event);
 909          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
 910          $this->assertEventContextNotUsed($event);
 911      }
 912  
 913      /**
 914       * Test the quiz grade updated event.
 915       *
 916       * There is no external API for updating quiz grade, so the unit test will simply
 917       * create and trigger the event and ensure the event data is returned as expected.
 918       */
 919      public function test_quiz_grade_updated() {
 920          $quizobj = $this->prepare_quiz();
 921  
 922          $params = [
 923              'objectid' => $quizobj->get_quizid(),
 924              'context' => context_module::instance($quizobj->get_cmid()),
 925              'other' => [
 926                  'oldgrade' => 1,
 927                  'newgrade' => 3,
 928              ]
 929          ];
 930          $event = \mod_quiz\event\quiz_grade_updated::create($params);
 931  
 932          // Trigger and capture the event.
 933          $sink = $this->redirectEvents();
 934          $event->trigger();
 935          $events = $sink->get_events();
 936          $event = reset($events);
 937  
 938          // Check that the event data is valid.
 939          $this->assertInstanceOf('\mod_quiz\event\quiz_grade_updated', $event);
 940          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
 941          $this->assertEventContextNotUsed($event);
 942      }
 943  
 944      /**
 945       * Test the quiz re-paginated event.
 946       *
 947       * There is no external API for re-paginating quiz, so the unit test will simply
 948       * create and trigger the event and ensure the event data is returned as expected.
 949       */
 950      public function test_quiz_repaginated() {
 951          $quizobj = $this->prepare_quiz();
 952  
 953          $params = [
 954              'objectid' => $quizobj->get_quizid(),
 955              'context' => context_module::instance($quizobj->get_cmid()),
 956              'other' => [
 957                  'slotsperpage' => 3,
 958              ]
 959          ];
 960          $event = \mod_quiz\event\quiz_repaginated::create($params);
 961  
 962          // Trigger and capture the event.
 963          $sink = $this->redirectEvents();
 964          $event->trigger();
 965          $events = $sink->get_events();
 966          $event = reset($events);
 967  
 968          // Check that the event data is valid.
 969          $this->assertInstanceOf('\mod_quiz\event\quiz_repaginated', $event);
 970          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
 971          $this->assertEventContextNotUsed($event);
 972      }
 973  
 974      /**
 975       * Test the section break created event.
 976       *
 977       * There is no external API for creating section break, so the unit test will simply
 978       * create and trigger the event and ensure the event data is returned as expected.
 979       */
 980      public function test_section_break_created() {
 981          $quizobj = $this->prepare_quiz();
 982  
 983          $params = [
 984              'objectid' => 1,
 985              'context' => context_module::instance($quizobj->get_cmid()),
 986              'other' => [
 987                  'quizid' => $quizobj->get_quizid(),
 988                  'firstslotid' => 1,
 989                  'firstslotnumber' => 2,
 990                  'title' => 'New title'
 991              ]
 992          ];
 993          $event = \mod_quiz\event\section_break_created::create($params);
 994  
 995          // Trigger and capture the event.
 996          $sink = $this->redirectEvents();
 997          $event->trigger();
 998          $events = $sink->get_events();
 999          $event = reset($events);
1000  
1001          // Check that the event data is valid.
1002          $this->assertInstanceOf('\mod_quiz\event\section_break_created', $event);
1003          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1004          $this->assertStringContainsString($params['other']['title'], $event->get_description());
1005          $this->assertEventContextNotUsed($event);
1006      }
1007  
1008      /**
1009       * Test the section break deleted event.
1010       *
1011       * There is no external API for deleting section break, so the unit test will simply
1012       * create and trigger the event and ensure the event data is returned as expected.
1013       */
1014      public function test_section_break_deleted() {
1015          $quizobj = $this->prepare_quiz();
1016  
1017          $params = [
1018              'objectid' => 1,
1019              'context' => context_module::instance($quizobj->get_cmid()),
1020              'other' => [
1021                  'quizid' => $quizobj->get_quizid(),
1022                  'firstslotid' => 1,
1023                  'firstslotnumber' => 2
1024              ]
1025          ];
1026          $event = \mod_quiz\event\section_break_deleted::create($params);
1027  
1028          // Trigger and capture the event.
1029          $sink = $this->redirectEvents();
1030          $event->trigger();
1031          $events = $sink->get_events();
1032          $event = reset($events);
1033  
1034          // Check that the event data is valid.
1035          $this->assertInstanceOf('\mod_quiz\event\section_break_deleted', $event);
1036          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1037          $this->assertEventContextNotUsed($event);
1038      }
1039  
1040      /**
1041       * Test the section shuffle updated event.
1042       *
1043       * There is no external API for updating section shuffle, so the unit test will simply
1044       * create and trigger the event and ensure the event data is returned as expected.
1045       */
1046      public function test_section_shuffle_updated() {
1047          $quizobj = $this->prepare_quiz();
1048  
1049          $params = [
1050              'objectid' => 1,
1051              'context' => context_module::instance($quizobj->get_cmid()),
1052              'other' => [
1053                  'quizid' => $quizobj->get_quizid(),
1054                  'firstslotnumber' => 2,
1055                  'shuffle' => true
1056              ]
1057          ];
1058          $event = \mod_quiz\event\section_shuffle_updated::create($params);
1059  
1060          // Trigger and capture the event.
1061          $sink = $this->redirectEvents();
1062          $event->trigger();
1063          $events = $sink->get_events();
1064          $event = reset($events);
1065  
1066          // Check that the event data is valid.
1067          $this->assertInstanceOf('\mod_quiz\event\section_shuffle_updated', $event);
1068          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1069          $this->assertEventContextNotUsed($event);
1070      }
1071  
1072      /**
1073       * Test the section title updated event.
1074       *
1075       * There is no external API for updating section title, so the unit test will simply
1076       * create and trigger the event and ensure the event data is returned as expected.
1077       */
1078      public function test_section_title_updated() {
1079          $quizobj = $this->prepare_quiz();
1080  
1081          $params = [
1082              'objectid' => 1,
1083              'context' => context_module::instance($quizobj->get_cmid()),
1084              'other' => [
1085                  'quizid' => $quizobj->get_quizid(),
1086                  'firstslotid' => 1,
1087                  'firstslotnumber' => 2,
1088                  'newtitle' => 'New title'
1089              ]
1090          ];
1091          $event = \mod_quiz\event\section_title_updated::create($params);
1092  
1093          // Trigger and capture the event.
1094          $sink = $this->redirectEvents();
1095          $event->trigger();
1096          $events = $sink->get_events();
1097          $event = reset($events);
1098  
1099          // Check that the event data is valid.
1100          $this->assertInstanceOf('\mod_quiz\event\section_title_updated', $event);
1101          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1102          $this->assertStringContainsString($params['other']['newtitle'], $event->get_description());
1103          $this->assertEventContextNotUsed($event);
1104      }
1105  
1106      /**
1107       * Test the slot created event.
1108       *
1109       * There is no external API for creating slot, so the unit test will simply
1110       * create and trigger the event and ensure the event data is returned as expected.
1111       */
1112      public function test_slot_created() {
1113          $quizobj = $this->prepare_quiz();
1114  
1115          $params = [
1116              'objectid' => 1,
1117              'context' => context_module::instance($quizobj->get_cmid()),
1118              'other' => [
1119                  'quizid' => $quizobj->get_quizid(),
1120                  'slotnumber' => 1,
1121                  'page' => 1
1122              ]
1123          ];
1124          $event = \mod_quiz\event\slot_created::create($params);
1125  
1126          // Trigger and capture the event.
1127          $sink = $this->redirectEvents();
1128          $event->trigger();
1129          $events = $sink->get_events();
1130          $event = reset($events);
1131  
1132          // Check that the event data is valid.
1133          $this->assertInstanceOf('\mod_quiz\event\slot_created', $event);
1134          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1135          $this->assertEventContextNotUsed($event);
1136      }
1137  
1138      /**
1139       * Test the slot deleted event.
1140       *
1141       * There is no external API for deleting slot, so the unit test will simply
1142       * create and trigger the event and ensure the event data is returned as expected.
1143       */
1144      public function test_slot_deleted() {
1145          $quizobj = $this->prepare_quiz();
1146  
1147          $params = [
1148              'objectid' => 1,
1149              'context' => context_module::instance($quizobj->get_cmid()),
1150              'other' => [
1151                  'quizid' => $quizobj->get_quizid(),
1152                  'slotnumber' => 1,
1153              ]
1154          ];
1155          $event = \mod_quiz\event\slot_deleted::create($params);
1156  
1157          // Trigger and capture the event.
1158          $sink = $this->redirectEvents();
1159          $event->trigger();
1160          $events = $sink->get_events();
1161          $event = reset($events);
1162  
1163          // Check that the event data is valid.
1164          $this->assertInstanceOf('\mod_quiz\event\slot_deleted', $event);
1165          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1166          $this->assertEventContextNotUsed($event);
1167      }
1168  
1169      /**
1170       * Test the slot mark updated event.
1171       *
1172       * There is no external API for updating slot mark, so the unit test will simply
1173       * create and trigger the event and ensure the event data is returned as expected.
1174       */
1175      public function test_slot_mark_updated() {
1176          $quizobj = $this->prepare_quiz();
1177  
1178          $params = [
1179              'objectid' => 1,
1180              'context' => context_module::instance($quizobj->get_cmid()),
1181              'other' => [
1182                  'quizid' => $quizobj->get_quizid(),
1183                  'previousmaxmark' => 1,
1184                  'newmaxmark' => 2,
1185              ]
1186          ];
1187          $event = \mod_quiz\event\slot_mark_updated::create($params);
1188  
1189          // Trigger and capture the event.
1190          $sink = $this->redirectEvents();
1191          $event->trigger();
1192          $events = $sink->get_events();
1193          $event = reset($events);
1194  
1195          // Check that the event data is valid.
1196          $this->assertInstanceOf('\mod_quiz\event\slot_mark_updated', $event);
1197          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1198          $this->assertEventContextNotUsed($event);
1199      }
1200  
1201      /**
1202       * Test the slot moved event.
1203       *
1204       * There is no external API for moving slot, so the unit test will simply
1205       * create and trigger the event and ensure the event data is returned as expected.
1206       */
1207      public function test_slot_moved() {
1208          $quizobj = $this->prepare_quiz();
1209  
1210          $params = [
1211              'objectid' => 1,
1212              'context' => context_module::instance($quizobj->get_cmid()),
1213              'other' => [
1214                  'quizid' => $quizobj->get_quizid(),
1215                  'previousslotnumber' => 1,
1216                  'afterslotnumber' => 2,
1217                  'page' => 1
1218              ]
1219          ];
1220          $event = \mod_quiz\event\slot_moved::create($params);
1221  
1222          // Trigger and capture the event.
1223          $sink = $this->redirectEvents();
1224          $event->trigger();
1225          $events = $sink->get_events();
1226          $event = reset($events);
1227  
1228          // Check that the event data is valid.
1229          $this->assertInstanceOf('\mod_quiz\event\slot_moved', $event);
1230          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1231          $this->assertEventContextNotUsed($event);
1232      }
1233  
1234      /**
1235       * Test the slot require previous updated event.
1236       *
1237       * There is no external API for updating slot require previous option, so the unit test will simply
1238       * create and trigger the event and ensure the event data is returned as expected.
1239       */
1240      public function test_slot_requireprevious_updated() {
1241          $quizobj = $this->prepare_quiz();
1242  
1243          $params = [
1244              'objectid' => 1,
1245              'context' => context_module::instance($quizobj->get_cmid()),
1246              'other' => [
1247                  'quizid' => $quizobj->get_quizid(),
1248                  'requireprevious' => true
1249              ]
1250          ];
1251          $event = \mod_quiz\event\slot_requireprevious_updated::create($params);
1252  
1253          // Trigger and capture the event.
1254          $sink = $this->redirectEvents();
1255          $event->trigger();
1256          $events = $sink->get_events();
1257          $event = reset($events);
1258  
1259          // Check that the event data is valid.
1260          $this->assertInstanceOf('\mod_quiz\event\slot_requireprevious_updated', $event);
1261          $this->assertEquals(context_module::instance($quizobj->get_cmid()), $event->get_context());
1262          $this->assertEventContextNotUsed($event);
1263      }
1264  }