Merge branch 'MDL-70422' of https://github.com/paulholden/moodle
[moodle.git] / mod / quiz / tests / attempt_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  * Tests for the quiz_attempt class.
19  *
20  * @package   mod_quiz
21  * @category  test
22  * @copyright 2014 Tim Hunt
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 . '/mod/quiz/locallib.php');
31 /**
32  * Tests for the quiz_attempt class.
33  *
34  * @copyright 2014 Tim Hunt
35  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36  */
37 class mod_quiz_attempt_testcase extends advanced_testcase {
39     /**
40      * Create quiz and attempt data with layout.
41      *
42      * @param string $layout layout to set. Like quiz attempt.layout. E.g. '1,2,0,3,4,0,'.
43      * @return quiz_attempt the new quiz_attempt object
44      */
45     protected function create_quiz_and_attempt_with_layout($layout) {
46         $this->resetAfterTest(true);
48         // Make a user to do the quiz.
49         $user = $this->getDataGenerator()->create_user();
50         $course = $this->getDataGenerator()->create_course();
51         // Make a quiz.
52         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
53         $quiz = $quizgenerator->create_instance(['course' => $course->id,
54             'grade' => 100.0, 'sumgrades' => 2, 'layout' => $layout]);
56         $quizobj = quiz::create($quiz->id, $user->id);
59         $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
60         $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
62         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
63         $cat = $questiongenerator->create_question_category();
65         $page = 1;
66         foreach (explode(',', $layout) as $slot) {
67             if ($slot == 0) {
68                 $page += 1;
69                 continue;
70             }
72             $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
73             quiz_add_quiz_question($question->id, $quiz, $page);
74         }
76         $timenow = time();
77         $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user->id);
78         quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
79         quiz_attempt_save_started($quizobj, $quba, $attempt);
81         return quiz_attempt::create($attempt->id);
82     }
84     /**
85      * Test the functions quiz_update_open_attempts() and get_list_of_overdue_attempts()
86      */
87     public function test_attempt_url() {
88         $attempt = $this->create_quiz_and_attempt_with_layout('1,2,0,3,4,0,5,6,0');
90         $attemptid = $attempt->get_attempt()->id;
91         $cmid = $attempt->get_cmid();
92         $url = '/mod/quiz/attempt.php';
93         $params = ['attempt' => $attemptid, 'cmid' => $cmid, 'page' => 2];
95         $this->assertEquals(new moodle_url($url, $params), $attempt->attempt_url(null, 2));
97         $params['page'] = 1;
98         $this->assertEquals(new moodle_url($url, $params), $attempt->attempt_url(3));
100         $questionattempt = $attempt->get_question_attempt(4);
101         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
102         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->attempt_url(4));
104         $this->assertEquals(new moodle_url('#'), $attempt->attempt_url(null, 2, 2));
105         $this->assertEquals(new moodle_url('#'), $attempt->attempt_url(3, -1, 1));
107         $questionattempt = $attempt->get_question_attempt(4);
108         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
109         $this->assertEquals(new moodle_url(null, null, $expecteanchor, null), $attempt->attempt_url(4, -1, 1));
111         // Summary page.
112         $url = '/mod/quiz/summary.php';
113         unset($params['page']);
114         $this->assertEquals(new moodle_url($url, $params), $attempt->summary_url());
116         // Review page.
117         $url = '/mod/quiz/review.php';
118         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url());
120         $params['page'] = 1;
121         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(3, -1, false));
122         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->review_url(4, -1, false));
124         unset($params['page']);
125         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, true));
126         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(1, -1, true));
128         $params['page'] = 2;
129         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, false));
130         unset($params['page']);
132         $params['showall'] = 0;
133         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 0, false));
134         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(1, -1, false));
136         $params['page'] = 1;
137         unset($params['showall']);
138         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(3, -1, false));
140         $params['page'] = 2;
141         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2));
142         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, -1, null, 0));
144         $questionattempt = $attempt->get_question_attempt(3);
145         $expecteanchor = '#' . $questionattempt->get_outer_question_div_unique_id();
146         $this->assertEquals(new moodle_url($expecteanchor), $attempt->review_url(3, -1, null, 0));
148         $questionattempt = $attempt->get_question_attempt(4);
149         $expecteanchor = '#' . $questionattempt->get_outer_question_div_unique_id();
150         $this->assertEquals(new moodle_url($expecteanchor), $attempt->review_url(4, -1, null, 0));
151         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 2, true, 0));
152         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, true, 0));
153         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, false, 0));
154         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 0, false, 0));
155         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, false, 0));
157         $params['page'] = 1;
158         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(3, -1, false, 0));
160         // Setup another attempt.
161         $attempt = $this->create_quiz_and_attempt_with_layout(
162             '1,2,3,4,5,6,7,8,9,10,0,11,12,13,14,15,16,17,18,19,20,0,' .
163             '21,22,23,24,25,26,27,28,29,30,0,31,32,33,34,35,36,37,38,39,40,0,' .
164             '41,42,43,44,45,46,47,48,49,50,0,51,52,53,54,55,56,57,58,59,60,0');
166         $attemptid = $attempt->get_attempt()->id;
167         $cmid = $attempt->get_cmid();
168         $params = ['attempt' => $attemptid, 'cmid' => $cmid];
169         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url());
171         $params['page'] = 2;
172         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2));
174         $params['page'] = 1;
175         unset($params['showall']);
176         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(11, -1, false));
178         $questionattempt = $attempt->get_question_attempt(12);
179         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
180         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->review_url(12, -1, false));
182         $params['showall'] = 1;
183         unset($params['page']);
184         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, true));
186         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(1, -1, true));
187         $params['page'] = 2;
188         unset($params['showall']);
189         $this->assertEquals(new moodle_url($url, $params),  $attempt->review_url(null, 2, false));
190         unset($params['page']);
191         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 0, false));
192         $params['page'] = 1;
193         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(11, -1, false));
194         $this->assertEquals(new moodle_url($url, $params, $expecteanchor), $attempt->review_url(12, -1, false));
195         $params['page'] = 2;
196         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2));
197         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, -1, null, 0));
199         $questionattempt = $attempt->get_question_attempt(3);
200         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
201         $this->assertEquals(new moodle_url(null, null, $expecteanchor), $attempt->review_url(3, -1, null, 0));
203         $questionattempt = $attempt->get_question_attempt(4);
204         $expecteanchor = $questionattempt->get_outer_question_div_unique_id();
205         $this->assertEquals(new moodle_url(null, null, $expecteanchor), $attempt->review_url(4, -1, null, 0));
207         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 2, true, 0));
208         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, true, 0));
210         $params['page'] = 2;
211         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(null, 2, false, 0));
212         $this->assertEquals(new moodle_url('#'), $attempt->review_url(null, 0, false, 0));
213         $this->assertEquals(new moodle_url('#'), $attempt->review_url(1, -1, false, 0));
215         $params['page'] = 1;
216         $this->assertEquals(new moodle_url($url, $params), $attempt->review_url(11, -1, false, 0));
217     }
219     /**
220      * Tests attempt page titles when all questions are on a single page.
221      */
222     public function test_attempt_titles_single() {
223         $attempt = $this->create_quiz_and_attempt_with_layout('1,2,0');
225         // Attempt page.
226         $this->assertEquals('Quiz 1', $attempt->attempt_page_title(0));
228         // Summary page.
229         $this->assertEquals('Quiz 1: Attempt summary', $attempt->summary_page_title());
231         // Review page.
232         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(0));
233     }
235     /**
236      * Tests attempt page titles when questions are on multiple pages, but are reviewed on a single page.
237      */
238     public function test_attempt_titles_multiple_single() {
239         $attempt = $this->create_quiz_and_attempt_with_layout('1,2,0,3,4,0,5,6,0');
241         // Attempt page.
242         $this->assertEquals('Quiz 1 (page 1 of 3)', $attempt->attempt_page_title(0));
243         $this->assertEquals('Quiz 1 (page 2 of 3)', $attempt->attempt_page_title(1));
244         $this->assertEquals('Quiz 1 (page 3 of 3)', $attempt->attempt_page_title(2));
246         // Summary page.
247         $this->assertEquals('Quiz 1: Attempt summary', $attempt->summary_page_title());
249         // Review page.
250         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(0, true));
251     }
253     /**
254      * Tests attempt page titles when questions are on multiple pages, and they are reviewed on multiple pages as well.
255      */
256     public function test_attempt_titles_multiple_multiple() {
257         $attempt = $this->create_quiz_and_attempt_with_layout(
258                 '1,2,3,4,5,6,7,8,9,10,0,11,12,13,14,15,16,17,18,19,20,0,' .
259                 '21,22,23,24,25,26,27,28,29,30,0,31,32,33,34,35,36,37,38,39,40,0,' .
260                 '41,42,43,44,45,46,47,48,49,50,0,51,52,53,54,55,56,57,58,59,60,0');
262         // Attempt page.
263         $this->assertEquals('Quiz 1 (page 1 of 6)', $attempt->attempt_page_title(0));
264         $this->assertEquals('Quiz 1 (page 2 of 6)', $attempt->attempt_page_title(1));
265         $this->assertEquals('Quiz 1 (page 6 of 6)', $attempt->attempt_page_title(5));
267         // Summary page.
268         $this->assertEquals('Quiz 1: Attempt summary', $attempt->summary_page_title());
270         // Review page.
271         $this->assertEquals('Quiz 1: Attempt review (page 1 of 6)', $attempt->review_page_title(0));
272         $this->assertEquals('Quiz 1: Attempt review (page 2 of 6)', $attempt->review_page_title(1));
273         $this->assertEquals('Quiz 1: Attempt review (page 6 of 6)', $attempt->review_page_title(5));
275         // When all questions are shown.
276         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(0, true));
277         $this->assertEquals('Quiz 1: Attempt review', $attempt->review_page_title(1, true));
278     }
280     public function test_is_participant() {
281         global $USER;
282         $this->resetAfterTest();
283         $this->setAdminUser();
284         $course = $this->getDataGenerator()->create_course();
285         $student = $this->getDataGenerator()->create_and_enrol($course, 'student');
286         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student', [], 'manual', 0, 0, ENROL_USER_SUSPENDED);
287         $quiz = $this->getDataGenerator()->create_module('quiz', array('course' => $course->id));
288         $quizobj = quiz::create($quiz->id);
290         // Login as student.
291         $this->setUser($student);
292         // Convert to a lesson object.
293         $this->assertEquals(true, $quizobj->is_participant($student->id),
294             'Student is enrolled, active and can participate');
296         // Login as student2.
297         $this->setUser($student2);
298         $this->assertEquals(false, $quizobj->is_participant($student2->id),
299             'Student is enrolled, suspended and can NOT participate');
301         // Login as an admin.
302         $this->setAdminUser();
303         $this->assertEquals(false, $quizobj->is_participant($USER->id),
304             'Admin is not enrolled and can NOT participate');
306         $this->getDataGenerator()->enrol_user(2, $course->id);
307         $this->assertEquals(true, $quizobj->is_participant($USER->id),
308             'Admin is enrolled and can participate');
310         $this->getDataGenerator()->enrol_user(2, $course->id, [], 'manual', 0, 0, ENROL_USER_SUSPENDED);
311         $this->assertEquals(true, $quizobj->is_participant($USER->id),
312             'Admin is enrolled, suspended and can participate');
313     }
315     /**
316      * Test quiz_prepare_and_start_new_attempt function
317      */
318     public function test_quiz_prepare_and_start_new_attempt() {
319         global $USER;
320         $this->resetAfterTest();
322         // Create course.
323         $course = $this->getDataGenerator()->create_course();
324         // Create students.
325         $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
326         $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
327         // Create quiz.
328         $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
329         $quiz = $quizgenerator->create_instance(['course' => $course->id, 'grade' => 100.0, 'sumgrades' => 2, 'layout' => '1,0']);
330         // Create question and add it to quiz.
331         $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
332         $cat = $questiongenerator->create_question_category();
333         $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
334         quiz_add_quiz_question($question->id, $quiz, 1);
336         $quizobj = quiz::create($quiz->id);
338         // Login as student1.
339         $this->setUser($student1);
340         // Create attempt for student1.
341         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null, false, [], []);
342         $this->assertEquals($student1->id, $attempt->userid);
343         $this->assertEquals(0, $attempt->preview);
345         // Login as student2.
346         $this->setUser($student2);
347         // Create attempt for student2.
348         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 1, null, false, [], []);
349         $this->assertEquals($student2->id, $attempt->userid);
350         $this->assertEquals(0, $attempt->preview);
352         // Login as admin.
353         $this->setAdminUser();
354         // Create attempt for student1.
355         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 2, null, false, [], [], $student1->id);
356         $this->assertEquals($student1->id, $attempt->userid);
357         $this->assertEquals(0, $attempt->preview);
358         $student1attempt = $attempt; // Save for extra verification below.
359         // Create attempt for student2.
360         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 2, null, false, [], [], $student2->id);
361         $this->assertEquals($student2->id, $attempt->userid);
362         $this->assertEquals(0, $attempt->preview);
363         // Create attempt for user id that the same with current $USER->id.
364         $attempt = quiz_prepare_and_start_new_attempt($quizobj, 2, null, false, [], [], $USER->id);
365         $this->assertEquals($USER->id, $attempt->userid);
366         $this->assertEquals(1, $attempt->preview);
368         // Check that the userid stored in the first step is the user the attempt is for,
369         // not the user who triggered the creation.
370         $quba = question_engine::load_questions_usage_by_activity($student1attempt->uniqueid);
371         $step = $quba->get_question_attempt(1)->get_step(0);
372         $this->assertEquals($student1->id, $step->get_user_id());
373     }