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;
18
 
19
/**
20
 * Test class for the quiz notification helper.
21
 *
22
 * @package    mod_quiz
23
 * @category   test
24
 * @copyright  2024 David Woloszyn <david.woloszyn@moodle.com>
25
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 * @covers \mod_quiz\notification_helper
27
 */
28
final class notification_helper_test extends \advanced_testcase {
29
    /**
30
     * Run all the tasks related to the notifications.
31
     */
32
    protected function run_notification_helper_tasks(): void {
33
        $task = \core\task\manager::get_scheduled_task(\mod_quiz\task\queue_all_quiz_open_notification_tasks::class);
34
        $task->execute();
35
        $clock = \core\di::get(\core\clock::class);
36
 
37
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
38
        if ($adhoctask) {
39
            $this->assertInstanceOf(\mod_quiz\task\queue_quiz_open_notification_tasks_for_users::class, $adhoctask);
40
            $adhoctask->execute();
41
            \core\task\manager::adhoc_task_complete($adhoctask);
42
        }
43
 
44
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
45
        if ($adhoctask) {
46
            $this->assertInstanceOf(\mod_quiz\task\send_quiz_open_soon_notification_to_user::class, $adhoctask);
47
            $adhoctask->execute();
48
            \core\task\manager::adhoc_task_complete($adhoctask);
49
        }
50
    }
51
 
52
    /**
53
     * Test getting quizzes with a 'timeopen' date within the date range.
54
     */
55
    public function test_get_quizzes_within_date_range(): void {
56
        $this->resetAfterTest();
57
        $generator = $this->getDataGenerator();
58
        $helper = \core\di::get(notification_helper::class);
59
        $clock = $this->mock_clock_with_frozen();
60
 
61
        // Create a quiz with an open date < 48 hours.
62
        $course = $generator->create_course();
63
        $generator->create_module('quiz', ['course' => $course->id, 'timeopen' => $clock->time() + DAYSECS]);
64
 
65
        // Check that we have a result returned.
66
        $result = $helper::get_quizzes_within_date_range();
67
        $this->assertTrue($result->valid());
68
        $result->close();
69
 
70
        // Time travel 3 days into the future. We should have no quizzes in range.
71
        $clock->bump(DAYSECS * 3);
72
        $result = $helper::get_quizzes_within_date_range();
73
        $this->assertFalse($result->valid());
74
        $result->close();
75
    }
76
 
77
    /**
78
     * Test getting users within a quiz that are within our date range.
79
     */
80
    public function test_get_users_within_quiz(): void {
81
        global $DB;
82
        $this->resetAfterTest();
83
        $generator = $this->getDataGenerator();
84
        $helper = \core\di::get(notification_helper::class);
85
        $clock = $this->mock_clock_with_frozen();
86
 
87
        // Create a course and enrol some users.
88
        $course = $generator->create_course();
89
        $user1 = $generator->create_user();
90
        $user2 = $generator->create_user();
91
        $user3 = $generator->create_user();
92
        $user4 = $generator->create_user();
93
        $user5 = $generator->create_user();
94
        $user6 = $generator->create_user(['suspended' => 1]);
95
        $generator->enrol_user($user1->id, $course->id, 'student');
96
        $generator->enrol_user($user2->id, $course->id, 'student');
97
        $generator->enrol_user($user3->id, $course->id, 'student');
98
        $generator->enrol_user($user4->id, $course->id, 'student');
99
        $generator->enrol_user($user5->id, $course->id, 'teacher');
100
        $generator->enrol_user($user6->id, $course->id, 'student');
101
 
102
        /** @var \mod_quiz_generator $quizgenerator */
103
        $quizgenerator = $generator->get_plugin_generator('mod_quiz');
104
 
105
        // Create a quiz with an open date < 48 hours.
106
        $timeopen = $clock->time() + DAYSECS;
107
        $quiz = $quizgenerator->create_instance([
108
            'course' => $course->id,
109
            'timeopen' => $timeopen,
110
        ]);
111
 
112
        // User1 will have a user specific override, giving them an extra 1 hour for 'timeopen'.
113
        $usertimeopen = $timeopen + HOURSECS;
114
        $quizgenerator->create_override([
115
            'quiz' => $quiz->id,
116
            'userid' => $user1->id,
117
            'timeopen' => $usertimeopen,
118
        ]);
119
 
120
        // User2 and user3 will have a group override, giving them an extra 2 hours for 'timeopen'.
121
        $grouptimeopen = $timeopen + (HOURSECS * 2);
122
        $group = $generator->create_group(['courseid' => $course->id]);
123
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
124
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
125
        $quizgenerator->create_override([
126
            'quiz' => $quiz->id,
127
            'groupid' => $group->id,
128
            'timeopen' => $grouptimeopen,
129
        ]);
130
 
131
        // Get the users within the date range.
132
        $quizzes = $helper::get_quizzes_within_date_range();
133
        foreach ($quizzes as $q) {
134
            $users = $helper::get_users_within_quiz($q->id);
135
        }
136
        $quizzes->close();
137
 
138
        // User1 has the 'user' override and its 'timeopen' date has been updated.
139
        $this->assertEquals($usertimeopen, $users[$user1->id]->timeopen);
140
        $this->assertEquals('user', $users[$user1->id]->overridetype);
141
 
142
        // User2 and user3 have the 'group' override and their 'timeopen' date has been updated.
143
        $this->assertEquals($grouptimeopen, $users[$user2->id]->timeopen);
144
        $this->assertEquals('group', $users[$user2->id]->overridetype);
145
        $this->assertEquals($grouptimeopen, $users[$user3->id]->timeopen);
146
        $this->assertEquals('group', $users[$user3->id]->overridetype);
147
 
148
        // User4 is unchanged.
149
        $this->assertEquals($timeopen, $users[$user4->id]->timeopen);
150
        $this->assertEquals('none', $users[$user4->id]->overridetype);
151
 
152
        // User5 should not be in the returned users because they are a teacher.
153
        $this->assertArrayNotHasKey($user5->id, $users);
154
 
155
        // User6 should not be in the returned users because it is suspended.
156
        $this->assertArrayNotHasKey($user6->id, $users);
157
 
158
        // Let's add some availability conditions.
159
        $availability =
160
        [
161
            'op' => '&',
162
            'showc' => [true],
163
            'c' => [
164
                [
165
                    'type' => 'group',
166
                    'id' => (int)$group->id,
167
                ],
168
            ],
169
        ];
170
        $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
171
        $DB->set_field('course_modules', 'availability', json_encode($availability), ['id' => $cm->id]);
172
 
173
        // Rebuild course cache to apply changes.
174
        rebuild_course_cache($course->id, true);
175
 
176
        // Get the users after availability conditions of the given quiz.
177
        $users = notification_helper::get_users_within_quiz($quiz->id);
178
 
179
        // Returns only users matching availability conditions who are in the specified group.
180
        $this->assertCount(2, $users);
181
        ksort($users);
182
        $this->assertEquals([$user2->id, $user3->id], array_keys($users));
183
    }
184
 
185
    /**
186
     * Test sending the quiz open soon notification to a user.
187
     */
188
    public function test_send_notification_to_user(): void {
189
        global $DB;
190
        $this->resetAfterTest();
191
        $generator = $this->getDataGenerator();
192
        $helper = \core\di::get(notification_helper::class);
193
        $clock = $this->mock_clock_with_frozen();
194
        $sink = $this->redirectMessages();
195
 
196
        // Create a course and enrol a user.
197
        $course = $generator->create_course();
198
        $user1 = $generator->create_user();
199
        $generator->enrol_user($user1->id, $course->id, 'student');
200
 
201
        /** @var \mod_quiz_generator $quizgenerator */
202
        $quizgenerator = $generator->get_plugin_generator('mod_quiz');
203
 
204
        // Create a quiz with an open date < 48 hours.
205
        $timeopen = $clock->time() + DAYSECS;
206
        $quiz = $quizgenerator->create_instance([
207
            'course' => $course->id,
208
            'timeopen' => $timeopen,
209
        ]);
210
        $clock->bump(5);
211
 
212
        // Get the users within the date range.
213
        $quizzes = $helper::get_quizzes_within_date_range();
214
        foreach ($quizzes as $q) {
215
            $users = $helper::get_users_within_quiz($q->id);
216
        }
217
        $quizzes->close();
218
 
219
        // Run the tasks.
220
        $this->run_notification_helper_tasks();
221
 
222
        // Get the notifications that should have been created during the adhoc task.
223
        $this->assertCount(1, $sink->get_messages());
224
 
225
        // Check the subject matches.
226
        $messages = $sink->get_messages_by_component('mod_quiz');
227
        $message = reset($messages);
228
        $stringparams = ['timeopen' => userdate($users[$user1->id]->timeopen), 'quizname' => $quiz->name];
229
        $expectedsubject = get_string('quizopendatesoonsubject', 'mod_quiz', $stringparams);
230
        $this->assertEquals($expectedsubject, $message->subject);
231
 
232
        // Clear sink.
233
        $sink->clear();
234
 
235
        // Run the tasks again.
236
        $this->run_notification_helper_tasks();
237
 
238
        // There should be no notification because nothing has changed.
239
        $this->assertEmpty($sink->get_messages_by_component('mod_quiz'));
240
 
241
        // Let's modify the 'timeopen' for the quiz (it will still be within the 48 hour range).
242
        $updatedata = new \stdClass();
243
        $updatedata->id = $quiz->id;
244
        $updatedata->timeopen = $timeopen + HOURSECS;
245
        $DB->update_record('quiz', $updatedata);
246
 
247
        // Run the tasks again.
248
        $this->run_notification_helper_tasks();
249
 
250
        // There should be a new notification because the 'timeopen' has been updated.
251
        $this->assertCount(1, $sink->get_messages_by_component('mod_quiz'));
252
        // Clear sink.
253
        $sink->clear();
254
 
255
        // Let's modify the 'timeopen' one more time and change the visibility.
256
        $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
257
        $DB->set_field('course_modules', 'visible', 0, ['id' => $cm->id]);
258
 
259
        $updatedata = new \stdClass();
260
        $updatedata->id = $quiz->id;
261
        $updatedata->timeopen = $timeopen + DAYSECS;
262
        $DB->update_record('quiz', $updatedata);
263
 
264
        // Run the tasks again.
265
        $this->run_notification_helper_tasks();
266
 
267
        // There should not be a new notification because the quiz is not visible.
268
        $this->assertCount(0, $sink->get_messages_by_component('mod_quiz'));
269
 
270
        // Set back the visibility.
271
        $DB->set_field('course_modules', 'visible', 1, ['id' => $cm->id]);
272
 
273
        // Clear sink.
274
        $sink->clear();
275
 
276
        // Let's modify the 'timeopen' one more time.
277
        $updatedata = new \stdClass();
278
        $updatedata->id = $quiz->id;
279
        $updatedata->timeopen = $timeopen + (HOURSECS * 2);
280
        $DB->update_record('quiz', $updatedata);
281
 
282
        // This time, the user will submit an attempt.
283
        $DB->insert_record('quiz_attempts', [
284
            'quiz' => $quiz->id,
285
            'userid' => $user1->id,
286
            'state' => 'finished',
287
            'timestart' => $clock->time(),
288
            'timecheckstate' => 0,
289
            'layout' => '',
290
            'uniqueid' => 123,
291
        ]);
292
        $clock->bump(5);
293
 
294
        // Run the tasks again.
295
        $this->run_notification_helper_tasks();
296
 
297
        // No new notification should have been sent.
298
        $this->assertEmpty($sink->get_messages_by_component('mod_quiz'));
299
 
300
        // Clear sink.
301
        $sink->clear();
302
    }
303
 
304
    /**
305
     * Test content filtering in the quiz open soon notification.
306
     */
307
    public function test_send_notification_to_user_filter_content(): void {
308
        $this->resetAfterTest();
309
 
310
        $generator = $this->getDataGenerator();
311
        $clock = $this->mock_clock_with_frozen();
312
        $sink = $this->redirectMessages();
313
 
314
        filter_set_global_state('multilang', TEXTFILTER_ON);
315
        filter_set_applies_to_strings('multilang', true);
316
 
317
        // Create a course and enrol a student.
318
        $course = $generator->create_course([
319
            'fullname' => '<span class="multilang" lang="en">A&B (en)</span><span class="multilang" lang="es">A&B (es)</span>'
320
        ]);
321
        $student = $generator->create_user();
322
        $generator->enrol_user($student->id, $course->id, 'student');
323
 
324
        $quizgenerator = $generator->get_plugin_generator('mod_quiz');
325
 
326
        // Create a quiz with an open date < 48 hours.
327
        $quiz = $quizgenerator->create_instance([
328
            'name' => '<span class="multilang" lang="en">C&D (en)</span><span class="multilang" lang="es">C&D (es)</span>',
329
            'course' => $course->id,
330
            'timeopen' => $clock->time() + DAYSECS,
331
        ]);
332
        $clock->bump(5);
333
 
334
        // Run the tasks.
335
        $this->run_notification_helper_tasks();
336
 
337
        // Get the notifications that should have been created during the adhoc task.
338
        $messages = $sink->get_messages_by_component('mod_quiz');
339
 
340
        $message = reset($messages);
341
 
342
        $subjectstringparams = [
343
            'timeopen' => userdate($quiz->timeopen),
344
            'quizname' => 'C&D (en)'
345
        ];
346
        $expectedsubject = get_string('quizopendatesoonsubject', 'mod_quiz', $subjectstringparams);
347
 
348
        $fullmessagestringparams = array_merge($subjectstringparams, [
349
            'firstname' => $student->firstname,
350
            'coursename' => 'A&B (en)',
351
            'timeclose' => !empty($quiz->timeclose) ? userdate($quiz->timeclose) : get_string('statusna'),
352
            'url' => new \moodle_url('/mod/quiz/view.php', ['id' => $quiz->cmid])
353
        ]);
354
        $expectedfullmessage = get_string('quizopendatesoonhtml', 'mod_quiz', $fullmessagestringparams);
355
 
356
        // Validate
357
        $this->assertEquals($expectedsubject, $message->subject);
358
        $this->assertEquals($expectedfullmessage, $message->fullmessagehtml);
359
 
360
        // Clear sink.
361
        $sink->clear();
362
    }
363
}