Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]

   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   * PHPUnit tests for quiz_settings class.
  19   *
  20   * @package    quizaccess_seb
  21   * @author     Andrew Madden <andrewmadden@catalyst-au.net>
  22   * @copyright  2019 Catalyst IT
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  use quizaccess_seb\quiz_settings;
  27  use quizaccess_seb\settings_provider;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  require_once (__DIR__ . '/test_helper_trait.php');
  32  
  33  /**
  34   * PHPUnit tests for quiz_settings class.
  35   *
  36   * @copyright  2020 Catalyst IT
  37   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  38   */
  39  class quizaccess_seb_quiz_settings_testcase extends advanced_testcase {
  40      use quizaccess_seb_test_helper_trait;
  41  
  42      /** @var context_module $context Test context. */
  43      protected $context;
  44  
  45      /** @var moodle_url $url Test quiz URL. */
  46      protected $url;
  47  
  48      /**
  49       * Called before every test.
  50       */
  51      public function setUp(): void {
  52          parent::setUp();
  53  
  54          $this->resetAfterTest();
  55  
  56          $this->setAdminUser();
  57          $this->course = $this->getDataGenerator()->create_course();
  58          $this->quiz = $this->getDataGenerator()->create_module('quiz', [
  59              'course' => $this->course->id,
  60              'seb_requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
  61          ]);
  62          $this->context = context_module::instance($this->quiz->cmid);
  63          $this->url = new moodle_url("/mod/quiz/view.php", ['id' => $this->quiz->cmid]);
  64      }
  65  
  66      /**
  67       * Test that config is generated immediately prior to saving quiz settings.
  68       */
  69      public function test_config_is_created_from_quiz_settings() {
  70          // Test settings to populate the in the object.
  71          $settings = $this->get_test_settings();
  72          $settings->quizid = $this->quiz->id;
  73          $settings->cmid = $this->quiz->cmid;
  74  
  75          // Obtain the existing record that is created when using a generator.
  76          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
  77  
  78          // Update the settings with values from the test function.
  79          $quizsettings->from_record($settings);
  80          $quizsettings->save();
  81  
  82          $config = $quizsettings->get_config();
  83          $this->assertEquals(
  84              "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
  85  <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
  86  <plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
  87                  . "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
  88                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
  89                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
  90                  . "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
  91                  . "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
  92                  . "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
  93                  . "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
  94                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
  95                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
  96              $config);
  97      }
  98  
  99      /**
 100       * Test that config string gets updated from quiz settings.
 101       */
 102      public function test_config_is_updated_from_quiz_settings() {
 103          // Test settings to populate the in the object.
 104          $settings = $this->get_test_settings();
 105          $settings->quizid = $this->quiz->id;
 106          $settings->cmid = $this->quiz->cmid;
 107  
 108          // Obtain the existing record that is created when using a generator.
 109          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 110  
 111          // Update the settings with values from the test function.
 112          $quizsettings->from_record($settings);
 113          $quizsettings->save();
 114  
 115          $config = $quizsettings->get_config();
 116          $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
 117  <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
 118  <plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
 119              . "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 120              . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
 121              . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
 122              . "<key>URLFilterEnableContentFilter</key><false/><key>hashedQuitPassword</key>"
 123              . "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
 124              . "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
 125              . "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
 126              . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 127              . "<key>allowPreferencesWindow</key><false/></dict></plist>\n", $config);
 128  
 129          $quizsettings->set('filterembeddedcontent', 1); // Alter the settings.
 130          $quizsettings->save();
 131          $config = $quizsettings->get_config();
 132          $this->assertEquals("<?xml version=\"1.0\" encoding=\"UTF-8\"?>
 133  <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
 134  <plist version=\"1.0\"><dict><key>showTaskBar</key><true/><key>allowWlan</key><false/><key>showReloadButton</key><true/>"
 135              . "<key>showTime</key><false/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 136              . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><true/><key>audioMute</key><false/>"
 137              . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><true/>"
 138              . "<key>URLFilterEnableContentFilter</key><true/><key>hashedQuitPassword</key>"
 139              . "<string>9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08</string><key>URLFilterRules</key>"
 140              . "<array><dict><key>action</key><integer>1</integer><key>active</key><true/><key>expression</key>"
 141              . "<string>test.com</string><key>regex</key><false/></dict></array><key>startURL</key><string>$this->url</string>"
 142              . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 143              . "<key>allowPreferencesWindow</key><false/></dict></plist>\n", $config);
 144      }
 145  
 146      /**
 147       * Test that config key is generated immediately prior to saving quiz settings.
 148       */
 149      public function test_config_key_is_created_from_quiz_settings() {
 150          $settings = $this->get_test_settings();
 151  
 152          $quizsettings = new quiz_settings(0, $settings);
 153          $configkey = $quizsettings->get_config_key();
 154          $this->assertEquals("b35510bd754f9d106ff88b9d2dc1bb297cddc9fc7b4bdde2dbda4e7d9e4b50d8",
 155              $configkey
 156          );
 157      }
 158  
 159      /**
 160       * Test that config key is generated immediately prior to saving quiz settings.
 161       */
 162      public function test_config_key_is_updated_from_quiz_settings() {
 163          $settings = $this->get_test_settings();
 164  
 165          $quizsettings = new quiz_settings(0, $settings);
 166          $configkey = $quizsettings->get_config_key();
 167          $this->assertEquals("b35510bd754f9d106ff88b9d2dc1bb297cddc9fc7b4bdde2dbda4e7d9e4b50d8",
 168                  $configkey);
 169  
 170          $quizsettings->set('filterembeddedcontent', 1); // Alter the settings.
 171          $configkey = $quizsettings->get_config_key();
 172          $this->assertEquals("58010792504cccc18f7b0e5c9680fe60b567e8c1b5fb9798654cc9bad9ddf30c",
 173              $configkey);
 174      }
 175  
 176      /**
 177       * Test that different URL filter expressions are turned into config XML.
 178       *
 179       * @param stdClass $settings Quiz settings
 180       * @param string $expectedxml SEB Config XML.
 181       *
 182       * @dataProvider filter_rules_provider
 183       */
 184      public function test_filter_rules_added_to_config(stdClass $settings, string $expectedxml) {
 185          $quizsettings = new quiz_settings(0, $settings);
 186          $config = $quizsettings->get_config();
 187          $this->assertEquals($expectedxml, $config);
 188      }
 189  
 190      /**
 191       * Test that browser keys are validated and retrieved as an array instead of string.
 192       */
 193      public function test_browser_exam_keys_are_retrieved_as_array() {
 194          $quizsettings = new quiz_settings();
 195          $quizsettings->set('allowedbrowserexamkeys', "one two,three\nfour");
 196          $retrievedkeys = $quizsettings->get('allowedbrowserexamkeys');
 197          $this->assertEquals(['one', 'two', 'three', 'four'], $retrievedkeys);
 198      }
 199  
 200      /**
 201       * Test validation of Browser Exam Keys.
 202       *
 203       * @param string $bek Browser Exam Key.
 204       * @param string $expectederrorstring Expected error.
 205       *
 206       * @dataProvider bad_browser_exam_key_provider
 207       */
 208      public function test_browser_exam_keys_validation_errors($bek, $expectederrorstring) {
 209          $quizsettings = new quiz_settings();
 210          $quizsettings->set('allowedbrowserexamkeys', $bek);
 211          $quizsettings->validate();
 212          $errors = $quizsettings->get_errors();
 213          $this->assertContains($expectederrorstring, $errors);
 214      }
 215  
 216      /**
 217       * Test that uploaded seb file gets converted to config string.
 218       */
 219      public function test_config_file_uploaded_converted_to_config() {
 220          $url = new moodle_url("/mod/quiz/view.php", ['id' => $this->quiz->cmid]);
 221          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 222                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 223                  . "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
 224                  . "<key>allowWlan</key><false/><key>startURL</key><string>$url</string>"
 225                  . "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
 226          $itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
 227          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 228          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 229          $quizsettings->save();
 230          $config = $quizsettings->get_config();
 231          $this->assertEquals($xml, $config);
 232      }
 233  
 234      /**
 235       * Test test_no_config_file_uploaded
 236       */
 237      public function test_no_config_file_uploaded() {
 238          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 239          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 240          $cmid = $quizsettings->get('cmid');
 241          $this->expectException(moodle_exception::class);
 242          $this->expectExceptionMessage("No uploaded SEB config file could be found for quiz with cmid: {$cmid}");
 243          $quizsettings->get_config();
 244      }
 245  
 246      /**
 247       * A helper function to build a config file.
 248       *
 249       * @param mixed $allowuserquitseb Required allowQuit setting.
 250       * @param mixed $quitpassword Required hashedQuitPassword setting.
 251       *
 252       * @return string
 253       */
 254      protected function get_config_xml($allowuserquitseb = null, $quitpassword = null) {
 255          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 256              . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 257              . "<plist version=\"1.0\"><dict><key>allowWlan</key><false/><key>startURL</key>"
 258              . "<string>https://safeexambrowser.org/start</string>"
 259              . "<key>sendBrowserExamKey</key><true/>";
 260  
 261          if (!is_null($allowuserquitseb)) {
 262              $allowuserquitseb = empty($allowuserquitseb) ? 'false' : 'true';
 263              $xml .= "<key>allowQuit</key><{$allowuserquitseb}/>";
 264          }
 265  
 266          if (!is_null($quitpassword)) {
 267              $xml .= "<key>hashedQuitPassword</key><string>{$quitpassword}</string>";
 268          }
 269  
 270          $xml .= "</dict></plist>\n";
 271  
 272          return $xml;
 273      }
 274  
 275      /**
 276       * Test using USE_SEB_TEMPLATE and have it override settings from the template when they are set.
 277       */
 278      public function test_using_seb_template_override_settings_when_they_set_in_template() {
 279          $xml = $this->get_config_xml(true, 'password');
 280          $template = $this->create_template($xml);
 281  
 282          $this->assertStringContainsString("<key>startURL</key><string>https://safeexambrowser.org/start</string>", $template->get('content'));
 283          $this->assertStringContainsString("<key>allowQuit</key><true/>", $template->get('content'));
 284          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
 285  
 286          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 287          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
 288          $quizsettings->set('templateid', $template->get('id'));
 289          $quizsettings->set('allowuserquitseb', 1);
 290          $quizsettings->save();
 291  
 292          $this->assertStringContainsString(
 293              "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
 294              $quizsettings->get_config()
 295          );
 296  
 297          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 298          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 299  
 300          $quizsettings->set('quitpassword', 'new password');
 301          $quizsettings->save();
 302          $hashedpassword = hash('SHA256', 'new password');
 303          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 304          $this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 305          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
 306  
 307          $quizsettings->set('allowuserquitseb', 0);
 308          $quizsettings->set('quitpassword', '');
 309          $quizsettings->save();
 310          $this->assertStringContainsString("<key>allowQuit</key><false/>", $quizsettings->get_config());
 311          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 312      }
 313  
 314      /**
 315       * Test using USE_SEB_TEMPLATE and have it override settings from the template when they are not set.
 316       */
 317      public function test_using_seb_template_override_settings_when_not_set_in_template() {
 318          $xml = $this->get_config_xml();
 319          $template = $this->create_template($xml);
 320  
 321          $this->assertStringContainsString("<key>startURL</key><string>https://safeexambrowser.org/start</string>", $template->get('content'));
 322          $this->assertStringNotContainsString("<key>allowQuit</key><true/>", $template->get('content'));
 323          $this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>password</string>", $template->get('content'));
 324  
 325          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 326          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
 327          $quizsettings->set('templateid', $template->get('id'));
 328          $quizsettings->set('allowuserquitseb', 1);
 329          $quizsettings->save();
 330  
 331          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 332          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 333  
 334          $quizsettings->set('quitpassword', 'new password');
 335          $quizsettings->save();
 336          $hashedpassword = hash('SHA256', 'new password');
 337          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 338          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
 339  
 340          $quizsettings->set('allowuserquitseb', 0);
 341          $quizsettings->set('quitpassword', '');
 342          $quizsettings->save();
 343          $this->assertStringContainsString("<key>allowQuit</key><false/>", $quizsettings->get_config());
 344          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 345      }
 346  
 347      /**
 348       * Test using USE_SEB_UPLOAD_CONFIG and use settings from the file if they are set.
 349       */
 350      public function test_using_own_config_settings_are_not_overridden_if_set() {
 351          $xml = $this->get_config_xml(true, 'password');
 352          $this->create_module_test_file($xml, $this->quiz->cmid);
 353  
 354          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 355          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 356          $quizsettings->set('allowuserquitseb', 0);
 357          $quizsettings->set('quitpassword', '');
 358          $quizsettings->save();
 359  
 360          $this->assertStringContainsString(
 361              "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
 362              $quizsettings->get_config()
 363          );
 364  
 365          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 366          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 367  
 368          $quizsettings->set('quitpassword', 'new password');
 369          $quizsettings->save();
 370          $hashedpassword = hash('SHA256', 'new password');
 371  
 372          $this->assertStringNotContainsString("<key>hashedQuitPassword</key><string>{$hashedpassword}</string>", $quizsettings->get_config());
 373          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 374          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 375  
 376          $quizsettings->set('allowuserquitseb', 0);
 377          $quizsettings->set('quitpassword', '');
 378          $quizsettings->save();
 379  
 380          $this->assertStringContainsString("<key>allowQuit</key><true/>", $quizsettings->get_config());
 381          $this->assertStringContainsString("<key>hashedQuitPassword</key><string>password</string>", $quizsettings->get_config());
 382      }
 383  
 384      /**
 385       * Test using USE_SEB_UPLOAD_CONFIG and use settings from the file if they are not set.
 386       */
 387      public function test_using_own_config_settings_are_not_overridden_if_not_set() {
 388          $xml = $this->get_config_xml();
 389          $this->create_module_test_file($xml, $this->quiz->cmid);
 390  
 391          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 392          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 393          $quizsettings->set('allowuserquitseb', 1);
 394          $quizsettings->set('quitpassword', '');
 395          $quizsettings->save();
 396  
 397          $this->assertStringContainsString(
 398              "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id={$this->quiz->cmid}</string>",
 399              $quizsettings->get_config()
 400          );
 401  
 402          $this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
 403          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 404  
 405          $quizsettings->set('quitpassword', 'new password');
 406          $quizsettings->save();
 407  
 408          $this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
 409          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 410  
 411          $quizsettings->set('allowuserquitseb', 0);
 412          $quizsettings->set('quitpassword', '');
 413          $quizsettings->save();
 414  
 415          $this->assertStringNotContainsString("allowQuit", $quizsettings->get_config());
 416          $this->assertStringNotContainsString("hashedQuitPassword", $quizsettings->get_config());
 417      }
 418  
 419      /**
 420       * Test using USE_SEB_TEMPLATE populates the linkquitseb setting if a quitURL is found.
 421       */
 422      public function test_template_has_quit_url_set() {
 423          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 424              . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 425              . "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
 426              . "<key>allowWlan</key><false/><key>quitURL</key><string>http://seb.quit.url</string>"
 427              . "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
 428  
 429          $template = $this->create_template($xml);
 430  
 431          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 432          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_TEMPLATE);
 433          $quizsettings->set('templateid', $template->get('id'));
 434  
 435          $this->assertEmpty($quizsettings->get('linkquitseb'));
 436          $quizsettings->save();
 437  
 438          $this->assertNotEmpty($quizsettings->get('linkquitseb'));
 439          $this->assertEquals('http://seb.quit.url', $quizsettings->get('linkquitseb'));
 440      }
 441  
 442      /**
 443       * Test using USE_SEB_UPLOAD_CONFIG populates the linkquitseb setting if a quitURL is found.
 444       */
 445      public function test_config_file_uploaded_has_quit_url_set() {
 446          $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 447              . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 448              . "<plist version=\"1.0\"><dict><key>hashedQuitPassword</key><string>hashedpassword</string>"
 449              . "<key>allowWlan</key><false/><key>quitURL</key><string>http://seb.quit.url</string>"
 450              . "<key>sendBrowserExamKey</key><true/></dict></plist>\n";
 451  
 452          $itemid = $this->create_module_test_file($xml, $this->quiz->cmid);
 453          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 454          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 455  
 456          $this->assertEmpty($quizsettings->get('linkquitseb'));
 457          $quizsettings->save();
 458  
 459          $this->assertNotEmpty($quizsettings->get('linkquitseb'));
 460          $this->assertEquals('http://seb.quit.url', $quizsettings->get('linkquitseb'));
 461      }
 462  
 463      /**
 464       * Test template id set correctly.
 465       */
 466      public function test_templateid_set_correctly_when_save_settings() {
 467          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 468          $this->assertEquals(0, $quizsettings->get('templateid'));
 469  
 470          $template = $this->create_template();
 471          $templateid = $template->get('id');
 472  
 473          // Initially set to USE_SEB_TEMPLATE with a template id.
 474          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 475          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 476          $this->assertEquals($templateid, $quizsettings->get('templateid'));
 477  
 478          // Case for USE_SEB_NO, ensure template id reverts to 0.
 479          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_NO);
 480          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 481          $this->assertEquals(0, $quizsettings->get('templateid'));
 482  
 483          // Reverting back to USE_SEB_TEMPLATE.
 484          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 485  
 486          // Case for USE_SEB_CONFIG_MANUALLY, ensure template id reverts to 0.
 487          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CONFIG_MANUALLY);
 488          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 489          $this->assertEquals(0, $quizsettings->get('templateid'));
 490  
 491          // Reverting back to USE_SEB_TEMPLATE.
 492          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 493  
 494          // Case for USE_SEB_CLIENT_CONFIG, ensure template id reverts to 0.
 495          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_CLIENT_CONFIG);
 496          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 497          $this->assertEquals(0, $quizsettings->get('templateid'));
 498  
 499          // Reverting back to USE_SEB_TEMPLATE.
 500          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 501  
 502          // Case for USE_SEB_UPLOAD_CONFIG, ensure template id reverts to 0.
 503          $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
 504          $this->create_module_test_file($xml, $this->quiz->cmid);
 505          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_UPLOAD_CONFIG);
 506          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 507          $this->assertEquals(0, $quizsettings->get('templateid'));
 508  
 509          // Case for USE_SEB_TEMPLATE, ensure template id is correct.
 510          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 511          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 512          $this->assertEquals($templateid, $quizsettings->get('templateid'));
 513      }
 514  
 515      /**
 516       * Helper function in tests to set USE_SEB_TEMPLATE and a template id on the quiz settings.
 517       *
 518       * @param quiz_settings $quizsettings Given quiz settings instance.
 519       * @param int $savetype Type of SEB usage.
 520       * @param int $templateid Template ID.
 521       */
 522      public function save_settings_with_optional_template($quizsettings, $savetype, $templateid = 0) {
 523          $quizsettings->set('requiresafeexambrowser', $savetype);
 524          if (!empty($templateid)) {
 525              $quizsettings->set('templateid', $templateid);
 526          }
 527          $quizsettings->save();
 528      }
 529  
 530      /**
 531       * Bad browser exam key data provider.
 532       *
 533       * @return array
 534       */
 535      public function bad_browser_exam_key_provider() : array {
 536          return [
 537              'Short string' => ['fdsf434r',
 538                      'A key should be a 64-character hex string.'],
 539              'Non hex string' => ['aadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf678!',
 540                      'A key should be a 64-character hex string.'],
 541              'Non unique' => ["aadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789"
 542                      . "\naadf6799aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789aadf6789", 'The keys must all be different.'],
 543          ];
 544      }
 545  
 546      /**
 547       * Provide settings for different filter rules.
 548       *
 549       * @return array Test data.
 550       */
 551      public function filter_rules_provider() : array {
 552          return [
 553              'enabled simple expessions' => [
 554                  (object) [
 555                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 556                      'quizid' => 1,
 557                      'cmid' => 1,
 558                      'expressionsallowed' => "test.com\r\nsecond.hello",
 559                      'regexallowed' => '',
 560                      'expressionsblocked' => '',
 561                      'regexblocked' => '',
 562                  ],
 563                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 564                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 565                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 566                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 567                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 568                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 569                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 570                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 571                  . "<dict><key>action</key><integer>1</integer><key>active</key><true/>"
 572                  . "<key>expression</key><string>test.com</string>"
 573                  . "<key>regex</key><false/></dict><dict><key>action</key><integer>1</integer>"
 574                  . "<key>active</key><true/><key>expression</key>"
 575                  . "<string>second.hello</string><key>regex</key><false/></dict></array>"
 576                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 577                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 578                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 579              ],
 580              'blocked simple expessions' => [
 581                  (object) [
 582                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 583                      'quizid' => 1,
 584                      'cmid' => 1,
 585                      'expressionsallowed' => '',
 586                      'regexallowed' => '',
 587                      'expressionsblocked' => "test.com\r\nsecond.hello",
 588                      'regexblocked' => '',
 589                  ],
 590                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 591                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 592                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 593                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 594                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 595                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 596                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 597                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 598                  . "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
 599                  . "<key>expression</key><string>test.com</string>"
 600                  . "<key>regex</key><false/></dict><dict><key>action</key><integer>0</integer>"
 601                  . "<key>active</key><true/><key>expression</key>"
 602                  . "<string>second.hello</string><key>regex</key><false/></dict></array>"
 603                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 604                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 605                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 606              ],
 607              'enabled regex expessions' => [
 608                  (object) [
 609                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 610                      'quizid' => 1,
 611                      'cmid' => 1,
 612                      'expressionsallowed' => '',
 613                      'regexallowed' => "test.com\r\nsecond.hello",
 614                      'expressionsblocked' => '',
 615                      'regexblocked' => '',
 616                  ],
 617                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 618                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 619                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 620                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 621                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 622                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 623                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 624                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 625                  . "<dict><key>action</key><integer>1</integer><key>active</key><true/>"
 626                  . "<key>expression</key><string>test.com</string>"
 627                  . "<key>regex</key><true/></dict><dict><key>action</key><integer>1</integer>"
 628                  . "<key>active</key><true/><key>expression</key>"
 629                  . "<string>second.hello</string><key>regex</key><true/></dict></array>"
 630                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 631                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 632                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 633              ],
 634              'blocked regex expessions' => [
 635                  (object) [
 636                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 637                      'quizid' => 1,
 638                      'cmid' => 1,
 639                      'expressionsallowed' => '',
 640                      'regexallowed' => '',
 641                      'expressionsblocked' => '',
 642                      'regexblocked' => "test.com\r\nsecond.hello",
 643                  ],
 644                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 645                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 646                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 647                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 648                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 649                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 650                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 651                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array>"
 652                  . "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
 653                  . "<key>expression</key><string>test.com</string>"
 654                  . "<key>regex</key><true/></dict><dict><key>action</key><integer>0</integer>"
 655                  . "<key>active</key><true/><key>expression</key>"
 656                  . "<string>second.hello</string><key>regex</key><true/></dict></array>"
 657                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 658                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 659                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 660              ],
 661              'multiple simple expessions' => [
 662                  (object) [
 663                      'requiresafeexambrowser' => settings_provider::USE_SEB_CONFIG_MANUALLY,
 664                      'quizid' => 1,
 665                      'cmid' => 1,
 666                      'expressionsallowed' => "*",
 667                      'regexallowed' => '',
 668                      'expressionsblocked' => '',
 669                      'regexblocked' => "test.com\r\nsecond.hello",
 670                  ],
 671                  "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
 672                  . "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
 673                  . "<plist version=\"1.0\"><dict><key>showTaskBar</key><true/>"
 674                  . "<key>allowWlan</key><false/><key>showReloadButton</key>"
 675                  . "<true/><key>showTime</key><true/><key>showInputLanguage</key><true/><key>allowQuit</key><true/>"
 676                  . "<key>quitURLConfirm</key><true/><key>audioControlEnabled</key><false/><key>audioMute</key><false/>"
 677                  . "<key>allowSpellCheck</key><false/><key>browserWindowAllowReload</key><true/><key>URLFilterEnable</key><false/>"
 678                  . "<key>URLFilterEnableContentFilter</key><false/><key>URLFilterRules</key><array><dict><key>action</key>"
 679                  . "<integer>1</integer><key>active</key><true/><key>expression</key><string>*</string>"
 680                  . "<key>regex</key><false/></dict>"
 681                  . "<dict><key>action</key><integer>0</integer><key>active</key><true/>"
 682                  . "<key>expression</key><string>test.com</string>"
 683                  . "<key>regex</key><true/></dict><dict><key>action</key><integer>0</integer>"
 684                  . "<key>active</key><true/><key>expression</key>"
 685                  . "<string>second.hello</string><key>regex</key><true/></dict></array>"
 686                  . "<key>startURL</key><string>https://www.example.com/moodle/mod/quiz/view.php?id=1</string>"
 687                  . "<key>sendBrowserExamKey</key><true/><key>examSessionClearCookiesOnStart</key><false/>"
 688                  . "<key>allowPreferencesWindow</key><false/></dict></plist>\n",
 689              ],
 690          ];
 691      }
 692  
 693      /**
 694       * Test that config and config key are null when expected.
 695       */
 696      public function test_generates_config_values_as_null_when_expected() {
 697          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 698          $this->assertNotNull($quizsettings->get_config());
 699          $this->assertNotNull($quizsettings->get_config_key());
 700  
 701          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_NO);
 702          $quizsettings->save();
 703          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 704          $this->assertNull($quizsettings->get_config());
 705          $this->assertNull($quizsettings->get_config());
 706  
 707          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_UPLOAD_CONFIG);
 708          $xml = file_get_contents(__DIR__ . '/fixtures/unencrypted.seb');
 709          $this->create_module_test_file($xml, $this->quiz->cmid);
 710          $quizsettings->save();
 711          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 712          $this->assertNotNull($quizsettings->get_config());
 713          $this->assertNotNull($quizsettings->get_config_key());
 714  
 715          $quizsettings->set('requiresafeexambrowser', settings_provider::USE_SEB_CLIENT_CONFIG);
 716          $quizsettings->save();
 717          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 718          $this->assertNull($quizsettings->get_config());
 719          $this->assertNull($quizsettings->get_config_key());
 720  
 721          $template = $this->create_template();
 722          $templateid = $template->get('id');
 723          $this->save_settings_with_optional_template($quizsettings, settings_provider::USE_SEB_TEMPLATE, $templateid);
 724          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 725          $this->assertNotNull($quizsettings->get_config());
 726          $this->assertNotNull($quizsettings->get_config_key());
 727      }
 728  
 729      /**
 730       * Test that quizsettings cache exists after creation.
 731       */
 732      public function test_quizsettings_cache_exists_after_creation() {
 733          $expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 734          $this->assertEquals($expected->to_record(), \cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
 735      }
 736  
 737      /**
 738       * Test that quizsettings cache gets deleted after deletion.
 739       */
 740      public function test_quizsettings_cache_purged_after_deletion() {
 741          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
 742  
 743          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 744          $quizsettings->delete();
 745  
 746          $this->assertFalse(\cache::make('quizaccess_seb', 'quizsettings')->get($this->quiz->id));
 747      }
 748  
 749      /**
 750       * Test that we can get quiz_settings by quiz id.
 751       */
 752      public function test_get_quiz_settings_by_quiz_id() {
 753          $expected = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 754  
 755          $this->assertEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
 756  
 757          // Check that data is getting from cache.
 758          $expected->set('showsebtaskbar', 0);
 759          $this->assertNotEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
 760  
 761          // Now save and check that cached as been updated.
 762          $expected->save();
 763          $this->assertEquals($expected->to_record(), quiz_settings::get_by_quiz_id($this->quiz->id)->to_record());
 764  
 765          // Returns false for non existing quiz.
 766          $this->assertFalse(quiz_settings::get_by_quiz_id(7777777));
 767      }
 768  
 769      /**
 770       * Test that SEB config cache exists after creation of the quiz.
 771       */
 772      public function test_config_cache_exists_after_creation() {
 773          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
 774      }
 775  
 776      /**
 777       * Test that SEB config cache gets deleted after deletion.
 778       */
 779      public function test_config_cache_purged_after_deletion() {
 780          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
 781  
 782          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 783          $quizsettings->delete();
 784  
 785          $this->assertFalse(\cache::make('quizaccess_seb', 'config')->get($this->quiz->id));
 786      }
 787  
 788      /**
 789       * Test that we can get SEB config by quiz id.
 790       */
 791      public function test_get_config_by_quiz_id() {
 792          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 793          $expected = $quizsettings->get_config();
 794  
 795          $this->assertEquals($expected, quiz_settings::get_config_by_quiz_id($this->quiz->id));
 796  
 797          // Check that data is getting from cache.
 798          $quizsettings->set('showsebtaskbar', 0);
 799          $this->assertNotEquals($quizsettings->get_config(), quiz_settings::get_config_by_quiz_id($this->quiz->id));
 800  
 801          // Now save and check that cached as been updated.
 802          $quizsettings->save();
 803          $this->assertEquals($quizsettings->get_config(), quiz_settings::get_config_by_quiz_id($this->quiz->id));
 804  
 805          // Returns null for non existing quiz.
 806          $this->assertNull(quiz_settings::get_config_by_quiz_id(7777777));
 807      }
 808  
 809      /**
 810       * Test that SEB config key cache exists after creation of the quiz.
 811       */
 812      public function test_config_key_cache_exists_after_creation() {
 813          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
 814      }
 815  
 816      /**
 817       * Test that SEB config key cache gets deleted after deletion.
 818       */
 819      public function test_config_key_cache_purged_after_deletion() {
 820          $this->assertNotEmpty(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
 821  
 822          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 823          $quizsettings->delete();
 824  
 825          $this->assertFalse(\cache::make('quizaccess_seb', 'configkey')->get($this->quiz->id));
 826      }
 827  
 828      /**
 829       * Test that we can get SEB config key by quiz id.
 830       */
 831      public function test_get_config_key_by_quiz_id() {
 832          $quizsettings = quiz_settings::get_record(['quizid' => $this->quiz->id]);
 833          $expected = $quizsettings->get_config_key();
 834  
 835          $this->assertEquals($expected, quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
 836  
 837          // Check that data is getting from cache.
 838          $quizsettings->set('showsebtaskbar', 0);
 839          $this->assertNotEquals($quizsettings->get_config_key(), quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
 840  
 841          // Now save and check that cached as been updated.
 842          $quizsettings->save();
 843          $this->assertEquals($quizsettings->get_config_key(), quiz_settings::get_config_key_by_quiz_id($this->quiz->id));
 844  
 845          // Returns null for non existing quiz.
 846          $this->assertNull(quiz_settings::get_config_key_by_quiz_id(7777777));
 847      }
 848  
 849  }