Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

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