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_assign;
18
 
19
use core\task\task_trait;
20
use mod_assign\task\queue_assignment_due_digest_notification_tasks_for_users;
21
 
22
/**
23
 * Test class for the assignment notification_helper.
24
 *
25
 * @package    mod_assign
26
 * @category   test
27
 * @copyright  2024 David Woloszyn <david.woloszyn@moodle.com>
28
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
29
 * @covers \mod_assign\notification_helper
30
 */
31
final class notification_helper_test extends \advanced_testcase {
32
 
33
    use task_trait;
34
 
35
    /**
36
     * Run all the tasks related to the 'due soon' notifications.
37
     */
38
    protected function run_due_soon_notification_helper_tasks(): void {
39
        $task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_due_soon_notification_tasks::class);
40
        $task->execute();
41
        $clock = \core\di::get(\core\clock::class);
42
 
43
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
44
        if ($adhoctask) {
45
            $this->assertInstanceOf(\mod_assign\task\queue_assignment_due_soon_notification_tasks_for_users::class, $adhoctask);
46
            $adhoctask->execute();
47
            \core\task\manager::adhoc_task_complete($adhoctask);
48
        }
49
 
50
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
51
        if ($adhoctask) {
52
            $this->assertInstanceOf(\mod_assign\task\send_assignment_due_soon_notification_to_user::class, $adhoctask);
53
            $adhoctask->execute();
54
            \core\task\manager::adhoc_task_complete($adhoctask);
55
        }
56
    }
57
 
58
    /**
59
     * Test getting due soon assignments.
60
     */
61
    public function test_get_due_soon_assignments(): void {
62
        $this->resetAfterTest();
63
        $generator = $this->getDataGenerator();
64
        $helper = \core\di::get(notification_helper::class);
65
        $clock = $this->mock_clock_with_frozen();
66
 
67
        // Create an assignment with a due date < 48 hours.
68
        $course = $generator->create_course();
69
        $generator->create_module('assign', ['course' => $course->id, 'duedate' => $clock->time() + DAYSECS]);
70
 
71
        // Check that we have a result returned.
72
        $result = $helper::get_due_soon_assignments();
73
        $this->assertTrue($result->valid());
74
        $result->close();
75
 
76
        // Time travel 3 days into the future. We should have no assignments in range.
77
        $clock->bump(DAYSECS * 3);
78
        $result = $helper::get_due_soon_assignments();
79
        $this->assertFalse($result->valid());
80
        $result->close();
81
    }
82
 
83
    /**
84
     * Test getting users within an assignment that have a due date soon.
85
     */
86
    public function test_get_due_soon_users_within_assignment(): void {
87
        $this->resetAfterTest();
88
        $generator = $this->getDataGenerator();
89
        $helper = \core\di::get(notification_helper::class);
90
        $clock = $this->mock_clock_with_frozen();
91
 
92
        // Create a course and enrol some users.
93
        $course = $generator->create_course();
94
        $user1 = $generator->create_user();
95
        $user2 = $generator->create_user();
96
        $user3 = $generator->create_user();
97
        $user4 = $generator->create_user();
98
        $user5 = $generator->create_user();
99
        $user6 = $generator->create_user();
100
        $generator->enrol_user($user1->id, $course->id, 'student');
101
        $generator->enrol_user($user2->id, $course->id, 'student');
102
        $generator->enrol_user($user3->id, $course->id, 'student');
103
        $generator->enrol_user($user4->id, $course->id, 'student');
104
        $generator->enrol_user($user5->id, $course->id, 'student');
105
        $generator->enrol_user($user6->id, $course->id, 'teacher');
106
 
107
        /** @var \mod_assign_generator $assignmentgenerator */
108
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
109
 
110
        // Create an assignment with a due date < 48 hours.
111
        $duedate = $clock->time() + DAYSECS;
112
        $assignment = $assignmentgenerator->create_instance([
113
            'course' => $course->id,
114
            'duedate' => $duedate,
115
            'submissiondrafts' => 0,
116
            'assignsubmission_onlinetext_enabled' => 1,
117
        ]);
118
 
119
        // User1 will have a user override, giving them an extra 1 hour for 'duedate'.
120
        $userduedate = $duedate + HOURSECS;
121
        $assignmentgenerator->create_override([
122
            'assignid' => $assignment->id,
123
            'userid' => $user1->id,
124
            'duedate' => $userduedate,
125
        ]);
126
 
127
        // User2 and user3 will have a group override, giving them an extra 2 hours for 'duedate'.
128
        $groupduedate = $duedate + (HOURSECS * 2);
129
        $group = $generator->create_group(['courseid' => $course->id]);
130
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
131
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
132
        $assignmentgenerator->create_override([
133
            'assignid' => $assignment->id,
134
            'groupid' => $group->id,
135
            'duedate' => $groupduedate,
136
        ]);
137
 
138
        // User4 will have a user override of one extra week, excluding them from the results.
139
        $userduedate = $duedate + WEEKSECS;
140
        $assignmentgenerator->create_override([
141
            'assignid' => $assignment->id,
142
            'userid' => $user4->id,
143
            'duedate' => $userduedate,
144
        ]);
145
 
146
        $assignmentgenerator->create_submission([
147
            'userid' => $user5->id,
148
            'cmid' => $assignment->cmid,
149
            'status' => 'submitted',
150
            'timemodified' => $clock->time(),
151
            'onlinetext' => 'Some text',
152
            'assignsubmission_onlinetext_enabled' => 1,
153
        ]);
154
 
155
        // There should be 3 users with the teacher excluded.
156
        $users = $helper::get_users_within_assignment($assignment->id, $helper::TYPE_DUE_SOON);
157
        $this->assertCount(3, $users);
158
    }
159
 
160
    /**
161
     * Test sending the assignment due soon notification to a user.
162
     */
163
    public function test_send_due_soon_notification_to_user(): void {
164
        global $DB;
165
        $this->resetAfterTest();
166
        $generator = $this->getDataGenerator();
167
        $helper = \core\di::get(notification_helper::class);
168
        $clock = $this->mock_clock_with_frozen();
169
        $sink = $this->redirectMessages();
170
 
171
        // Create a course and enrol a user.
172
        $course = $generator->create_course();
173
        $user1 = $generator->create_user();
174
        $generator->enrol_user($user1->id, $course->id, 'student');
175
 
176
        // Suspended user, should not receive notification.
177
        $user2 = $generator->create_user(['suspended' => 1]);
178
        $generator->enrol_user($user2->id, $course->id, 'student');
179
 
180
        // Nologin user, should not receive notification.
181
        $user3 = $generator->create_user(['auth' => 'nologin']);
182
        $generator->enrol_user($user3->id, $course->id, 'student');
183
 
184
        /** @var \mod_assign_generator $assignmentgenerator */
185
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
186
 
187
        // Create an assignment with a due date < 48 hours.
188
        $duedate = $clock->time() + DAYSECS;
189
        $assignment = $assignmentgenerator->create_instance([
190
            'course' => $course->id,
191
            'duedate' => $duedate,
192
            'submissiondrafts' => 0,
193
            'assignsubmission_onlinetext_enabled' => 1,
194
        ]);
195
        $clock->bump(5);
196
 
197
        // Run the tasks.
198
        $this->run_due_soon_notification_helper_tasks();
199
 
200
        // Get the assignment object.
201
        [$course, $assigncm] = get_course_and_cm_from_instance($assignment->id, 'assign');
202
        $cmcontext = \context_module::instance($assigncm->id);
203
        $assignmentobj = new \assign($cmcontext, $assigncm, $course);
204
        $duedate = $assignmentobj->get_instance($user1->id)->duedate;
205
 
206
        // Get the notifications that should have been created during the adhoc task.
207
        $messages = $sink->get_messages_by_component('mod_assign');
208
        $this->assertCount(1, $messages);
209
 
210
        // Check the subject matches.
211
        $message = reset($messages);
212
        $stringparams = [
213
            'duedate' => userdate($duedate),
214
            'assignmentname' => $assignment->name,
215
        ];
216
        $expectedsubject = get_string('assignmentduesoonsubject', 'mod_assign', $stringparams);
217
        $this->assertEquals($expectedsubject, $message->subject);
218
 
219
        // Clear sink.
220
        $sink->clear();
221
 
222
        // Run the tasks again.
223
        $this->run_due_soon_notification_helper_tasks();
224
 
225
        // There should be no notification because nothing has changed.
226
        $this->assertEmpty($sink->get_messages_by_component('mod_assign'));
227
 
228
        // Let's modify the 'duedate' for the assignment (it will still be within the 48 hour range).
229
        $updatedata = new \stdClass();
230
        $updatedata->id = $assignment->id;
231
        $updatedata->duedate = $duedate + HOURSECS;
232
        $DB->update_record('assign', $updatedata);
233
 
234
        // Run the tasks again.
235
        $this->run_due_soon_notification_helper_tasks();
236
 
237
        // There should be a new notification because the 'duedate' has been updated.
238
        $messages = $sink->get_messages_by_component('mod_assign');
239
        $this->assertCount(1, $messages);
240
        $message = reset($messages);
241
        $stringparams = [
242
            'duedate' => userdate($updatedata->duedate),
243
            'assignmentname' => $assignment->name,
244
        ];
245
        $expectedsubject = get_string('assignmentduesoonsubject', 'mod_assign', $stringparams);
246
        $this->assertEquals($expectedsubject, $message->subject);
247
 
248
        // Clear sink.
249
        $sink->clear();
250
 
251
        // Let's update the assignment visibility.
252
        $DB->set_field('course_modules', 'visible', 0, ['id' => $assigncm->id]);
253
 
254
        // Update the duedate to force a new notification.
255
        $updatedata = new \stdClass();
256
        $updatedata->id = $assignment->id;
257
        $updatedata->duedate = $duedate + HOURSECS * 3;
258
        $DB->update_record('assign', $updatedata);
259
 
260
        // Run the tasks again.
261
        $this->run_due_soon_notification_helper_tasks();
262
 
263
        // There should not be a new notification the assignmnet is not visible.
264
        $this->assertEmpty($sink->get_messages_by_component('mod_assign'));
265
 
266
        // Update the visibility back to visible before the next assert.
267
        $DB->set_field('course_modules', 'visible', 1, ['id' => $assigncm->id]);
268
 
269
        // Clear sink.
270
        $sink->clear();
271
 
272
        // Let's modify the 'duedate' one more time.
273
        $updatedata = new \stdClass();
274
        $updatedata->id = $assignment->id;
275
        $updatedata->duedate = $duedate + (HOURSECS * 2);
276
        $DB->update_record('assign', $updatedata);
277
 
278
        // This time, the user will submit the assignment.
279
        $assignmentgenerator->create_submission([
280
            'userid' => $user1->id,
281
            'cmid' => $assignment->cmid,
282
            'status' => 'submitted',
283
            'timemodified' => $clock->time(),
284
            'onlinetext' => 'Some text',
285
        ]);
286
        $clock->bump(5);
287
 
288
        // Run the tasks again.
289
        $this->run_due_soon_notification_helper_tasks();
290
 
291
        // There should only be one notifcation for submission (not for due soon).
292
        $messages = $sink->get_messages_by_component('mod_assign');
293
        $this->assertCount(1, $messages);
294
        $expectedsubject = get_string('submissionreceiptsmall', 'mod_assign', ['assignment' => $assignment->name]);
295
        $this->assertEquals($expectedsubject, reset($messages)->subject);
296
 
297
        // Clear sink.
298
        $sink->clear();
299
    }
300
 
301
    /**
302
     * Test that we do not fail on deleted assignments with due soon notifications to a user.
303
     */
304
    public function test_not_to_fail_on_deleted_assigment_with_due_soon_notifications_to_user(): void {
305
        global $DB;
306
        $this->resetAfterTest();
307
        $generator = $this->getDataGenerator();
308
        $clock = $this->mock_clock_with_frozen();
309
 
310
        // Create a course and enrol a user.
311
        $course = $generator->create_course();
312
        $user1 = $generator->create_user();
313
        $generator->enrol_user($user1->id, $course->id, 'student');
314
 
315
        /** @var \mod_assign_generator $assignmentgenerator */
316
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
317
 
318
        // Create an assignment with a due date < 48 hours.
319
        $duedate = $clock->time() + DAYSECS;
320
        $assignment = $assignmentgenerator->create_instance([
321
            'course' => $course->id,
322
            'duedate' => $duedate,
323
            'submissiondrafts' => 0,
324
            'assignsubmission_onlinetext_enabled' => 1,
325
        ]);
326
        $clock->bump(5);
327
 
328
        // Run the scheduled and ad-hoc task to queue the notifications.
329
        $task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_due_soon_notification_tasks::class);
330
        $task->execute();
331
 
332
        $clock->bump(5);
333
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
334
        $this->assertInstanceOf(\mod_assign\task\queue_assignment_due_soon_notification_tasks_for_users::class, $adhoctask);
335
        $adhoctask->execute();
336
        \core\task\manager::adhoc_task_complete($adhoctask);
337
 
338
        // Delete the assignment.
339
        $DB->delete_records('assign', ['id' => $assignment->id]);
340
 
341
        // Try to run the ad-hoc task to send the notifications.
342
        $clock->bump(5);
343
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
344
        $this->assertInstanceOf(\mod_assign\task\send_assignment_due_soon_notification_to_user::class, $adhoctask);
345
 
346
        ob_start();
347
        $adhoctask->execute();
348
        $output = ob_get_clean();
349
 
350
        \core\task\manager::adhoc_task_complete($adhoctask);
351
 
352
        // The ad-hoc task should be deleted.
353
        $this->assertNull(\core\task\manager::get_next_adhoc_task($clock->time()));
354
        $this->assertStringContainsString(
355
            needle: "No notification send as the assignment $assignment->id can no longer be found in the database.",
356
            haystack: $output
357
        );
358
    }
359
 
360
    /**
361
     * Run all the tasks related to the 'overdue' notifications.
362
     */
363
    protected function run_overdue_notification_helper_tasks(): void {
364
        $task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_overdue_notification_tasks::class);
365
        $task->execute();
366
        $clock = \core\di::get(\core\clock::class);
367
 
368
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
369
        if ($adhoctask) {
370
            $this->assertInstanceOf(\mod_assign\task\queue_assignment_overdue_notification_tasks_for_users::class, $adhoctask);
371
            $adhoctask->execute();
372
            \core\task\manager::adhoc_task_complete($adhoctask);
373
        }
374
 
375
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
376
        if ($adhoctask) {
377
            $this->assertInstanceOf(\mod_assign\task\send_assignment_overdue_notification_to_user::class, $adhoctask);
378
            $adhoctask->execute();
379
            \core\task\manager::adhoc_task_complete($adhoctask);
380
        }
381
    }
382
 
383
    /**
384
     * Test getting overdue assignments.
385
     */
386
    public function test_get_overdue_assignments(): void {
387
        $this->resetAfterTest();
388
        $generator = $this->getDataGenerator();
389
        $helper = \core\di::get(notification_helper::class);
390
        $clock = $this->mock_clock_with_frozen();
391
 
392
        // Create an overdue assignment.
393
        $course = $generator->create_course();
394
        $generator->create_module('assign', ['course' => $course->id, 'duedate' => $clock->time() - HOURSECS]);
395
 
396
        // Check that we have a result returned.
397
        $result = $helper::get_overdue_assignments();
398
        $this->assertTrue($result->valid());
399
        $result->close();
400
 
401
        // Time travel 2 hours into the future.
402
        // We should have no assignments found as we are only getting overdue assignments within a 2 hour window.
403
        $clock->bump(HOURSECS * 2);
404
        $result = $helper::get_overdue_assignments();
405
        $this->assertFalse($result->valid());
406
        $result->close();
407
    }
408
 
409
    /**
410
     * Test getting users within an assignment that is overdue.
411
     */
412
    public function test_get_overdue_users_within_assignment(): void {
413
        $this->resetAfterTest();
414
        $generator = $this->getDataGenerator();
415
        $helper = \core\di::get(notification_helper::class);
416
        $clock = $this->mock_clock_with_frozen();
417
 
418
        // Create a course and enrol some users.
419
        $course = $generator->create_course();
420
        $user1 = $generator->create_and_enrol($course, 'student');
421
        $user2 = $generator->create_and_enrol($course, 'student');
422
        $user3 = $generator->create_and_enrol($course, 'student');
423
        $user4 = $generator->create_and_enrol($course, 'student');
424
        $user5 = $generator->create_and_enrol($course, 'student');
425
        $user6 = $generator->create_and_enrol($course, 'student');
426
        $user7 = $generator->create_and_enrol($course, 'teacher');
427
 
428
        /** @var \mod_assign_generator $assignmentgenerator */
429
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
430
 
431
        // Create an overdue assignment.
432
        $duedate = $clock->time() - HOURSECS;
433
        $assignment = $assignmentgenerator->create_instance([
434
            'course' => $course->id,
435
            'duedate' => $duedate,
436
            'submissiondrafts' => 0,
437
            'assignsubmission_onlinetext_enabled' => 1,
438
        ]);
439
 
440
        // User1 will have a user override, giving them an extra minute for 'duedate'.
441
        $userduedate = $duedate + MINSECS;
442
        $assignmentgenerator->create_override([
443
            'assignid' => $assignment->id,
444
            'userid' => $user1->id,
445
            'duedate' => $userduedate,
446
        ]);
447
 
448
        // User2 and user3 will have a group override, giving them an extra minute for 'duedate'.
449
        $groupduedate = $duedate + MINSECS;
450
        $group = $generator->create_group(['courseid' => $course->id]);
451
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
452
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
453
        $assignmentgenerator->create_override([
454
            'assignid' => $assignment->id,
455
            'groupid' => $group->id,
456
            'duedate' => $groupduedate,
457
        ]);
458
 
459
        // User4 will have a user override of one extra week, excluding them from the results.
460
        $userduedate = $duedate + WEEKSECS;
461
        $assignmentgenerator->create_override([
462
            'assignid' => $assignment->id,
463
            'userid' => $user4->id,
464
            'duedate' => $userduedate,
465
        ]);
466
 
467
        // User5 will submit the assignment, excluding them from the results.
468
        $assignmentgenerator->create_submission([
469
            'userid' => $user5->id,
470
            'cmid' => $assignment->cmid,
471
            'status' => 'submitted',
472
            'timemodified' => $clock->time(),
473
            'onlinetext' => 'Some text',
474
            'assignsubmission_onlinetext_enabled' => 1,
475
        ]);
476
 
477
        // User6 will have a cut-off date override that has already lapsed, excluding them from the results.
478
        $usercutoffdate = $clock->time() - MINSECS;
479
        $assignmentgenerator->create_override([
480
            'assignid' => $assignment->id,
481
            'userid' => $user6->id,
482
            'cutoffdate' => $usercutoffdate,
483
        ]);
484
 
485
        // There should be 3 users with the teacher excluded.
486
        $users = $helper::get_users_within_assignment($assignment->id, $helper::TYPE_OVERDUE);
487
        $this->assertCount(3, $users);
488
        $this->assertArrayHasKey($user1->id, $users);
489
        $this->assertArrayHasKey($user2->id, $users);
490
        $this->assertArrayHasKey($user3->id, $users);
491
    }
492
 
493
    /**
494
     * Test sending the assignment overdue notification to a user.
495
     */
496
    public function test_send_overdue_notification_to_user(): void {
497
        global $DB;
498
        $this->resetAfterTest();
499
        $generator = $this->getDataGenerator();
500
        $clock = $this->mock_clock_with_frozen();
501
        $sink = $this->redirectMessages();
502
 
503
        // Create a course and enrol a user.
504
        $course = $generator->create_course();
505
        $user1 = $generator->create_and_enrol($course, 'student');
506
 
507
        // Suspended user, should not receive notification.
508
        $user2 = $generator->create_user(['suspended' => 1]);
509
        $generator->enrol_user($user2->id, $course->id, 'student');
510
 
511
        // Nologin user, should not receive notification.
512
        $user3 = $generator->create_user(['auth' => 'nologin']);
513
        $generator->enrol_user($user3->id, $course->id, 'student');
514
 
515
        /** @var \mod_assign_generator $assignmentgenerator */
516
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
517
 
518
        // Create an assignment that is overdue.
519
        $duedate = $clock->time() - HOURSECS;
520
        $cutoffdate = $clock->time() + DAYSECS;
521
        $assignment = $assignmentgenerator->create_instance([
522
            'course' => $course->id,
523
            'duedate' => $duedate,
524
            'cutoffdate' => $cutoffdate,
525
            'submissiondrafts' => 0,
526
            'assignsubmission_onlinetext_enabled' => 1,
527
        ]);
528
        $clock->bump(5);
529
 
530
        // Run the tasks.
531
        $this->run_overdue_notification_helper_tasks();
532
 
533
        // Get the notifications that should have been created during the adhoc task.
534
        $this->assertCount(1, $sink->get_messages());
535
 
536
        // Check the subject matches.
537
        $messages = $sink->get_messages_by_component('mod_assign');
538
        $message = reset($messages);
539
        $expectedsubject = get_string('assignmentoverduesubject', 'mod_assign', ['assignmentname' => $assignment->name]);
540
        $this->assertEquals($expectedsubject, $message->subject);
541
 
542
        // Clear sink.
543
        $sink->clear();
544
 
545
        // Run the tasks again.
546
        $this->run_overdue_notification_helper_tasks();
547
 
548
        // There should be no notification because nothing has changed.
549
        $this->assertEmpty($sink->get_messages_by_component('mod_assign'));
550
 
551
        // Let's modify the 'duedate' for the assignment (it will still be overdue).
552
        $updatedata = new \stdClass();
553
        $updatedata->id = $assignment->id;
554
        $updatedata->duedate = $duedate + MINSECS;
555
        $DB->update_record('assign', $updatedata);
556
 
557
        // Clear sink.
558
        $sink->clear();
559
 
560
        // Run the tasks again.
561
        $this->run_overdue_notification_helper_tasks();
562
 
563
        // There should be a new notification because the 'duedate' has been updated.
564
        $messages = $sink->get_messages_by_component('mod_assign');
565
        $this->assertCount(1, $messages);
566
        $message = reset($messages);
567
        $expectedsubject = get_string('assignmentoverduesubject', 'mod_assign', ['assignmentname' => $assignment->name]);
568
        $this->assertEquals($expectedsubject, $message->subject);
569
 
570
        // Let's modify the 'cut-off date'.
571
        $updatedata = new \stdClass();
572
        $updatedata->id = $assignment->id;
573
        $updatedata->cutoffdate = $cutoffdate + MINSECS;
574
        $DB->update_record('assign', $updatedata);
575
 
576
        // Clear sink.
577
        $sink->clear();
578
 
579
        // Run the tasks again.
580
        $this->run_overdue_notification_helper_tasks();
581
 
582
        // There should be a new notification because the 'cut-off date' has been updated.
583
        $messages = $sink->get_messages_by_component('mod_assign');
584
        $message = reset($messages);
585
        $expectedsubject = get_string('assignmentoverduesubject', 'mod_assign', ['assignmentname' => $assignment->name]);
586
        $this->assertEquals($expectedsubject, $message->subject);
587
 
588
        // Let's update the assignment visibility.
589
        $cm = get_coursemodule_from_instance('assign', $assignment->id, $course->id);
590
        $DB->set_field('course_modules', 'visible', 0, ['id' => $cm->id]);
591
 
592
        // Update the duedate to force a new notification.
593
        $updatedata = new \stdClass();
594
        $updatedata->id = $assignment->id;
595
        $updatedata->duedate = $duedate + (MINSECS * 3);
596
        $DB->update_record('assign', $updatedata);
597
 
598
        // Clear sink.
599
        $sink->clear();
600
 
601
        // Run the tasks again.
602
        $this->run_overdue_notification_helper_tasks();
603
 
604
        // There should not be a new notification because the assignment is not visible.
605
        $this->assertEmpty($sink->get_messages_by_component('mod_assign'));
606
 
607
        // Update the visibility back to visible before the next assert.
608
        $DB->set_field('course_modules', 'visible', 1, ['id' => $cm->id]);
609
 
610
        // Let's modify the 'duedate' one more time.
611
        $updatedata = new \stdClass();
612
        $updatedata->id = $assignment->id;
613
        $updatedata->duedate = $duedate + (MINSECS * 2);
614
        $DB->update_record('assign', $updatedata);
615
 
616
        // This time, the user will submit the assignment.
617
        $assignmentgenerator->create_submission([
618
            'userid' => $user1->id,
619
            'cmid' => $assignment->cmid,
620
            'status' => 'submitted',
621
            'timemodified' => $clock->time(),
622
            'onlinetext' => 'Some text',
623
            'assignsubmission_onlinetext_enabled' => 1,
624
        ]);
625
 
626
        // Clear sink.
627
        $sink->clear();
628
 
629
        // Run the tasks again.
630
        $this->run_overdue_notification_helper_tasks();
631
 
632
        // No new notification should have been sent.
633
        $this->assertEmpty($sink->get_messages_by_component('mod_assign'));
634
    }
635
 
636
    /**
637
     * Test that we do not fail on deleted assignments with overdue notifications to a user.
638
     */
639
    public function test_not_to_fail_on_deleted_assigment_with_overdue_notifications_to_user(): void {
640
        global $DB;
641
        $this->resetAfterTest();
642
        $generator = $this->getDataGenerator();
643
        $clock = $this->mock_clock_with_frozen();
644
 
645
        // Create a course and enrol a user.
646
        $course = $generator->create_course();
647
        $user1 = $generator->create_user();
648
        $generator->enrol_user($user1->id, $course->id, 'student');
649
 
650
        /** @var \mod_assign_generator $assignmentgenerator */
651
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
652
 
653
        // Create an assignment that is overdue.
654
        $duedate = $clock->time() - HOURSECS;
655
        $cutoffdate = $clock->time() + DAYSECS;
656
        $assignment = $assignmentgenerator->create_instance([
657
            'course' => $course->id,
658
            'duedate' => $duedate,
659
            'cutoffdate' => $cutoffdate,
660
            'submissiondrafts' => 0,
661
            'assignsubmission_onlinetext_enabled' => 1,
662
        ]);
663
        $clock->bump(5);
664
 
665
        // Run the scheduled and ad-hoc task to queue the notifications.
666
        $task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_overdue_notification_tasks::class);
667
        $task->execute();
668
 
669
        $clock->bump(5);
670
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
671
        $this->assertInstanceOf(\mod_assign\task\queue_assignment_overdue_notification_tasks_for_users::class, $adhoctask);
672
        $adhoctask->execute();
673
        \core\task\manager::adhoc_task_complete($adhoctask);
674
 
675
        // Delete the assignment.
676
        $DB->delete_records('assign', ['id' => $assignment->id]);
677
 
678
        // Try to run the ad-hoc task to send the notifications.
679
        $clock->bump(5);
680
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
681
        $this->assertInstanceOf(\mod_assign\task\send_assignment_overdue_notification_to_user::class, $adhoctask);
682
 
683
        ob_start();
684
        $adhoctask->execute();
685
        $output = ob_get_clean();
686
 
687
        \core\task\manager::adhoc_task_complete($adhoctask);
688
 
689
        // The ad-hoc task should be deleted.
690
        $this->assertNull(\core\task\manager::get_next_adhoc_task($clock->time()));
691
        $this->assertStringContainsString(
692
            needle: "No notification send as the assignment $assignment->id can no longer be found in the database.",
693
            haystack: $output
694
        );
695
    }
696
 
697
    /**
698
     * Run all the tasks related to the due digest notifications.
699
     */
700
    protected function run_due_digest_notification_helper_tasks(): void {
701
        $task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_due_digest_notification_tasks::class);
702
        $task->execute();
703
        $clock = \core\di::get(\core\clock::class);
704
 
705
        $adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
706
        if ($adhoctask) {
707
            $this->assertInstanceOf(\mod_assign\task\send_assignment_due_digest_notification_to_user::class, $adhoctask);
708
            $adhoctask->execute();
709
            \core\task\manager::adhoc_task_complete($adhoctask);
710
        }
711
    }
712
 
713
    /**
714
     * Test getting users for the due digest.
715
     */
716
    public function test_get_users_for_due_digest(): void {
717
        $this->resetAfterTest();
718
        $generator = $this->getDataGenerator();
719
        $helper = \core\di::get(notification_helper::class);
720
        $clock = $this->mock_clock_with_frozen();
721
 
722
        // Create a course and enrol some users.
723
        $course = $generator->create_course();
724
        $user1 = $generator->create_user();
725
        $user2 = $generator->create_user();
726
        $user3 = $generator->create_user();
727
        $user4 = $generator->create_user();
728
        $user5 = $generator->create_user();
729
        $user6 = $generator->create_user();
730
        $generator->enrol_user($user1->id, $course->id, 'student');
731
        $generator->enrol_user($user2->id, $course->id, 'student');
732
        $generator->enrol_user($user3->id, $course->id, 'student');
733
        $generator->enrol_user($user4->id, $course->id, 'student');
734
        $generator->enrol_user($user5->id, $course->id, 'student');
735
        $generator->enrol_user($user6->id, $course->id, 'teacher');
736
 
737
        /** @var \mod_assign_generator $assignmentgenerator */
738
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
739
 
740
        // Create an assignment with a due date 7 days from now (the due digest range).
741
        $duedate = $clock->time() + WEEKSECS;
742
        $assignment = $assignmentgenerator->create_instance([
743
            'course' => $course->id,
744
            'duedate' => $duedate,
745
            'submissiondrafts' => 0,
746
            'assignsubmission_onlinetext_enabled' => 1,
747
        ]);
748
 
749
        // User1 will have a user override, giving them an extra 1 day for 'duedate', excluding them from the results.
750
        $userduedate = $duedate + DAYSECS;
751
        $assignmentgenerator->create_override([
752
            'assignid' => $assignment->id,
753
            'userid' => $user1->id,
754
            'duedate' => $userduedate,
755
        ]);
756
 
757
        // User2 and user3 will have a group override, giving them an extra 2 days for 'duedate', excluding them from the results.
758
        $groupduedate = $duedate + (DAYSECS * 2);
759
        $group = $generator->create_group(['courseid' => $course->id]);
760
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
761
        $generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
762
        $assignmentgenerator->create_override([
763
            'assignid' => $assignment->id,
764
            'groupid' => $group->id,
765
            'duedate' => $groupduedate,
766
        ]);
767
 
768
        // User4 will submit the assignment, excluding them from the results.
769
        $assignmentgenerator->create_submission([
770
            'userid' => $user4->id,
771
            'cmid' => $assignment->cmid,
772
            'status' => 'submitted',
773
            'timemodified' => $clock->time(),
774
            'onlinetext' => 'Some text',
775
            'assignsubmission_onlinetext_enabled' => 1,
776
        ]);
777
 
778
        // There should be 1 user with the teacher excluded.
779
        $users = $helper::get_users_within_assignment($assignment->id, $helper::TYPE_DUE_DIGEST);
780
        $this->assertCount(1, $users);
781
    }
782
 
783
    /**
784
     * Test sending the assignment due digest notification to a user.
785
     */
786
    public function test_send_due_digest_notification_to_user(): void {
787
        global $DB;
788
        $this->resetAfterTest();
789
        $generator = $this->getDataGenerator();
790
        $clock = $this->mock_clock_with_frozen();
791
        $sink = $this->redirectMessages();
792
 
793
        // Create a course and enrol a user.
794
        $course = $generator->create_course();
795
        $user1 = $generator->create_user();
796
        $generator->enrol_user($user1->id, $course->id, 'student');
797
 
798
        // Suspended user, should not receive notification.
799
        $user2 = $generator->create_user(['suspended' => 1]);
800
        $generator->enrol_user($user2->id, $course->id, 'student');
801
 
802
        // Nologin user, should not receive notification.
803
        $user3 = $generator->create_user(['auth' => 'nologin']);
804
        $generator->enrol_user($user3->id, $course->id, 'student');
805
 
806
        /** @var \mod_assign_generator $assignmentgenerator */
807
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
808
 
809
        // Create a few assignments with different due dates.
810
        $duedate1 = $clock->time() + WEEKSECS;
811
        $assignment1 = $assignmentgenerator->create_instance([
812
            'course' => $course->id,
813
            'duedate' => $duedate1,
814
            'submissiondrafts' => 0,
815
            'assignsubmission_onlinetext_enabled' => 1,
816
        ]);
817
        $duedate2 = $clock->time() + WEEKSECS;
818
        $assignment2 = $assignmentgenerator->create_instance([
819
            'course' => $course->id,
820
            'duedate' => $duedate2,
821
            'submissiondrafts' => 0,
822
            'assignsubmission_onlinetext_enabled' => 1,
823
        ]);
824
        $duedate3 = $clock->time() + WEEKSECS + DAYSECS;
825
        $assignment3 = $assignmentgenerator->create_instance([
826
            'course' => $course->id,
827
            'duedate' => $duedate3,
828
            'submissiondrafts' => 0,
829
            'assignsubmission_onlinetext_enabled' => 1,
830
        ]);
831
        // Create an assignment with a visibility restriction.
832
        $duedate4 = $clock->time() + WEEKSECS;
833
        $assignment4 = $assignmentgenerator->create_instance([
834
            'course' => $course->id,
835
            'duedate' => $duedate4,
836
            'visible' => 0,
837
            'submissiondrafts' => 0,
838
            'assignsubmission_onlinetext_enabled' => 1,
839
        ]);
840
        $clock->bump(5);
841
 
842
        // Run the tasks.
843
        $this->run_due_digest_notification_helper_tasks();
844
 
845
        // Get the notifications that should have been created during the adhoc task.
846
        $messages = $sink->get_messages_by_component('mod_assign');
847
        $this->assertCount(1, $messages);
848
 
849
        // Check the message for the expected assignments.
850
        $message = reset($messages);
851
        $this->assertStringContainsString($assignment1->name, $message->fullmessagehtml);
852
        $this->assertStringContainsString($assignment2->name, $message->fullmessagehtml);
853
        $this->assertStringNotContainsString($assignment3->name, $message->fullmessagehtml);
854
        $this->assertStringNotContainsString($assignment4->name, $message->fullmessagehtml);
855
 
856
        // Check the message contains the formatted due date.
857
        $formatteddate = userdate($duedate1, get_string('strftimedaydate', 'langconfig'));
858
        $this->assertStringContainsString($formatteddate, $message->fullmessagehtml);
859
 
860
        // Check the subject matches.
861
        $expectedsubject = get_string('assignmentduedigestsubject', 'mod_assign', $message->subject);
862
        $this->assertEquals($expectedsubject, $message->subject);
863
 
864
        // Clear sink.
865
        $sink->clear();
866
 
867
        // Let's modify the due date for one of the assignment.
868
        $updatedata = new \stdClass();
869
        $updatedata->id = $assignment1->id;
870
        $updatedata->duedate = $duedate1 + DAYSECS;
871
        $DB->update_record('assign', $updatedata);
872
 
873
        // Run the tasks again.
874
        $this->run_due_digest_notification_helper_tasks();
875
 
876
        // Check the message for the expected assignments.
877
        $messages = $sink->get_messages_by_component('mod_assign');
878
        $message = reset($messages);
879
        $this->assertStringNotContainsString($assignment1->name, $message->fullmessagehtml);
880
        $this->assertStringContainsString($assignment2->name, $message->fullmessagehtml);
881
        $this->assertStringNotContainsString($assignment3->name, $message->fullmessagehtml);
882
        $this->assertStringNotContainsString($assignment4->name, $message->fullmessagehtml);
883
 
884
        // Clear sink.
885
        $sink->clear();
886
 
887
        // This time, the user will submit an assignment.
888
        $assignmentgenerator->create_submission([
889
            'userid' => $user1->id,
890
            'cmid' => $assignment2->cmid,
891
            'status' => 'submitted',
892
            'timemodified' => $clock->time(),
893
            'onlinetext' => 'Some text',
894
            'assignsubmission_onlinetext_enabled' => 1,
895
        ]);
896
        $clock->bump(5);
897
 
898
        // Run the tasks again.
899
        $this->run_due_digest_notification_helper_tasks();
900
 
901
        // There are no assignments left to report, so no notification should have been sent.
902
        // There should only be one notifcation for submission.
903
        $messages = $sink->get_messages_by_component('mod_assign');
904
        $this->assertCount(1, $messages);
905
        $expectedsubject = get_string('submissionreceiptsmall', 'mod_assign', ['assignment' => $assignment2->name]);
906
        $this->assertEquals($expectedsubject, reset($messages)->subject);
907
 
908
        // Clear sink.
909
        $sink->clear();
910
    }
911
 
912
    /**
913
     * Test sending the assignment due digest notification to users in groups with restricted access.
914
     */
915
    public function test_send_due_digest_notification_to_users_in_groups(): void {
916
        global $DB;
917
        $this->resetAfterTest();
918
        $generator = $this->getDataGenerator();
919
        $clock = $this->mock_clock_with_incrementing();
920
        $sink = $this->redirectMessages();
921
        /** @var \mod_assign_generator $assignmentgenerator */
922
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
923
 
924
        // Create a course, users and enrol users.
925
        $course = $generator->create_course();
926
        $user1 = $generator->create_user();
927
        $user2 = $generator->create_user();
928
        $user3 = $generator->create_user();
929
        $generator->enrol_user($user1->id, $course->id, 'student');
930
        $generator->enrol_user($user2->id, $course->id, 'student');
931
        $generator->enrol_user($user3->id, $course->id, 'student');
932
 
933
        // Create groups and add users to groups.
934
        $group1 = $generator->create_group(['courseid' => $course->id]);
935
        $group2 = $generator->create_group(['courseid' => $course->id]);
936
        $generator->create_group_member(['groupid' => $group1->id, 'userid' => $user1->id]);
937
        $generator->create_group_member(['groupid' => $group2->id, 'userid' => $user2->id]);
938
 
939
        // Create assignments.
940
        $assignment1 = $assignmentgenerator->create_instance([
941
            'course' => $course->id,
942
            'duedate' => $clock->time() + WEEKSECS,
943
            'submissiondrafts' => 0,
944
            'assignsubmission_onlinetext_enabled' => 1,
945
        ]);
946
        $assignment2 = $assignmentgenerator->create_instance([
947
            'course' => $course->id,
948
            'duedate' => $clock->time() + WEEKSECS,
949
            'submissiondrafts' => 0,
950
            'assignsubmission_onlinetext_enabled' => 1,
951
        ]);
952
        $assignment3 = $assignmentgenerator->create_instance([
953
            'course' => $course->id,
954
            'duedate' => $clock->time() + WEEKSECS,
955
            'submissiondrafts' => 0,
956
            'assignsubmission_onlinetext_enabled' => 1,
957
        ]);
958
 
959
        // Set restricted access for assignment1 and assignment2 to groups.
960
        $availability = [
961
            'op' => '&',
962
            'showc' => [true],
963
            'c' => [
964
                [
965
                    'type' => 'group',
966
                    'id' => (int) $group1->id,
967
                ],
968
            ],
969
        ];
970
        $cm = get_coursemodule_from_instance('assign', $assignment1->id, $course->id);
971
        $DB->set_field('course_modules', 'availability', json_encode($availability), ['id' => $cm->id]);
972
 
973
        $availability = [
974
            'op' => '&',
975
            'showc' => [true],
976
            'c' => [
977
                [
978
                    'type' => 'group',
979
                    'id' => (int) $group2->id,
980
                ],
981
            ],
982
        ];
983
        $cm = get_coursemodule_from_instance('assign', $assignment2->id, $course->id);
984
        $DB->set_field('course_modules', 'availability', json_encode($availability), ['id' => $cm->id]);
985
 
986
        // Rebuild course cache to apply changes.
987
        rebuild_course_cache($course->id, true);
988
 
989
        // Run the tasks. We want to run all the adhoc tasks at the same time. So we will use the normal task runner.
990
        $this->execute_task('\mod_assign\task\queue_all_assignment_due_digest_notification_tasks');
991
        // Execute the remaining ad-hoc backup task.
992
        $this->start_output_buffering();
993
        $this->runAdhocTasks('\mod_assign\task\send_assignment_due_digest_notification_to_user');
994
        $this->stop_output_buffering();
995
        $messages = $sink->get_messages_by_component('mod_assign');
996
 
997
        // Process the messages.
998
        $processedmessages = [];
999
        foreach ($messages as $message) {
1000
            $processedmessages[$message->useridto] = $message;
1001
        }
1002
 
1003
        // Verify the messages.
1004
        $this->assertCount(3, $processedmessages);
1005
        // User1 should receive a message for assignment1 and assignment3.
1006
        $this->assertArrayHasKey($user1->id, $processedmessages);
1007
        $this->assertStringContainsString($assignment1->name, $processedmessages[$user1->id]->fullmessagehtml);
1008
        $this->assertStringContainsString($assignment3->name, $processedmessages[$user1->id]->fullmessagehtml);
1009
        $this->assertStringNotContainsString($assignment2->name, $processedmessages[$user1->id]->fullmessagehtml);
1010
        // User2 should receive a message for assignment2 and assignment3.
1011
        $this->assertArrayHasKey($user2->id, $processedmessages);
1012
        $this->assertStringContainsString($assignment2->name, $processedmessages[$user2->id]->fullmessagehtml);
1013
        $this->assertStringContainsString($assignment3->name, $processedmessages[$user2->id]->fullmessagehtml);
1014
        $this->assertStringNotContainsString($assignment1->name, $processedmessages[$user2->id]->fullmessagehtml);
1015
        // User3 should receive a message for assignment3 only.
1016
        $this->assertArrayHasKey($user3->id, $processedmessages);
1017
        $this->assertStringContainsString($assignment3->name, $processedmessages[$user3->id]->fullmessagehtml);
1018
        $this->assertStringNotContainsString($assignment1->name, $processedmessages[$user3->id]->fullmessagehtml);
1019
        $this->assertStringNotContainsString($assignment2->name, $processedmessages[$user3->id]->fullmessagehtml);
1020
    }
1021
 
1022
    /**
1023
     * Test sending the assignment notification to a user with a list of the submitted files.
1024
     */
1025
    public function test_send_notification_with_summary_to_user(): void {
1026
        $this->resetAfterTest();
1027
        $generator = $this->getDataGenerator();
1028
        $sink = $this->redirectMessages();
1029
 
1030
        // Create a course and enrol a user.
1031
        $course = $generator->create_course(['shortname' => 'A100']);
1032
        $user1 = $generator->create_user();
1033
        $generator->enrol_user($user1->id, $course->id, 'student');
1034
 
1035
        /** @var \mod_assign_generator $assignmentgenerator */
1036
        $assignmentgenerator = $generator->get_plugin_generator('mod_assign');
1037
 
1038
        // Create activity.
1039
        $assignment = $assignmentgenerator->create_instance([
1040
            'course' => $course->id,
1041
            'name' => 'Assignment 1',
1042
            'submissiondrafts' => 0,
1043
            'assignsubmission_file_enabled' => 1,
1044
            'assignsubmission_file_maxfiles' => 12,
1045
            'assignsubmission_file_maxsizebytes' => 1024 * 1024,
1046
            'assignsubmission_onlinetext_enabled' => 1,
1047
        ]);
1048
 
1049
        $filename1 = 'submissionsample01.txt';
1050
        $filename2 = 'submissionsample02.txt';
1051
        $files = [
1052
            "mod/assign/tests/fixtures/" . $filename1,
1053
            "mod/assign/tests/fixtures/" . $filename2,
1054
        ];
1055
 
1056
        // Generate submissions.
1057
        $assignmentgenerator->create_submission([
1058
            'userid' => $user1->id,
1059
            'cmid' => $assignment->cmid,
1060
            'status' => 'submitted',
1061
            'file' => implode(',', $files),
1062
            'onlinetext' => 'Some text example',
1063
        ]);
1064
 
1065
        // Get the notifications.
1066
        $messages = $sink->get_messages_by_component('mod_assign');
1067
        $this->assertCount(1, $messages);
1068
        $message = reset($messages);
1069
 
1070
        // Check the subject line and short message.
1071
        $this->assertEquals('Assignment submission confirmation - Assignment 1', $message->subject);
1072
        $this->assertEquals('Assignment submission confirmation - Assignment 1', $message->smallmessage);
1073
 
1074
        // Check the plain text message.
1075
        $this->assertEquals('A100 -> Assignment -> Assignment 1
1076
---------------------------------------------------------------------
1077
You have submitted an assignment submission for \'Assignment 1\'.
1078
 
1079
You can see the status of your assignment submission:
1080
 
1081
    https://www.example.com/moodle/mod/assign/view.php?id=' . $assignment->cmid . '
1082
 
1083
Your submission contains:
1084
 
1085
Online text
1086
(3 words)
1087
 
1088
File submissions
1089
* submissionsample01.txt (42 bytes)
1090
* submissionsample02.txt (42 bytes)
1091
 
1092
 
1093
---------------------------------------------------------------------
1094
', $message->fullmessage);
1095
 
1096
        $expectedfragments = [
1097
            '<p>Your assignment submission for \'Assignment 1\' has been submitted.</p>',
1098
            '<p>You can view your submission and check its status on the <a href="' .
1099
                'https://www.example.com/moodle/mod/assign/view.php?id=' .
1100
                $assignment->cmid . '">assignment page</a>.</p>',
1101
            '<h2>Your submission contains:</h2>',
1102
            '<h3>Online text</h3>',
1103
            '<p>(3 words)</p>',
1104
            '<h3>File submissions</h3>',
1105
            '<li>submissionsample01.txt (42 bytes)</li>',
1106
            '<li>submissionsample02.txt (42 bytes)</li>',
1107
        ];
1108
        foreach ($expectedfragments as $html) {
1109
            $this->assertStringContainsString($html, $message->fullmessagehtml);
1110
        }
1111
 
1112
        // Clear sink.
1113
        $sink->clear();
1114
    }
1115
}