Merge branch 'MDL-54907' of https://github.com/timhunt/moodle
[moodle.git] / mod / quiz / tests / attempts_test.php
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/>.
17 /**
18  * Quiz attempt overdue handling tests
19  *
20  * @package    mod_quiz
21  * @category   phpunit
22  * @copyright  2012 Matt Petro
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->dirroot.'/group/lib.php');
31 /**
32  * Unit tests for quiz attempt overdue handling
33  *
34  * @package    mod_quiz
35  * @category   phpunit
36  * @copyright  2012 Matt Petro
37  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38  */
39 class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
41     /**
42      * Test the functions quiz_update_open_attempts(), get_list_of_overdue_attempts() and
43      * update_overdue_attempts().
44      */
45     public function test_bulk_update_functions() {
46         global $DB,$CFG;
48         require_once($CFG->dirroot.'/mod/quiz/cronlib.php');
50         $this->resetAfterTest();
52         $this->setAdminUser();
54         // Setup course, user and groups
56         $course = $this->getDataGenerator()->create_course();
57         $user1 = $this->getDataGenerator()->create_user();
58         $studentrole = $DB->get_record('role', ['shortname' => 'student']);
59         $this->assertNotEmpty($studentrole);
60         $this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
61         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
62         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
63         $group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
64         $this->assertTrue(groups_add_member($group1, $user1));
65         $this->assertTrue(groups_add_member($group2, $user1));
67         $usertimes = [];
69         /** @var mod_quiz_generator $quizgenerator */
70         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
72         // Basic quiz settings
74         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
75         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
76                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
77         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test1A', 'time1000state' => 'finished'];
79         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 1800]);
80         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
81                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
82         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 1800, 'message' => 'Test1B', 'time1000state' => 'inprogress'];
84         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
85         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
86                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
87         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 0, 'message' => 'Test1C', 'time1000state' => 'inprogress'];
89         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
90         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
91                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
92         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 600, 'message' => 'Test1D', 'time1000state' => 'finished'];
94         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 0]);
95         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
96                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
97         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 0, 'message' => 'Test1E', 'time1000state' => 'inprogress'];
99         // Group overrides
101         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
102         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
103                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
104         $DB->insert_record('quiz_overrides',
105                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
106         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 0, 'message' => 'Test2A', 'time1000state' => 'inprogress'];
108         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
109         $DB->insert_record('quiz_overrides',
110                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1100, 'timelimit' => null]);
111         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
112                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
113         $usertimes[$attemptid] = ['timeclose' => 1100, 'timelimit' => 0, 'message' => 'Test2B', 'time1000state' => 'inprogress'];
115         $quiz = $quizgenerator->create_instance(
116                 ['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600, 'overduehandling' => 'autoabandon']);
117         $DB->insert_record('quiz_overrides',
118                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 700]);
119         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
120                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
121         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 700, 'message' => 'Test2C', 'time1000state' => 'abandoned'];
123         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
124         $DB->insert_record('quiz_overrides',
125                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 500]);
126         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
127                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
128         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 500, 'message' => 'Test2D', 'time1000state' => 'finished'];
130         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
131         $DB->insert_record('quiz_overrides',
132                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 0]);
133         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
134                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
135         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 0, 'message' => 'Test2E', 'time1000state' => 'inprogress'];
137         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
138         $DB->insert_record('quiz_overrides',
139                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
140         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
141                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
142         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 500, 'message' => 'Test2F', 'time1000state' => 'finished'];
143         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
144                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
145         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 500, 'message' => 'Test2G', 'time1000state' => 'inprogress'];
147         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
148         $DB->insert_record('quiz_overrides',
149                 ['quiz' => $quiz->id, 'groupid' => $group3->id, 'timeclose' => 1300, 'timelimit' => 500]); // User not in group.
150         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
151                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
152         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test2H', 'time1000state' => 'finished'];
153         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
154                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
155         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test2I', 'time1000state' => 'inprogress'];
157         // Multiple group overrides
159         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
160         $DB->insert_record('quiz_overrides',
161                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 501]);
162         $DB->insert_record('quiz_overrides',
163                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => 500]);
164         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
165                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
166         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3A', 'time1000state' => 'finished'];
167         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
168                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
169         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3B', 'time1000state' => 'inprogress'];
171         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
172         $DB->insert_record('quiz_overrides',
173                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1301, 'timelimit' => 500]);
174         $DB->insert_record('quiz_overrides',
175                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1300, 'timelimit' => 501]);
176         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
177                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
178         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3C', 'time1000state' => 'finished'];
179         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
180                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
181         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3D', 'time1000state' => 'inprogress'];
183         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
184                 'overduehandling' => 'autoabandon']);
185         $DB->insert_record('quiz_overrides',
186                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1301, 'timelimit' => 500]);
187         $DB->insert_record('quiz_overrides',
188                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1300, 'timelimit' => 501]);
189         $DB->insert_record('quiz_overrides',
190                 ['quiz' => $quiz->id, 'groupid' => $group3->id, 'timeclose' => 1500, 'timelimit' => 1000]); // User not in group.
191         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
192                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
193         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3E', 'time1000state' => 'abandoned'];
194         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
195                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
196         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3F', 'time1000state' => 'inprogress'];
198         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
199         $DB->insert_record('quiz_overrides',
200                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
201         $DB->insert_record('quiz_overrides',
202                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => null, 'timelimit' => 501]);
203         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
204                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
205         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 501, 'message' => 'Test3G', 'time1000state' => 'finished'];
206         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
207                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
208         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 501, 'message' => 'Test3H', 'time1000state' => 'inprogress'];
210         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
211         $DB->insert_record('quiz_overrides',
212                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
213         $DB->insert_record('quiz_overrides',
214                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => null]);
215         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
216                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
217         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 500, 'message' => 'Test3I', 'time1000state' => 'finished'];
218         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
219                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
220         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 500, 'message' => 'Test3J', 'time1000state' => 'inprogress'];
222         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
223         $DB->insert_record('quiz_overrides',
224                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
225         $DB->insert_record('quiz_overrides',
226                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => 0]);
227         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
228                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
229         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 0, 'message' => 'Test3K', 'time1000state' => 'inprogress'];
230         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
231                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
232         $usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 0, 'message' => 'Test3L', 'time1000state' => 'inprogress'];
234         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
235         $DB->insert_record('quiz_overrides',
236                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
237         $DB->insert_record('quiz_overrides',
238                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 0, 'timelimit' => 501]);
239         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
240                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
241         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 501, 'message' => 'Test3M', 'time1000state' => 'finished'];
242         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
243                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
244         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 501, 'message' => 'Test3N', 'time1000state' => 'inprogress'];
246         // User overrides
248         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
249         $DB->insert_record('quiz_overrides',
250                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
251         $DB->insert_record('quiz_overrides',
252                 ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => 601]);
253         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
254                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
255         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 601, 'message' => 'Test4A', 'time1000state' => 'finished'];
256         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
257                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
258         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 601, 'message' => 'Test4B', 'time1000state' => 'inprogress'];
260         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
261         $DB->insert_record('quiz_overrides',
262                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
263         $DB->insert_record('quiz_overrides',
264                 ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 0, 'timelimit' => 601]);
265         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
266                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
267         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 601, 'message' => 'Test4C', 'time1000state' => 'finished'];
268         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
269                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
270         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 601, 'message' => 'Test4D', 'time1000state' => 'inprogress'];
272         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
273         $DB->insert_record('quiz_overrides',
274                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
275         $DB->insert_record('quiz_overrides',
276                 ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => 0]);
277         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
278                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
279         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 0, 'message' => 'Test4E', 'time1000state' => 'inprogress'];
280         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
281                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
282         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 0, 'message' => 'Test4F', 'time1000state' => 'inprogress'];
284         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
285                 'overduehandling' => 'autoabandon']);
286         $DB->insert_record('quiz_overrides',
287                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
288         $DB->insert_record('quiz_overrides',
289                 ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => null, 'timelimit' => 601]);
290         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
291                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
292         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 601, 'message' => 'Test4G', 'time1000state' => 'abandoned'];
293         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
294                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
295         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 601, 'message' => 'Test4H', 'time1000state' => 'inprogress'];
297         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
298         $DB->insert_record('quiz_overrides',
299                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 700]);
300         $DB->insert_record('quiz_overrides',
301                 ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => null, 'timelimit' => 601]);
302         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
303                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
304         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 601, 'message' => 'Test4I', 'time1000state' => 'finished'];
305         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
306                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
307         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 601, 'message' => 'Test4J', 'time1000state' => 'inprogress'];
309         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
310         $DB->insert_record('quiz_overrides',
311                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
312         $DB->insert_record('quiz_overrides',
313                 ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => null]);
314         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
315                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
316         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 700, 'message' => 'Test4K', 'time1000state' => 'finished'];
317         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
318                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
319         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 700, 'message' => 'Test4L', 'time1000state' => 'inprogress'];
321         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
322         $DB->insert_record('quiz_overrides',
323                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
324         $DB->insert_record('quiz_overrides',
325                 ['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => null]);
326         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
327                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
328         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 600, 'message' => 'Test4M', 'time1000state' => 'finished'];
329         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
330                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
331         $usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 600, 'message' => 'Test4N', 'time1000state' => 'inprogress'];
333         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
334         $DB->insert_record('quiz_overrides',
335                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
336         $DB->insert_record('quiz_overrides',
337                 ['quiz' => $quiz->id, 'userid' => 0, 'timeclose' => 1201, 'timelimit' => 601]); // Not user.
338         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
339                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
340         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 700, 'message' => 'Test4O', 'time1000state' => 'finished'];
341         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
342                 'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
343         $usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 700, 'message' => 'Test4P', 'time1000state' => 'inprogress'];
345         // Attempt state overdue
347         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
348                 'overduehandling' => 'graceperiod', 'graceperiod' => 250]);
349         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'overdue',
350                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
351         $usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test5A', 'time1000state' => 'overdue'];
353         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600,
354                 'overduehandling' => 'graceperiod', 'graceperiod' => 250]);
355         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'overdue',
356                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
357         $usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 600, 'message' => 'Test5B', 'time1000state' => 'overdue'];
359         // Compute expected end time for each attempt.
360         foreach ($usertimes as $attemptid => $times) {
361             $attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid], '*', MUST_EXIST);
363             if ($times['timeclose'] > 0 && $times['timelimit'] > 0) {
364                 $usertimes[$attemptid]['timedue'] = min($times['timeclose'], $attempt->timestart + $times['timelimit']);
365             } else if ($times['timeclose'] > 0) {
366                 $usertimes[$attemptid]['timedue'] = $times['timeclose'];
367             } else if ($times['timelimit'] > 0) {
368                 $usertimes[$attemptid]['timedue'] = $attempt->timestart + $times['timelimit'];
369             }
370         }
372         //
373         // Test quiz_update_open_attempts().
374         //
376         quiz_update_open_attempts(['courseid' => $course->id]);
377         foreach ($usertimes as $attemptid => $times) {
378             $attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid], '*', MUST_EXIST);
380             if ($attempt->state == 'overdue') {
381                 $graceperiod = $DB->get_field('quiz', 'graceperiod', ['id' => $attempt->quiz]);
382             } else {
383                 $graceperiod = 0;
384             }
385             if (isset($times['timedue'])) {
386                 $this->assertEquals($times['timedue'] + $graceperiod, $attempt->timecheckstate, $times['message']);
387             } else {
388                 $this->assertNull($attempt->timecheckstate, $times['message']);
389             }
390         }
392         //
393         // Test get_list_of_overdue_attempts().
394         //
396         $overduehander = new mod_quiz_overdue_attempt_updater();
398         $attempts = $overduehander->get_list_of_overdue_attempts(100000); // way in the future
399         $count = 0;
400         foreach ($attempts as $attempt) {
401             $this->assertTrue(isset($usertimes[$attempt->id]));
402             $times = $usertimes[$attempt->id];
403             $this->assertEquals($times['timeclose'], $attempt->usertimeclose, $times['message']);
404             $this->assertEquals($times['timelimit'], $attempt->usertimelimit, $times['message']);
405             $count++;
407         }
408         $attempts->close();
409         $this->assertEquals($DB->count_records_select('quiz_attempts', 'timecheckstate IS NOT NULL'), $count);
411         $attempts = $overduehander->get_list_of_overdue_attempts(0); // before all attempts
412         $count = 0;
413         foreach ($attempts as $attempt) {
414             $count++;
415         }
416         $attempts->close();
417         $this->assertEquals(0, $count);
419         //
420         // Test update_overdue_attempts().
421         //
423         [$count, $quizcount] = $overduehander->update_overdue_attempts(1000, 940);
425         $attempts = $DB->get_records('quiz_attempts', null, 'quiz, userid, attempt',
426                 'id, quiz, userid, attempt, state, timestart, timefinish, timecheckstate');
427         foreach ($attempts as $attempt) {
428             $this->assertTrue(isset($usertimes[$attempt->id]));
429             $times = $usertimes[$attempt->id];
430             $this->assertEquals($times['time1000state'], $attempt->state, $times['message']);
431             switch ($times['time1000state']) {
432                 case 'finished':
433                     $this->assertEquals($times['timedue'], $attempt->timefinish, $times['message']);
434                     $this->assertNull($attempt->timecheckstate, $times['message']);
435                     break;
437                 case 'overdue':
438                     $this->assertEquals(0, $attempt->timefinish, $times['message']);
439                     $graceperiod = $DB->get_field('quiz', 'graceperiod', ['id' => $attempt->quiz]);
440                     $this->assertEquals($times['timedue'] + $graceperiod, $attempt->timecheckstate, $times['message']);
441                     break;
443                 case 'abandoned':
444                     $this->assertEquals(0, $attempt->timefinish, $times['message']);
445                     $this->assertNull($attempt->timecheckstate, $times['message']);
446                     break;
447             }
448         }
450         $this->assertEquals(19, $count);
451         $this->assertEquals(19, $quizcount);
452     }
454     /**
455      * Make any old question usage for a quiz.
456      *
457      * The attempts used in test_bulk_update_functions must have some
458      * question usage to store in uniqueid, but they don't have to be
459      * very realistic.
460      *
461      * @param stdClass $quiz
462      * @return int question usage id.
463      */
464     protected function usage_id(stdClass $quiz): int {
465         $quba = question_engine::make_questions_usage_by_activity('mod_quiz',
466                 context_module::instance($quiz->cmid));
467         $quba->set_preferred_behaviour('deferredfeedback');
468         question_engine::save_questions_usage_by_activity($quba);
469         return $quba->get_id();
470     }
472     /**
473      * Test the group event handlers
474      */
475     public function test_group_event_handlers() {
476         global $DB;
478         $this->resetAfterTest();
480         $this->setAdminUser();
482         // Setup course, user and groups
484         $course = $this->getDataGenerator()->create_course();
485         $user1 = $this->getDataGenerator()->create_user();
486         $studentrole = $DB->get_record('role', ['shortname' => 'student']);
487         $this->assertNotEmpty($studentrole);
488         $this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
489         $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
490         $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
491         $this->assertTrue(groups_add_member($group1, $user1));
492         $this->assertTrue(groups_add_member($group2, $user1));
494         /** @var mod_quiz_generator $quizgenerator */
495         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
497         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
499         // add a group1 override
500         $DB->insert_record('quiz_overrides',
501                 ['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
503         // add an attempt
504         $attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
505                 'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
507         // update timecheckstate
508         quiz_update_open_attempts(['quizid' => $quiz->id]);
509         $this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
511         // remove from group
512         $this->assertTrue(groups_remove_member($group1, $user1));
513         $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
515         // add back to group
516         $this->assertTrue(groups_add_member($group1, $user1));
517         $this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
519         // delete group
520         groups_delete_group($group1);
521         $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
522         $this->assertEquals(0, $DB->count_records('quiz_overrides', ['quiz' => $quiz->id]));
524         // add a group2 override
525         $DB->insert_record('quiz_overrides',
526                 ['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1400, 'timelimit' => null]);
527         quiz_update_open_attempts(['quizid' => $quiz->id]);
528         $this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
530         // delete user1 from all groups
531         groups_delete_group_members($course->id, $user1->id);
532         $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
534         // add back to group2
535         $this->assertTrue(groups_add_member($group2, $user1));
536         $this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
538         // delete everyone from all groups
539         groups_delete_group_members($course->id);
540         $this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
541     }
543     /**
544      * Test the functions quiz_create_attempt_handling_errors
545      */
546     public function test_quiz_create_attempt_handling_errors() {
547         $this->resetAfterTest(true);
548         $this->setAdminUser();
550         // Make a quiz.
551         $course = $this->getDataGenerator()->create_course();
552         $user1 = $this->getDataGenerator()->create_user();
553         $student = $this->getDataGenerator()->create_user();
554         $this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
555         /** @var mod_quiz_generator $quizgenerator */
556         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
557         /** @var core_question_generator $questiongenerator */
558         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
559         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'questionsperpage' => 0, 'grade' => 100.0,
560             'sumgrades' => 2]);
561         // Create questions.
562         $cat = $questiongenerator->create_question_category();
563         $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
564         $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
565         // Add them to the quiz.
566         quiz_add_quiz_question($saq->id, $quiz);
567         quiz_add_quiz_question($numq->id, $quiz);
568         $quizobj = quiz::create($quiz->id, $user1->id);
569         $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
570         $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
571         $timenow = time();
572         // Create an attempt.
573         $attempt = quiz_create_attempt($quizobj, 1, null, $timenow, false, $user1->id);
574         quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
575         quiz_attempt_save_started($quizobj, $quba, $attempt);
576         $result = quiz_create_attempt_handling_errors($attempt->id, $quiz->cmid);
577         $this->assertEquals($result->get_attemptid(), $attempt->id);
578         try {
579             $result = quiz_create_attempt_handling_errors($attempt->id, 9999);
580             $this->fail('Exception expected due to invalid course module id.');
581         } catch (moodle_exception $e) {
582             $this->assertEquals('invalidcoursemodule', $e->errorcode);
583         }
584         try {
585             quiz_create_attempt_handling_errors(9999, $result->get_cmid());
586             $this->fail('Exception expected due to quiz content change.');
587         } catch (moodle_exception $e) {
588             $this->assertEquals('attempterrorcontentchange', $e->errorcode);
589         }
590         try {
591             quiz_create_attempt_handling_errors(9999);
592             $this->fail('Exception expected due to invalid quiz attempt id.');
593         } catch (moodle_exception $e) {
594             $this->assertEquals('attempterrorinvalid', $e->errorcode);
595         }
596         // Set up as normal user without permission to view preview.
597         $this->setUser($student->id);
598         try {
599             quiz_create_attempt_handling_errors(9999, $result->get_cmid());
600             $this->fail('Exception expected due to quiz content change for user without permission.');
601         } catch (moodle_exception $e) {
602             $this->assertEquals('attempterrorcontentchangeforuser', $e->errorcode);
603         }
604         try {
605             quiz_create_attempt_handling_errors($attempt->id, 9999);
606             $this->fail('Exception expected due to invalid course module id for user without permission.');
607         } catch (moodle_exception $e) {
608             $this->assertEquals('invalidcoursemodule', $e->errorcode);
609         }
610     }