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 mod_quiz\external; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 require_once (__DIR__ . '/../../../../webservice/tests/helpers.php'); 22 23 use coding_exception; 24 use core_question_generator; 25 use externallib_advanced_testcase; 26 use mod_quiz\quiz_attempt; 27 use mod_quiz\quiz_settings; 28 use required_capability_exception; 29 use stdClass; 30 31 /** 32 * Test for the reopen_attempt and get_reopen_attempt_confirmation services. 33 * 34 * @package mod_quiz 35 * @category external 36 * @copyright 2023 The Open University 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 * @covers \mod_quiz\external\reopen_attempt 39 * @covers \mod_quiz\external\get_reopen_attempt_confirmation 40 */ 41 class reopen_attempt_test extends externallib_advanced_testcase { 42 /** @var stdClass|null if we make a quiz attempt, we store the student object here. */ 43 protected $student; 44 45 public function test_reopen_attempt_service_works() { 46 [$attemptid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(); 47 48 reopen_attempt::execute($attemptid); 49 50 $attemptobj = quiz_attempt::create($attemptid); 51 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attemptobj->get_state()); 52 } 53 54 public function test_reopen_attempt_service_checks_permissions() { 55 [$attemptid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(); 56 57 $unprivilegeduser = $this->getDataGenerator()->create_user(); 58 $this->setUser($unprivilegeduser); 59 60 $this->expectException(required_capability_exception::class); 61 reopen_attempt::execute($attemptid); 62 } 63 64 public function test_reopen_attempt_service_checks_attempt_state() { 65 [$attemptid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(quiz_attempt::IN_PROGRESS); 66 67 $this->expectExceptionMessage("Attempt $attemptid is in the wrong state (In progress) to be reopened."); 68 reopen_attempt::execute($attemptid); 69 } 70 71 public function test_get_reopen_attempt_confirmation_staying_open() { 72 global $DB; 73 [$attemptid, $quizid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(); 74 $DB->set_field('quiz', 'timeclose', 0, ['id' => $quizid]); 75 76 $message = get_reopen_attempt_confirmation::execute($attemptid); 77 78 $this->assertEquals('<p>This will reopen attempt 1 by ' . fullname($this->student) . 79 '.</p><p>The attempt will remain open and can be continued.</p>', 80 $message); 81 } 82 83 public function test_get_reopen_attempt_confirmation_staying_open_until() { 84 global $DB; 85 [$attemptid, $quizid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(); 86 $timeclose = time() + HOURSECS; 87 $DB->set_field('quiz', 'timeclose', $timeclose, ['id' => $quizid]); 88 89 $message = get_reopen_attempt_confirmation::execute($attemptid); 90 91 $this->assertEquals('<p>This will reopen attempt 1 by ' . fullname($this->student) . 92 '.</p><p>The attempt will remain open and can be continued until the quiz closes on ' . 93 userdate($timeclose) . '.</p>', 94 $message); 95 } 96 97 public function test_get_reopen_attempt_confirmation_submitting() { 98 global $DB; 99 [$attemptid, $quizid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(); 100 $timeclose = time() - HOURSECS; 101 $DB->set_field('quiz', 'timeclose', $timeclose, ['id' => $quizid]); 102 103 $message = get_reopen_attempt_confirmation::execute($attemptid); 104 105 $this->assertEquals('<p>This will reopen attempt 1 by ' . fullname($this->student) . 106 '.</p><p>The attempt will be immediately submitted for grading.</p>', 107 $message); 108 } 109 110 public function test_get_reopen_attempt_confirmation_service_checks_permissions() { 111 [$attemptid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(); 112 113 $unprivilegeduser = $this->getDataGenerator()->create_user(); 114 $this->setUser($unprivilegeduser); 115 116 $this->expectException(required_capability_exception::class); 117 get_reopen_attempt_confirmation::execute($attemptid); 118 } 119 120 public function test_get_reopen_attempt_confirmation_service_checks_attempt_state() { 121 [$attemptid] = $this->create_attempt_at_quiz_with_one_shortanswer_question(quiz_attempt::IN_PROGRESS); 122 123 $this->expectExceptionMessage("Attempt $attemptid is in the wrong state (In progress) to be reopened."); 124 get_reopen_attempt_confirmation::execute($attemptid); 125 } 126 127 /** 128 * Create a quiz of one shortanswer question and an attempt in a given state. 129 * 130 * @param string $attemptstate the desired attempt state. quiz_attempt::ABANDONED or ::IN_PROGRESS. 131 * @return array with two elements, the attempt id and the quiz id. 132 */ 133 protected function create_attempt_at_quiz_with_one_shortanswer_question( 134 string $attemptstate = quiz_attempt::ABANDONED 135 ): array { 136 global $SITE; 137 $this->resetAfterTest(); 138 139 // Make a quiz. 140 $timeclose = time() + HOURSECS; 141 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); 142 143 $quiz = $quizgenerator->create_instance([ 144 'course' => $SITE->id, 145 'timeclose' => $timeclose, 146 'overduehandling' => 'autoabandon' 147 ]); 148 149 // Create a question. 150 /** @var core_question_generator $questiongenerator */ 151 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 152 $cat = $questiongenerator->create_question_category(); 153 $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 154 155 // Add them to the quiz. 156 $quizobj = quiz_settings::create($quiz->id); 157 quiz_add_quiz_question($saq->id, $quiz, 0, 1); 158 $quizobj->get_grade_calculator()->recompute_quiz_sumgrades(); 159 160 // Make a user to do the quiz. 161 $this->student = $this->getDataGenerator()->create_user(); 162 $this->setUser($this->student); 163 $quizobj = quiz_settings::create($quiz->id, $this->student->id); 164 165 // Start the attempt. 166 $attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null); 167 $attemptobj = quiz_attempt::create($attempt->id); 168 169 if ($attemptstate === quiz_attempt::ABANDONED) { 170 // Attempt goes overdue (e.g. if cron ran). 171 $attemptobj->process_abandon($timeclose + 2 * get_config('quiz', 'graceperiodmin'), false); 172 } else if ($attemptstate !== quiz_attempt::IN_PROGRESS) { 173 throw new coding_exception('State ' . $attemptstate . ' not currently supported.'); 174 } 175 176 // Set current user to admin before we return. 177 $this->setAdminUser(); 178 179 return [$attemptobj->get_attemptid(), $attemptobj->get_quizid()]; 180 } 181 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body