Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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\task;
18
 
19
use mod_quiz\quiz_attempt;
20
use mod_quiz\quiz_settings;
21
 
22
defined('MOODLE_INTERNAL') || die();
23
 
24
global $CFG;
25
require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php');
26
 
27
/**
28
 * Unit tests for precreate_attempts
29
 *
30
 * @package   mod_quiz
31
 * @copyright 2024 onwards Catalyst IT EU {@link https://catalyst-eu.net}
32
 * @author    Mark Johnson <mark.johnson@catalyst-eu.net>
33
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34
 * @covers \mod_quiz\task\precreate_attempts
35
 */
36
final class precreate_attempts_test extends \advanced_testcase {
37
    use \quiz_question_helper_test_trait;
38
 
39
    /**
40
     * Generate the various possible combinations precreation settings and the corresponding task output.
41
     *
42
     * @return array
43
     */
44
    public static function precreate_settings_provider(): array {
45
        return [
46
            [
47
                'period' => 0,
48
                'output' => 'Pre-creation of quiz attempts is disabled. Nothing to do.',
49
            ],
50
            [
51
                'period' => 1,
52
                'output' => 'Found 0 quizzes to create attempts for.',
53
            ],
54
        ];
55
    }
56
 
57
    /**
58
     * The scheduled task only looks for quizzes to generate if pre-creation is configured.
59
     *
60
     * Only look for quizzes to generate attempts for if precreateperiod is not 0, and precreateattempts is 1 or is unlocked.
61
     *
62
     * @param int $period
63
     * @param string $output
64
     * @dataProvider precreate_settings_provider
65
     */
66
    public function test_execute_disabled(int $period, string $output): void {
67
        $this->resetAfterTest();
68
        set_config('precreateperiod', $period, 'quiz');
69
 
70
        $task = new precreate_attempts();
71
        ob_start();
72
        $task->execute();
73
        $log = ob_get_clean();
74
        $this->assertMatchesRegularExpression("/{$output}/", $log);
75
    }
76
 
77
    /**
78
     * Test precreate attempts task.
79
     *
80
     * Generate quizzes with a variety of timeopen and precreateperiod settings, and ensure those that match the criteria
81
     * for attempt pre-generation are picked up by the scheduled task.
82
     */
83
    public function test_execute(): void {
84
        $this->resetAfterTest();
85
 
86
        // Generate a course.
87
        $course = $this->getDataGenerator()->create_course();
88
        // Generate 3 users.
89
        $student1 = $this->getDataGenerator()->create_user();
90
        $student2 = $this->getDataGenerator()->create_user();
91
        $teacher = $this->getDataGenerator()->create_user();
92
        // Enrol users on the course with the appropriate roles.
93
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
94
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
95
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
96
 
97
        set_config('precreateperiod', 12 * HOURSECS, 'quiz');
98
        set_config('precreateattempts', 1, 'quiz');
99
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
100
        // Generate a quiz with timeopen 1 day in the future.
101
        $quizinfuture = $quizgenerator->create_instance([
102
            'course' => $course->id,
103
            'timeopen' => time() + 86400,
104
            'questionsperpage' => 0,
105
            'grade' => 100.0,
106
            'sumgrades' => 2,
107
        ]);
108
        // Generate a quiz with timeopen 0.
109
        $quiznotimeopen = $quizgenerator->create_instance([
110
            'course' => $course->id,
111
            'precreateperiod' => 43200,
112
            'questionsperpage' => 0,
113
            'grade' => 100.0,
114
            'sumgrades' => 2,
115
        ]);
116
        // Generate a quiz with timeopen in the past.
117
        $quizinpast = $quizgenerator->create_instance([
118
            'course' => $course->id,
119
            'timeopen' => time() - 86400,
120
            'questionsperpage' => 0,
121
            'grade' => 100.0,
122
            'sumgrades' => 2,
123
        ]);
124
        // Generate a quiz with timeopen 11 hours in the future.
125
        $quizwithattempts = $quizgenerator->create_instance([
126
            'course' => $course->id,
127
            'timeopen' => time() + 39600,
128
            'questionsperpage' => 0,
129
            'grade' => 100.0,
130
            'sumgrades' => 2,
131
        ]);
132
        // Generate second quiz with timeopen 11 hours in the future.
133
        $quizinprecreateperiod = $quizgenerator->create_instance([
134
            'course' => $course->id,
135
            'timeopen' => time() + 39600,
136
            'questionsperpage' => 0,
137
            'grade' => 100.0,
138
            'sumgrades' => 2,
139
        ]);
140
        // Generate second quiz with timeopen 11 hours in the future,
141
        // but do not give it any questions.
142
        $quizwithoutquestions = $quizgenerator->create_instance([
143
            'course' => $course->id,
144
            'timeopen' => time() + 39600,
145
            'questionsperpage' => 0,
146
            'grade' => 100.0,
147
            'sumgrades' => 2,
148
        ]);
149
        // Add questions to the quizzes.
150
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
151
        $this->add_two_regular_questions($questiongenerator, $quizinfuture);
152
        $this->add_two_regular_questions($questiongenerator, $quiznotimeopen);
153
        $this->add_two_regular_questions($questiongenerator, $quizinpast);
154
        $this->add_two_regular_questions($questiongenerator, $quizwithattempts);
155
        $this->add_two_regular_questions($questiongenerator, $quizinprecreateperiod);
156
 
157
        // Create attempts for one student on quiz 5.
158
        $quiz5settings = quiz_settings::create($quizwithattempts->id);
159
        $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quiz5settings->get_context());
160
        $quba->set_preferred_behaviour($quiz5settings->get_quiz()->preferredbehaviour);
161
        $attempt = quiz_create_attempt($quiz5settings, 1, false, time(), false, $student1->id);
162
        quiz_start_new_attempt($quiz5settings, $quba, $attempt, 1, time());
163
        quiz_attempt_save_started($quiz5settings, $quba, $attempt);
164
 
165
        $this->assertEmpty(
166
            quiz_get_user_attempts(
167
                [
168
                    $quizinfuture->id,
169
                    $quiznotimeopen->id,
170
                    $quizinpast->id,
171
                    $quizwithoutquestions->id,
172
                ],
173
                $student1->id,
174
                'all'
175
            )
176
        );
177
        $this->assertEmpty(
178
            quiz_get_user_attempts(
179
                [
180
                    $quizinfuture->id,
181
                    $quiznotimeopen->id,
182
                    $quizinpast->id,
183
                    $quizwithattempts->id,
184
                    $quizwithoutquestions->id,
185
                ],
186
                $student2->id,
187
                'all',
188
            ),
189
        );
190
        $this->assertEmpty(
191
            quiz_get_user_attempts(
192
                [
193
                    $quizinfuture->id,
194
                    $quiznotimeopen->id,
195
                    $quizinpast->id,
196
                    $quizwithattempts->id,
197
                    $quizinprecreateperiod->id,
198
                    $quizwithoutquestions->id,
199
                ],
200
                $teacher->id,
201
                'all',
202
            ),
203
        );
204
 
205
        $student1existingattempts = quiz_get_user_attempts($quizwithattempts->id, $student1->id, 'all');
206
        $this->assertCount(1, $student1existingattempts);
207
        $this->assertEquals(reset($student1existingattempts)->state, quiz_attempt::IN_PROGRESS);
208
        $student1precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student1->id, 'all');
209
        $this->assertEmpty($student1precreatedattempts);
210
        $student2precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student2->id, 'all');
211
        $this->assertEmpty($student2precreatedattempts);
212
 
213
        $task = new precreate_attempts();
214
        ob_start();
215
        $task->execute();
216
        $log = ob_get_clean();
217
 
218
        $this->assertMatchesRegularExpression('/Found 1 quizzes to create attempts for/', $log);
219
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizinfuture->name}/", $log);
220
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiznotimeopen->name}/", $log);
221
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizinpast->name}/", $log);
222
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizwithattempts->name}/", $log);
223
        $this->assertMatchesRegularExpression("/Creating attempts for {$quizinprecreateperiod->name}/", $log);
224
        $this->assertMatchesRegularExpression("/Created 2 attempts for {$quizinprecreateperiod->name}/", $log);
225
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizwithoutquestions->name}/", $log);
226
        $this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
227
 
228
        // Students should have no attempts on quizzes that didn't meet criteria for pre-creation.
229
        $this->assertEmpty(
230
            quiz_get_user_attempts(
231
                [
232
                    $quizinfuture->id,
233
                    $quiznotimeopen->id,
234
                    $quizinpast->id,
235
                    $quizwithoutquestions->id,
236
                ],
237
                $student1->id,
238
                'all'
239
            )
240
        );
241
        $this->assertEmpty(
242
            quiz_get_user_attempts(
243
                [
244
                    $quizinfuture->id,
245
                    $quiznotimeopen->id,
246
                    $quizinpast->id,
247
                    $quizwithattempts->id,
248
                    $quizwithoutquestions->id,
249
                ],
250
                $student2->id,
251
                'all',
252
            ),
253
        );
254
        // Teacher should not have any attempts on any quizzes.
255
        $this->assertEmpty(
256
            quiz_get_user_attempts(
257
                [
258
                    $quizinfuture->id,
259
                    $quiznotimeopen->id,
260
                    $quizinpast->id,
261
                    $quizwithattempts->id,
262
                    $quizinprecreateperiod->id,
263
                    $quizwithoutquestions->id,
264
                ],
265
                $teacher->id,
266
                'all',
267
            ),
268
        );
269
 
270
        // Students existing attempts should remain.
271
        $student1existingattempts = quiz_get_user_attempts($quizwithattempts->id, $student1->id, 'all');
272
        $this->assertCount(1, $student1existingattempts);
273
        $this->assertEquals(reset($student1existingattempts)->state, quiz_attempt::IN_PROGRESS);
274
        // They should have NOT_STARTED attempts on quizzes that meet the criteria for pre-creation.
275
        $student1precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student1->id, 'all');
276
        $this->assertCount(1, $student1precreatedattempts);
277
        $this->assertEquals(reset($student1precreatedattempts)->state, quiz_attempt::NOT_STARTED);
278
        $student2precreatedattempts = quiz_get_user_attempts($quizinprecreateperiod->id, $student2->id, 'all');
279
        $this->assertCount(1, $student2precreatedattempts);
280
        $this->assertEquals(reset($student1precreatedattempts)->state, quiz_attempt::NOT_STARTED);
281
    }
282
 
283
    /**
284
     * Processing should stop at the end of a quiz once maxruntime has been reached.
285
     *
286
     * @return void
287
     */
288
    public function test_execute_maxruntime(): void {
289
        $this->resetAfterTest();
290
 
291
        // Generate a course.
292
        $course = $this->getDataGenerator()->create_course();
293
        // Generate 3 users.
294
        $student1 = $this->getDataGenerator()->create_user();
295
        // Enrol users on the course with the appropriate roles.
296
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
297
 
298
        set_config('precreateperiod', 12 * HOURSECS, 'quiz');
299
        set_config('precreateattempts', 1, 'quiz');
300
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
301
        // Generate 3 quizzes within the pre-creation window.
302
        $timenow = time();
303
        $quiz1 = $quizgenerator->create_instance([
304
            'course' => $course->id,
305
            'timeopen' => $timenow + 39600,
306
            'questionsperpage' => 0,
307
            'grade' => 100.0,
308
            'sumgrades' => 2,
309
        ]);
310
        // This quiz opens first, so should be processed first.
311
        $quiz2 = $quizgenerator->create_instance([
312
            'course' => $course->id,
313
            'timeopen' => $timenow + 39599,
314
            'questionsperpage' => 0,
315
            'grade' => 100.0,
316
            'sumgrades' => 2,
317
        ]);
318
        $quiz3 = $quizgenerator->create_instance([
319
            'course' => $course->id,
320
            'timeopen' => $timenow + 39600,
321
            'questionsperpage' => 0,
322
            'grade' => 100.0,
323
            'sumgrades' => 2,
324
        ]);
325
        // Add questions to the quizzes.
326
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
327
        $this->add_two_regular_questions($questiongenerator, $quiz1);
328
        $this->add_two_regular_questions($questiongenerator, $quiz2);
329
        $this->add_two_regular_questions($questiongenerator, $quiz3);
330
 
331
        // Run the task with a maxruntime of 0, so that it should stop after processing the first quiz.
332
        $task = new precreate_attempts(0);
333
        ob_start();
334
        $task->execute();
335
        $log = ob_get_clean();
336
 
337
        // Verify that the task stopped after the quiz opening soonest.
338
        $this->assertMatchesRegularExpression('/Found 3 quizzes to create attempts for/', $log);
339
        $this->assertMatchesRegularExpression("/Creating attempts for {$quiz2->name}/", $log);
340
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiz1->name}/", $log);
341
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiz3->name}/", $log);
342
        $this->assertMatchesRegularExpression("/Created 1 attempts for {$quiz2->name}/", $log);
343
        $this->assertMatchesRegularExpression('/Time limit reached./', $log);
344
        $this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
345
 
346
        // Run the task again with the default maxruntime.
347
        ob_start();
348
        $task = new precreate_attempts();
349
        $task->execute();
350
        $log = ob_get_clean();
351
 
352
        // Verify that it picks up the remaining quiz for processing.
353
        $this->assertMatchesRegularExpression('/Found 2 quizzes to create attempts for/', $log);
354
        $this->assertMatchesRegularExpression("/Creating attempts for {$quiz1->name}/", $log);
355
        $this->assertMatchesRegularExpression("/Creating attempts for {$quiz3->name}/", $log);
356
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiz2->name}/", $log);
357
        $this->assertMatchesRegularExpression("/Created 1 attempts for {$quiz1->name}/", $log);
358
        $this->assertMatchesRegularExpression("/Created 1 attempts for {$quiz3->name}/", $log);
359
        $this->assertDoesNotMatchRegularExpression('/Time limit reached./', $log);
360
        $this->assertMatchesRegularExpression('/Created attempts for 2 quizzes./', $log);
361
    }
362
 
363
    /**
364
     * Pre-creation is opt in based on quiz setting.
365
     *
366
     * @return void
367
     */
368
    public function test_execute_optin(): void {
369
        $this->resetAfterTest();
370
 
371
        // Generate a course.
372
        $course = $this->getDataGenerator()->create_course();
373
        // Generate 3 users.
374
        $student1 = $this->getDataGenerator()->create_user();
375
        $student2 = $this->getDataGenerator()->create_user();
376
        $teacher = $this->getDataGenerator()->create_user();
377
        // Enrol users on the course with the appropriate roles.
378
        $this->getDataGenerator()->enrol_user($student1->id, $course->id, 'student');
379
        $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student');
380
        $this->getDataGenerator()->enrol_user($teacher->id, $course->id, 'editingteacher');
381
 
382
        // Precreation is disabled by default.
383
        set_config('precreateperiod', 12 * HOURSECS, 'quiz');
384
        set_config('precreateattempts', 0, 'quiz');
385
        $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
386
        // Generate a quiz with timeopen 11 hours in the future, and precreateattempts set to 1.
387
        $quizprecreate = $quizgenerator->create_instance([
388
            'course' => $course->id,
389
            'timeopen' => time() + 39600,
390
            'questionsperpage' => 0,
391
            'grade' => 100.0,
392
            'sumgrades' => 2,
393
            'precreateattempts' => 1,
394
        ]);
395
        // Generate a quiz with timeopen 11 hours in the future, and precreateattempts set to 0.
396
        $quiznoprecreate = $quizgenerator->create_instance([
397
            'course' => $course->id,
398
            'timeopen' => time() + 39600,
399
            'questionsperpage' => 0,
400
            'grade' => 100.0,
401
            'sumgrades' => 2,
402
            'precreateattempts' => 0,
403
        ]);
404
        // Generate a quiz with timeopen 11 hours in the future, and precreateattempts set to null.
405
        $quizprecreatenull = $quizgenerator->create_instance([
406
            'course' => $course->id,
407
            'timeopen' => time() + 39600,
408
            'questionsperpage' => 0,
409
            'grade' => 100.0,
410
            'sumgrades' => 2,
411
        ]);
412
        $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
413
        $this->add_two_regular_questions($questiongenerator, $quizprecreate);
414
        $this->add_two_regular_questions($questiongenerator, $quiznoprecreate);
415
        $this->add_two_regular_questions($questiongenerator, $quizprecreatenull);
416
 
417
        // Run the task.
418
        ob_start();
419
        $task = new precreate_attempts();
420
        $task->execute();
421
        $log = ob_get_clean();
422
 
423
        // Attempts were now created for the opted-in quiz.
424
        $this->assertMatchesRegularExpression('/Found 1 quizzes to create attempts for/', $log);
425
        $this->assertMatchesRegularExpression("/Creating attempts for {$quizprecreate->name}/", $log);
426
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiznoprecreate->name}/", $log);
427
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizprecreatenull->name}/", $log);
428
        $this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
429
 
430
        // Now enabled by default.
431
        set_config('precreateattempts', 1, 'quiz');
432
 
433
        // Run the task again.
434
        ob_start();
435
        $task = new precreate_attempts();
436
        $task->execute();
437
        $log = ob_get_clean();
438
 
439
        // The quiz with null now has attempts generated.
440
        $this->assertMatchesRegularExpression('/Found 1 quizzes to create attempts for/', $log);
441
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quizprecreate->name}/", $log);
442
        $this->assertDoesNotMatchRegularExpression("/Creating attempts for {$quiznoprecreate->name}/", $log);
443
        $this->assertMatchesRegularExpression("/Creating attempts for {$quizprecreatenull->name}/", $log);
444
        $this->assertMatchesRegularExpression('/Created attempts for 1 quizzes./', $log);
445
    }
446
}