MDL-41922 mod_quiz: Don't report quiz due when an attempt was finished
[moodle.git] / mod / quiz / tests / lib_test.php
blob22353ff0ac33a8ff15a371fed35b75a3a5cdae01
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.
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 * Unit tests for (some of) mod/quiz/locallib.php.
20 * @package mod_quiz
21 * @category test
22 * @copyright 2008 The Open University
23 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
27 defined('MOODLE_INTERNAL') || die();
29 global $CFG;
30 require_once($CFG->dirroot . '/mod/quiz/lib.php');
32 /**
33 * @copyright 2008 The Open University
34 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
36 class mod_quiz_lib_testcase extends advanced_testcase {
37 public function test_quiz_has_grades() {
38 $quiz = new stdClass();
39 $quiz->grade = '100.0000';
40 $quiz->sumgrades = '100.0000';
41 $this->assertTrue(quiz_has_grades($quiz));
42 $quiz->sumgrades = '0.0000';
43 $this->assertFalse(quiz_has_grades($quiz));
44 $quiz->grade = '0.0000';
45 $this->assertFalse(quiz_has_grades($quiz));
46 $quiz->sumgrades = '100.0000';
47 $this->assertFalse(quiz_has_grades($quiz));
50 public function test_quiz_format_grade() {
51 $quiz = new stdClass();
52 $quiz->decimalpoints = 2;
53 $this->assertEquals(quiz_format_grade($quiz, 0.12345678), format_float(0.12, 2));
54 $this->assertEquals(quiz_format_grade($quiz, 0), format_float(0, 2));
55 $this->assertEquals(quiz_format_grade($quiz, 1.000000000000), format_float(1, 2));
56 $quiz->decimalpoints = 0;
57 $this->assertEquals(quiz_format_grade($quiz, 0.12345678), '0');
60 public function test_quiz_get_grade_format() {
61 $quiz = new stdClass();
62 $quiz->decimalpoints = 2;
63 $this->assertEquals(quiz_get_grade_format($quiz), 2);
64 $this->assertEquals($quiz->questiondecimalpoints, -1);
65 $quiz->questiondecimalpoints = 2;
66 $this->assertEquals(quiz_get_grade_format($quiz), 2);
67 $quiz->decimalpoints = 3;
68 $quiz->questiondecimalpoints = -1;
69 $this->assertEquals(quiz_get_grade_format($quiz), 3);
70 $quiz->questiondecimalpoints = 4;
71 $this->assertEquals(quiz_get_grade_format($quiz), 4);
74 public function test_quiz_format_question_grade() {
75 $quiz = new stdClass();
76 $quiz->decimalpoints = 2;
77 $quiz->questiondecimalpoints = 2;
78 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.12, 2));
79 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 2));
80 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 2));
81 $quiz->decimalpoints = 3;
82 $quiz->questiondecimalpoints = -1;
83 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.123, 3));
84 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 3));
85 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 3));
86 $quiz->questiondecimalpoints = 4;
87 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.1235, 4));
88 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 4));
89 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 4));
92 /**
93 * Test deleting a quiz instance.
95 public function test_quiz_delete_instance() {
96 global $SITE, $DB;
97 $this->resetAfterTest(true);
98 $this->setAdminUser();
100 // Setup a quiz with 1 standard and 1 random question.
101 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
102 $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
104 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
105 $cat = $questiongenerator->create_question_category();
106 $standardq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
108 quiz_add_quiz_question($standardq->id, $quiz);
109 quiz_add_random_questions($quiz, 0, $cat->id, 1, false);
111 // Get the random question.
112 $randomq = $DB->get_record('question', array('qtype' => 'random'));
114 quiz_delete_instance($quiz->id);
116 // Check that the random question was deleted.
117 $count = $DB->count_records('question', array('id' => $randomq->id));
118 $this->assertEquals(0, $count);
119 // Check that the standard question was not deleted.
120 $count = $DB->count_records('question', array('id' => $standardq->id));
121 $this->assertEquals(1, $count);
123 // Check that all the slots were removed.
124 $count = $DB->count_records('quiz_slots', array('quizid' => $quiz->id));
125 $this->assertEquals(0, $count);
127 // Check that the quiz was removed.
128 $count = $DB->count_records('quiz', array('id' => $quiz->id));
129 $this->assertEquals(0, $count);
133 * Test checking the completion state of a quiz.
135 public function test_quiz_get_completion_state() {
136 global $CFG, $DB;
137 $this->resetAfterTest(true);
139 // Enable completion before creating modules, otherwise the completion data is not written in DB.
140 $CFG->enablecompletion = true;
142 // Create a course and student.
143 $course = $this->getDataGenerator()->create_course(array('enablecompletion' => true));
144 $passstudent = $this->getDataGenerator()->create_user();
145 $failstudent = $this->getDataGenerator()->create_user();
146 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
147 $this->assertNotEmpty($studentrole);
149 // Enrol students.
150 $this->assertTrue($this->getDataGenerator()->enrol_user($passstudent->id, $course->id, $studentrole->id));
151 $this->assertTrue($this->getDataGenerator()->enrol_user($failstudent->id, $course->id, $studentrole->id));
153 // Make a scale and an outcome.
154 $scale = $this->getDataGenerator()->create_scale();
155 $data = array('courseid' => $course->id,
156 'fullname' => 'Team work',
157 'shortname' => 'Team work',
158 'scaleid' => $scale->id);
159 $outcome = $this->getDataGenerator()->create_grade_outcome($data);
161 // Make a quiz with the outcome on.
162 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
163 $data = array('course' => $course->id,
164 'outcome_'.$outcome->id => 1,
165 'grade' => 100.0,
166 'questionsperpage' => 0,
167 'sumgrades' => 1,
168 'completion' => COMPLETION_TRACKING_AUTOMATIC,
169 'completionpass' => 1);
170 $quiz = $quizgenerator->create_instance($data);
171 $cm = get_coursemodule_from_id('quiz', $quiz->cmid);
173 // Create a couple of questions.
174 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
176 $cat = $questiongenerator->create_question_category();
177 $question = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
178 quiz_add_quiz_question($question->id, $quiz);
180 $quizobj = quiz::create($quiz->id, $passstudent->id);
182 // Set grade to pass.
183 $item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod',
184 'itemmodule' => 'quiz', 'iteminstance' => $quiz->id, 'outcomeid' => null));
185 $item->gradepass = 80;
186 $item->update();
188 // Start the passing attempt.
189 $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
190 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
192 $timenow = time();
193 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $passstudent->id);
194 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
195 quiz_attempt_save_started($quizobj, $quba, $attempt);
197 // Process some responses from the student.
198 $attemptobj = quiz_attempt::create($attempt->id);
199 $tosubmit = array(1 => array('answer' => '3.14'));
200 $attemptobj->process_submitted_actions($timenow, false, $tosubmit);
202 // Finish the attempt.
203 $attemptobj = quiz_attempt::create($attempt->id);
204 $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
205 $attemptobj->process_finish($timenow, false);
207 // Start the failing attempt.
208 $quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
209 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
211 $timenow = time();
212 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $failstudent->id);
213 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
214 quiz_attempt_save_started($quizobj, $quba, $attempt);
216 // Process some responses from the student.
217 $attemptobj = quiz_attempt::create($attempt->id);
218 $tosubmit = array(1 => array('answer' => '0'));
219 $attemptobj->process_submitted_actions($timenow, false, $tosubmit);
221 // Finish the attempt.
222 $attemptobj = quiz_attempt::create($attempt->id);
223 $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
224 $attemptobj->process_finish($timenow, false);
226 // Check the results.
227 $this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
228 $this->assertFalse(quiz_get_completion_state($course, $cm, $failstudent->id, 'return'));
231 public function test_quiz_get_user_attempts() {
232 global $DB;
233 $this->resetAfterTest();
235 $dg = $this->getDataGenerator();
236 $quizgen = $dg->get_plugin_generator('mod_quiz');
237 $course = $dg->create_course();
238 $u1 = $dg->create_user();
239 $u2 = $dg->create_user();
240 $u3 = $dg->create_user();
241 $u4 = $dg->create_user();
242 $role = $DB->get_record('role', ['shortname' => 'student']);
244 $dg->enrol_user($u1->id, $course->id, $role->id);
245 $dg->enrol_user($u2->id, $course->id, $role->id);
246 $dg->enrol_user($u3->id, $course->id, $role->id);
247 $dg->enrol_user($u4->id, $course->id, $role->id);
249 $quiz1 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]);
250 $quiz2 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]);
252 // Questions.
253 $questgen = $dg->get_plugin_generator('core_question');
254 $quizcat = $questgen->create_question_category();
255 $question = $questgen->create_question('numerical', null, ['category' => $quizcat->id]);
256 quiz_add_quiz_question($question->id, $quiz1);
257 quiz_add_quiz_question($question->id, $quiz2);
259 $quizobj1a = quiz::create($quiz1->id, $u1->id);
260 $quizobj1b = quiz::create($quiz1->id, $u2->id);
261 $quizobj1c = quiz::create($quiz1->id, $u3->id);
262 $quizobj1d = quiz::create($quiz1->id, $u4->id);
263 $quizobj2a = quiz::create($quiz2->id, $u1->id);
265 // Set attempts.
266 $quba1a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1a->get_context());
267 $quba1a->set_preferred_behaviour($quizobj1a->get_quiz()->preferredbehaviour);
268 $quba1b = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1b->get_context());
269 $quba1b->set_preferred_behaviour($quizobj1b->get_quiz()->preferredbehaviour);
270 $quba1c = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1c->get_context());
271 $quba1c->set_preferred_behaviour($quizobj1c->get_quiz()->preferredbehaviour);
272 $quba1d = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1d->get_context());
273 $quba1d->set_preferred_behaviour($quizobj1d->get_quiz()->preferredbehaviour);
274 $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
275 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
277 $timenow = time();
279 // User 1 passes quiz 1.
280 $attempt = quiz_create_attempt($quizobj1a, 1, false, $timenow, false, $u1->id);
281 quiz_start_new_attempt($quizobj1a, $quba1a, $attempt, 1, $timenow);
282 quiz_attempt_save_started($quizobj1a, $quba1a, $attempt);
283 $attemptobj = quiz_attempt::create($attempt->id);
284 $attemptobj->process_submitted_actions($timenow, false, [1 => ['answer' => '3.14']]);
285 $attemptobj->process_finish($timenow, false);
287 // User 2 goes overdue in quiz 1.
288 $attempt = quiz_create_attempt($quizobj1b, 1, false, $timenow, false, $u2->id);
289 quiz_start_new_attempt($quizobj1b, $quba1b, $attempt, 1, $timenow);
290 quiz_attempt_save_started($quizobj1b, $quba1b, $attempt);
291 $attemptobj = quiz_attempt::create($attempt->id);
292 $attemptobj->process_going_overdue($timenow, true);
294 // User 3 does not finish quiz 1.
295 $attempt = quiz_create_attempt($quizobj1c, 1, false, $timenow, false, $u3->id);
296 quiz_start_new_attempt($quizobj1c, $quba1c, $attempt, 1, $timenow);
297 quiz_attempt_save_started($quizobj1c, $quba1c, $attempt);
299 // User 4 abandons the quiz 1.
300 $attempt = quiz_create_attempt($quizobj1d, 1, false, $timenow, false, $u4->id);
301 quiz_start_new_attempt($quizobj1d, $quba1d, $attempt, 1, $timenow);
302 quiz_attempt_save_started($quizobj1d, $quba1d, $attempt);
303 $attemptobj = quiz_attempt::create($attempt->id);
304 $attemptobj->process_abandon($timenow, true);
306 // User 1 attempts the quiz three times (abandon, finish, in progress).
307 $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
308 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
310 $attempt = quiz_create_attempt($quizobj2a, 1, false, $timenow, false, $u1->id);
311 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 1, $timenow);
312 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt);
313 $attemptobj = quiz_attempt::create($attempt->id);
314 $attemptobj->process_abandon($timenow, true);
316 $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
317 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
319 $attempt = quiz_create_attempt($quizobj2a, 2, false, $timenow, false, $u1->id);
320 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 2, $timenow);
321 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt);
322 $attemptobj = quiz_attempt::create($attempt->id);
323 $attemptobj->process_finish($timenow, false);
325 $quba2a = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context());
326 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour);
328 $attempt = quiz_create_attempt($quizobj2a, 3, false, $timenow, false, $u1->id);
329 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 3, $timenow);
330 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt);
332 // Check for user 1.
333 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'all');
334 $this->assertCount(1, $attempts);
335 $attempt = array_shift($attempts);
336 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
337 $this->assertEquals($u1->id, $attempt->userid);
338 $this->assertEquals($quiz1->id, $attempt->quiz);
340 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'finished');
341 $this->assertCount(1, $attempts);
342 $attempt = array_shift($attempts);
343 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
344 $this->assertEquals($u1->id, $attempt->userid);
345 $this->assertEquals($quiz1->id, $attempt->quiz);
347 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'unfinished');
348 $this->assertCount(0, $attempts);
350 // Check for user 2.
351 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'all');
352 $this->assertCount(1, $attempts);
353 $attempt = array_shift($attempts);
354 $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state);
355 $this->assertEquals($u2->id, $attempt->userid);
356 $this->assertEquals($quiz1->id, $attempt->quiz);
358 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'finished');
359 $this->assertCount(0, $attempts);
361 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'unfinished');
362 $this->assertCount(1, $attempts);
363 $attempt = array_shift($attempts);
364 $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state);
365 $this->assertEquals($u2->id, $attempt->userid);
366 $this->assertEquals($quiz1->id, $attempt->quiz);
368 // Check for user 3.
369 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'all');
370 $this->assertCount(1, $attempts);
371 $attempt = array_shift($attempts);
372 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
373 $this->assertEquals($u3->id, $attempt->userid);
374 $this->assertEquals($quiz1->id, $attempt->quiz);
376 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'finished');
377 $this->assertCount(0, $attempts);
379 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'unfinished');
380 $this->assertCount(1, $attempts);
381 $attempt = array_shift($attempts);
382 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
383 $this->assertEquals($u3->id, $attempt->userid);
384 $this->assertEquals($quiz1->id, $attempt->quiz);
386 // Check for user 4.
387 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'all');
388 $this->assertCount(1, $attempts);
389 $attempt = array_shift($attempts);
390 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
391 $this->assertEquals($u4->id, $attempt->userid);
392 $this->assertEquals($quiz1->id, $attempt->quiz);
394 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'finished');
395 $this->assertCount(1, $attempts);
396 $attempt = array_shift($attempts);
397 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
398 $this->assertEquals($u4->id, $attempt->userid);
399 $this->assertEquals($quiz1->id, $attempt->quiz);
401 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'unfinished');
402 $this->assertCount(0, $attempts);
404 // Multiple attempts for user 1 in quiz 2.
405 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'all');
406 $this->assertCount(3, $attempts);
407 $attempt = array_shift($attempts);
408 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
409 $this->assertEquals($u1->id, $attempt->userid);
410 $this->assertEquals($quiz2->id, $attempt->quiz);
411 $attempt = array_shift($attempts);
412 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
413 $this->assertEquals($u1->id, $attempt->userid);
414 $this->assertEquals($quiz2->id, $attempt->quiz);
415 $attempt = array_shift($attempts);
416 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
417 $this->assertEquals($u1->id, $attempt->userid);
418 $this->assertEquals($quiz2->id, $attempt->quiz);
420 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'finished');
421 $this->assertCount(2, $attempts);
422 $attempt = array_shift($attempts);
423 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
424 $attempt = array_shift($attempts);
425 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
427 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'unfinished');
428 $this->assertCount(1, $attempts);
429 $attempt = array_shift($attempts);
431 // Multiple quiz attempts fetched at once.
432 $attempts = quiz_get_user_attempts([$quiz1->id, $quiz2->id], $u1->id, 'all');
433 $this->assertCount(4, $attempts);
434 $attempt = array_shift($attempts);
435 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
436 $this->assertEquals($u1->id, $attempt->userid);
437 $this->assertEquals($quiz1->id, $attempt->quiz);
438 $attempt = array_shift($attempts);
439 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state);
440 $this->assertEquals($u1->id, $attempt->userid);
441 $this->assertEquals($quiz2->id, $attempt->quiz);
442 $attempt = array_shift($attempts);
443 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state);
444 $this->assertEquals($u1->id, $attempt->userid);
445 $this->assertEquals($quiz2->id, $attempt->quiz);
446 $attempt = array_shift($attempts);
447 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state);
448 $this->assertEquals($u1->id, $attempt->userid);
449 $this->assertEquals($quiz2->id, $attempt->quiz);