Autoría | Ultima modificación | Ver Log |
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_assign;
use core\task\task_trait;
use mod_assign\task\queue_assignment_due_digest_notification_tasks_for_users;
/**
* Test class for the assignment notification_helper.
*
* @package mod_assign
* @category test
* @copyright 2024 David Woloszyn <david.woloszyn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @covers \mod_assign\notification_helper
*/
final class notification_helper_test extends \advanced_testcase {
use task_trait;
/**
* Run all the tasks related to the 'due soon' notifications.
*/
protected function run_due_soon_notification_helper_tasks(): void {
$task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_due_soon_notification_tasks::class);
$task->execute();
$clock = \core\di::get(\core\clock::class);
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
if ($adhoctask) {
$this->assertInstanceOf(\mod_assign\task\queue_assignment_due_soon_notification_tasks_for_users::class, $adhoctask);
$adhoctask->execute();
\core\task\manager::adhoc_task_complete($adhoctask);
}
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
if ($adhoctask) {
$this->assertInstanceOf(\mod_assign\task\send_assignment_due_soon_notification_to_user::class, $adhoctask);
$adhoctask->execute();
\core\task\manager::adhoc_task_complete($adhoctask);
}
}
/**
* Test getting due soon assignments.
*/
public function test_get_due_soon_assignments(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$helper = \core\di::get(notification_helper::class);
$clock = $this->mock_clock_with_frozen();
// Create an assignment with a due date < 48 hours.
$course = $generator->create_course();
$generator->create_module('assign', ['course' => $course->id, 'duedate' => $clock->time() + DAYSECS]);
// Check that we have a result returned.
$result = $helper::get_due_soon_assignments();
$this->assertTrue($result->valid());
$result->close();
// Time travel 3 days into the future. We should have no assignments in range.
$clock->bump(DAYSECS * 3);
$result = $helper::get_due_soon_assignments();
$this->assertFalse($result->valid());
$result->close();
}
/**
* Test getting users within an assignment that have a due date soon.
*/
public function test_get_due_soon_users_within_assignment(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$helper = \core\di::get(notification_helper::class);
$clock = $this->mock_clock_with_frozen();
// Create a course and enrol some users.
$course = $generator->create_course();
$user1 = $generator->create_user();
$user2 = $generator->create_user();
$user3 = $generator->create_user();
$user4 = $generator->create_user();
$user5 = $generator->create_user();
$user6 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
$generator->enrol_user($user2->id, $course->id, 'student');
$generator->enrol_user($user3->id, $course->id, 'student');
$generator->enrol_user($user4->id, $course->id, 'student');
$generator->enrol_user($user5->id, $course->id, 'student');
$generator->enrol_user($user6->id, $course->id, 'teacher');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create an assignment with a due date < 48 hours.
$duedate = $clock->time() + DAYSECS;
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
// User1 will have a user override, giving them an extra 1 hour for 'duedate'.
$userduedate = $duedate + HOURSECS;
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'userid' => $user1->id,
'duedate' => $userduedate,
]);
// User2 and user3 will have a group override, giving them an extra 2 hours for 'duedate'.
$groupduedate = $duedate + (HOURSECS * 2);
$group = $generator->create_group(['courseid' => $course->id]);
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'groupid' => $group->id,
'duedate' => $groupduedate,
]);
// User4 will have a user override of one extra week, excluding them from the results.
$userduedate = $duedate + WEEKSECS;
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'userid' => $user4->id,
'duedate' => $userduedate,
]);
$assignmentgenerator->create_submission([
'userid' => $user5->id,
'cmid' => $assignment->cmid,
'status' => 'submitted',
'timemodified' => $clock->time(),
'onlinetext' => 'Some text',
'assignsubmission_onlinetext_enabled' => 1,
]);
// There should be 3 users with the teacher excluded.
$users = $helper::get_users_within_assignment($assignment->id, $helper::TYPE_DUE_SOON);
$this->assertCount(3, $users);
}
/**
* Test sending the assignment due soon notification to a user.
*/
public function test_send_due_soon_notification_to_user(): void {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$helper = \core\di::get(notification_helper::class);
$clock = $this->mock_clock_with_frozen();
$sink = $this->redirectMessages();
// Create a course and enrol a user.
$course = $generator->create_course();
$user1 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
// Suspended user, should not receive notification.
$user2 = $generator->create_user(['suspended' => 1]);
$generator->enrol_user($user2->id, $course->id, 'student');
// Nologin user, should not receive notification.
$user3 = $generator->create_user(['auth' => 'nologin']);
$generator->enrol_user($user3->id, $course->id, 'student');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create an assignment with a due date < 48 hours.
$duedate = $clock->time() + DAYSECS;
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$clock->bump(5);
// Run the tasks.
$this->run_due_soon_notification_helper_tasks();
// Get the assignment object.
[$course, $assigncm] = get_course_and_cm_from_instance($assignment->id, 'assign');
$cmcontext = \context_module::instance($assigncm->id);
$assignmentobj = new \assign($cmcontext, $assigncm, $course);
$duedate = $assignmentobj->get_instance($user1->id)->duedate;
// Get the notifications that should have been created during the adhoc task.
$messages = $sink->get_messages_by_component('mod_assign');
$this->assertCount(1, $messages);
// Check the subject matches.
$message = reset($messages);
$stringparams = [
'duedate' => userdate($duedate),
'assignmentname' => $assignment->name,
];
$expectedsubject = get_string('assignmentduesoonsubject', 'mod_assign', $stringparams);
$this->assertEquals($expectedsubject, $message->subject);
// Clear sink.
$sink->clear();
// Run the tasks again.
$this->run_due_soon_notification_helper_tasks();
// There should be no notification because nothing has changed.
$this->assertEmpty($sink->get_messages_by_component('mod_assign'));
// Let's modify the 'duedate' for the assignment (it will still be within the 48 hour range).
$updatedata = new \stdClass();
$updatedata->id = $assignment->id;
$updatedata->duedate = $duedate + HOURSECS;
$DB->update_record('assign', $updatedata);
// Run the tasks again.
$this->run_due_soon_notification_helper_tasks();
// There should be a new notification because the 'duedate' has been updated.
$messages = $sink->get_messages_by_component('mod_assign');
$this->assertCount(1, $messages);
$message = reset($messages);
$stringparams = [
'duedate' => userdate($updatedata->duedate),
'assignmentname' => $assignment->name,
];
$expectedsubject = get_string('assignmentduesoonsubject', 'mod_assign', $stringparams);
$this->assertEquals($expectedsubject, $message->subject);
// Clear sink.
$sink->clear();
// Let's update the assignment visibility.
$DB->set_field('course_modules', 'visible', 0, ['id' => $assigncm->id]);
// Update the duedate to force a new notification.
$updatedata = new \stdClass();
$updatedata->id = $assignment->id;
$updatedata->duedate = $duedate + HOURSECS * 3;
$DB->update_record('assign', $updatedata);
// Run the tasks again.
$this->run_due_soon_notification_helper_tasks();
// There should not be a new notification the assignmnet is not visible.
$this->assertEmpty($sink->get_messages_by_component('mod_assign'));
// Update the visibility back to visible before the next assert.
$DB->set_field('course_modules', 'visible', 1, ['id' => $assigncm->id]);
// Clear sink.
$sink->clear();
// Let's modify the 'duedate' one more time.
$updatedata = new \stdClass();
$updatedata->id = $assignment->id;
$updatedata->duedate = $duedate + (HOURSECS * 2);
$DB->update_record('assign', $updatedata);
// This time, the user will submit the assignment.
$assignmentgenerator->create_submission([
'userid' => $user1->id,
'cmid' => $assignment->cmid,
'status' => 'submitted',
'timemodified' => $clock->time(),
'onlinetext' => 'Some text',
]);
$clock->bump(5);
// Run the tasks again.
$this->run_due_soon_notification_helper_tasks();
// There should only be one notifcation for submission (not for due soon).
$messages = $sink->get_messages_by_component('mod_assign');
$this->assertCount(1, $messages);
$expectedsubject = get_string('submissionreceiptsmall', 'mod_assign', ['assignment' => $assignment->name]);
$this->assertEquals($expectedsubject, reset($messages)->subject);
// Clear sink.
$sink->clear();
}
/**
* Test that we do not fail on deleted assignments with due soon notifications to a user.
*/
public function test_not_to_fail_on_deleted_assigment_with_due_soon_notifications_to_user(): void {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$clock = $this->mock_clock_with_frozen();
// Create a course and enrol a user.
$course = $generator->create_course();
$user1 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create an assignment with a due date < 48 hours.
$duedate = $clock->time() + DAYSECS;
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$clock->bump(5);
// Run the scheduled and ad-hoc task to queue the notifications.
$task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_due_soon_notification_tasks::class);
$task->execute();
$clock->bump(5);
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
$this->assertInstanceOf(\mod_assign\task\queue_assignment_due_soon_notification_tasks_for_users::class, $adhoctask);
$adhoctask->execute();
\core\task\manager::adhoc_task_complete($adhoctask);
// Delete the assignment.
$DB->delete_records('assign', ['id' => $assignment->id]);
// Try to run the ad-hoc task to send the notifications.
$clock->bump(5);
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
$this->assertInstanceOf(\mod_assign\task\send_assignment_due_soon_notification_to_user::class, $adhoctask);
ob_start();
$adhoctask->execute();
$output = ob_get_clean();
\core\task\manager::adhoc_task_complete($adhoctask);
// The ad-hoc task should be deleted.
$this->assertNull(\core\task\manager::get_next_adhoc_task($clock->time()));
$this->assertStringContainsString(
needle: "No notification send as the assignment $assignment->id can no longer be found in the database.",
haystack: $output
);
}
/**
* Run all the tasks related to the 'overdue' notifications.
*/
protected function run_overdue_notification_helper_tasks(): void {
$task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_overdue_notification_tasks::class);
$task->execute();
$clock = \core\di::get(\core\clock::class);
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
if ($adhoctask) {
$this->assertInstanceOf(\mod_assign\task\queue_assignment_overdue_notification_tasks_for_users::class, $adhoctask);
$adhoctask->execute();
\core\task\manager::adhoc_task_complete($adhoctask);
}
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
if ($adhoctask) {
$this->assertInstanceOf(\mod_assign\task\send_assignment_overdue_notification_to_user::class, $adhoctask);
$adhoctask->execute();
\core\task\manager::adhoc_task_complete($adhoctask);
}
}
/**
* Test getting overdue assignments.
*/
public function test_get_overdue_assignments(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$helper = \core\di::get(notification_helper::class);
$clock = $this->mock_clock_with_frozen();
// Create an overdue assignment.
$course = $generator->create_course();
$generator->create_module('assign', ['course' => $course->id, 'duedate' => $clock->time() - HOURSECS]);
// Check that we have a result returned.
$result = $helper::get_overdue_assignments();
$this->assertTrue($result->valid());
$result->close();
// Time travel 2 hours into the future.
// We should have no assignments found as we are only getting overdue assignments within a 2 hour window.
$clock->bump(HOURSECS * 2);
$result = $helper::get_overdue_assignments();
$this->assertFalse($result->valid());
$result->close();
}
/**
* Test getting users within an assignment that is overdue.
*/
public function test_get_overdue_users_within_assignment(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$helper = \core\di::get(notification_helper::class);
$clock = $this->mock_clock_with_frozen();
// Create a course and enrol some users.
$course = $generator->create_course();
$user1 = $generator->create_and_enrol($course, 'student');
$user2 = $generator->create_and_enrol($course, 'student');
$user3 = $generator->create_and_enrol($course, 'student');
$user4 = $generator->create_and_enrol($course, 'student');
$user5 = $generator->create_and_enrol($course, 'student');
$user6 = $generator->create_and_enrol($course, 'student');
$user7 = $generator->create_and_enrol($course, 'teacher');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create an overdue assignment.
$duedate = $clock->time() - HOURSECS;
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
// User1 will have a user override, giving them an extra minute for 'duedate'.
$userduedate = $duedate + MINSECS;
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'userid' => $user1->id,
'duedate' => $userduedate,
]);
// User2 and user3 will have a group override, giving them an extra minute for 'duedate'.
$groupduedate = $duedate + MINSECS;
$group = $generator->create_group(['courseid' => $course->id]);
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'groupid' => $group->id,
'duedate' => $groupduedate,
]);
// User4 will have a user override of one extra week, excluding them from the results.
$userduedate = $duedate + WEEKSECS;
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'userid' => $user4->id,
'duedate' => $userduedate,
]);
// User5 will submit the assignment, excluding them from the results.
$assignmentgenerator->create_submission([
'userid' => $user5->id,
'cmid' => $assignment->cmid,
'status' => 'submitted',
'timemodified' => $clock->time(),
'onlinetext' => 'Some text',
'assignsubmission_onlinetext_enabled' => 1,
]);
// User6 will have a cut-off date override that has already lapsed, excluding them from the results.
$usercutoffdate = $clock->time() - MINSECS;
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'userid' => $user6->id,
'cutoffdate' => $usercutoffdate,
]);
// There should be 3 users with the teacher excluded.
$users = $helper::get_users_within_assignment($assignment->id, $helper::TYPE_OVERDUE);
$this->assertCount(3, $users);
$this->assertArrayHasKey($user1->id, $users);
$this->assertArrayHasKey($user2->id, $users);
$this->assertArrayHasKey($user3->id, $users);
}
/**
* Test sending the assignment overdue notification to a user.
*/
public function test_send_overdue_notification_to_user(): void {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$clock = $this->mock_clock_with_frozen();
$sink = $this->redirectMessages();
// Create a course and enrol a user.
$course = $generator->create_course();
$user1 = $generator->create_and_enrol($course, 'student');
// Suspended user, should not receive notification.
$user2 = $generator->create_user(['suspended' => 1]);
$generator->enrol_user($user2->id, $course->id, 'student');
// Nologin user, should not receive notification.
$user3 = $generator->create_user(['auth' => 'nologin']);
$generator->enrol_user($user3->id, $course->id, 'student');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create an assignment that is overdue.
$duedate = $clock->time() - HOURSECS;
$cutoffdate = $clock->time() + DAYSECS;
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate,
'cutoffdate' => $cutoffdate,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$clock->bump(5);
// Run the tasks.
$this->run_overdue_notification_helper_tasks();
// Get the notifications that should have been created during the adhoc task.
$this->assertCount(1, $sink->get_messages());
// Check the subject matches.
$messages = $sink->get_messages_by_component('mod_assign');
$message = reset($messages);
$expectedsubject = get_string('assignmentoverduesubject', 'mod_assign', ['assignmentname' => $assignment->name]);
$this->assertEquals($expectedsubject, $message->subject);
// Clear sink.
$sink->clear();
// Run the tasks again.
$this->run_overdue_notification_helper_tasks();
// There should be no notification because nothing has changed.
$this->assertEmpty($sink->get_messages_by_component('mod_assign'));
// Let's modify the 'duedate' for the assignment (it will still be overdue).
$updatedata = new \stdClass();
$updatedata->id = $assignment->id;
$updatedata->duedate = $duedate + MINSECS;
$DB->update_record('assign', $updatedata);
// Clear sink.
$sink->clear();
// Run the tasks again.
$this->run_overdue_notification_helper_tasks();
// There should be a new notification because the 'duedate' has been updated.
$messages = $sink->get_messages_by_component('mod_assign');
$this->assertCount(1, $messages);
$message = reset($messages);
$expectedsubject = get_string('assignmentoverduesubject', 'mod_assign', ['assignmentname' => $assignment->name]);
$this->assertEquals($expectedsubject, $message->subject);
// Let's modify the 'cut-off date'.
$updatedata = new \stdClass();
$updatedata->id = $assignment->id;
$updatedata->cutoffdate = $cutoffdate + MINSECS;
$DB->update_record('assign', $updatedata);
// Clear sink.
$sink->clear();
// Run the tasks again.
$this->run_overdue_notification_helper_tasks();
// There should be a new notification because the 'cut-off date' has been updated.
$messages = $sink->get_messages_by_component('mod_assign');
$message = reset($messages);
$expectedsubject = get_string('assignmentoverduesubject', 'mod_assign', ['assignmentname' => $assignment->name]);
$this->assertEquals($expectedsubject, $message->subject);
// Let's update the assignment visibility.
$cm = get_coursemodule_from_instance('assign', $assignment->id, $course->id);
$DB->set_field('course_modules', 'visible', 0, ['id' => $cm->id]);
// Update the duedate to force a new notification.
$updatedata = new \stdClass();
$updatedata->id = $assignment->id;
$updatedata->duedate = $duedate + (MINSECS * 3);
$DB->update_record('assign', $updatedata);
// Clear sink.
$sink->clear();
// Run the tasks again.
$this->run_overdue_notification_helper_tasks();
// There should not be a new notification because the assignment is not visible.
$this->assertEmpty($sink->get_messages_by_component('mod_assign'));
// Update the visibility back to visible before the next assert.
$DB->set_field('course_modules', 'visible', 1, ['id' => $cm->id]);
// Let's modify the 'duedate' one more time.
$updatedata = new \stdClass();
$updatedata->id = $assignment->id;
$updatedata->duedate = $duedate + (MINSECS * 2);
$DB->update_record('assign', $updatedata);
// This time, the user will submit the assignment.
$assignmentgenerator->create_submission([
'userid' => $user1->id,
'cmid' => $assignment->cmid,
'status' => 'submitted',
'timemodified' => $clock->time(),
'onlinetext' => 'Some text',
'assignsubmission_onlinetext_enabled' => 1,
]);
// Clear sink.
$sink->clear();
// Run the tasks again.
$this->run_overdue_notification_helper_tasks();
// No new notification should have been sent.
$this->assertEmpty($sink->get_messages_by_component('mod_assign'));
}
/**
* Test that we do not fail on deleted assignments with overdue notifications to a user.
*/
public function test_not_to_fail_on_deleted_assigment_with_overdue_notifications_to_user(): void {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$clock = $this->mock_clock_with_frozen();
// Create a course and enrol a user.
$course = $generator->create_course();
$user1 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create an assignment that is overdue.
$duedate = $clock->time() - HOURSECS;
$cutoffdate = $clock->time() + DAYSECS;
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate,
'cutoffdate' => $cutoffdate,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$clock->bump(5);
// Run the scheduled and ad-hoc task to queue the notifications.
$task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_overdue_notification_tasks::class);
$task->execute();
$clock->bump(5);
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
$this->assertInstanceOf(\mod_assign\task\queue_assignment_overdue_notification_tasks_for_users::class, $adhoctask);
$adhoctask->execute();
\core\task\manager::adhoc_task_complete($adhoctask);
// Delete the assignment.
$DB->delete_records('assign', ['id' => $assignment->id]);
// Try to run the ad-hoc task to send the notifications.
$clock->bump(5);
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
$this->assertInstanceOf(\mod_assign\task\send_assignment_overdue_notification_to_user::class, $adhoctask);
ob_start();
$adhoctask->execute();
$output = ob_get_clean();
\core\task\manager::adhoc_task_complete($adhoctask);
// The ad-hoc task should be deleted.
$this->assertNull(\core\task\manager::get_next_adhoc_task($clock->time()));
$this->assertStringContainsString(
needle: "No notification send as the assignment $assignment->id can no longer be found in the database.",
haystack: $output
);
}
/**
* Run all the tasks related to the due digest notifications.
*/
protected function run_due_digest_notification_helper_tasks(): void {
$task = \core\task\manager::get_scheduled_task(\mod_assign\task\queue_all_assignment_due_digest_notification_tasks::class);
$task->execute();
$clock = \core\di::get(\core\clock::class);
$adhoctask = \core\task\manager::get_next_adhoc_task($clock->time());
if ($adhoctask) {
$this->assertInstanceOf(\mod_assign\task\send_assignment_due_digest_notification_to_user::class, $adhoctask);
$adhoctask->execute();
\core\task\manager::adhoc_task_complete($adhoctask);
}
}
/**
* Test getting users for the due digest.
*/
public function test_get_users_for_due_digest(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$helper = \core\di::get(notification_helper::class);
$clock = $this->mock_clock_with_frozen();
// Create a course and enrol some users.
$course = $generator->create_course();
$user1 = $generator->create_user();
$user2 = $generator->create_user();
$user3 = $generator->create_user();
$user4 = $generator->create_user();
$user5 = $generator->create_user();
$user6 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
$generator->enrol_user($user2->id, $course->id, 'student');
$generator->enrol_user($user3->id, $course->id, 'student');
$generator->enrol_user($user4->id, $course->id, 'student');
$generator->enrol_user($user5->id, $course->id, 'student');
$generator->enrol_user($user6->id, $course->id, 'teacher');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create an assignment with a due date 7 days from now (the due digest range).
$duedate = $clock->time() + WEEKSECS;
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
// User1 will have a user override, giving them an extra 1 day for 'duedate', excluding them from the results.
$userduedate = $duedate + DAYSECS;
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'userid' => $user1->id,
'duedate' => $userduedate,
]);
// User2 and user3 will have a group override, giving them an extra 2 days for 'duedate', excluding them from the results.
$groupduedate = $duedate + (DAYSECS * 2);
$group = $generator->create_group(['courseid' => $course->id]);
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user2->id]);
$generator->create_group_member(['groupid' => $group->id, 'userid' => $user3->id]);
$assignmentgenerator->create_override([
'assignid' => $assignment->id,
'groupid' => $group->id,
'duedate' => $groupduedate,
]);
// User4 will submit the assignment, excluding them from the results.
$assignmentgenerator->create_submission([
'userid' => $user4->id,
'cmid' => $assignment->cmid,
'status' => 'submitted',
'timemodified' => $clock->time(),
'onlinetext' => 'Some text',
'assignsubmission_onlinetext_enabled' => 1,
]);
// There should be 1 user with the teacher excluded.
$users = $helper::get_users_within_assignment($assignment->id, $helper::TYPE_DUE_DIGEST);
$this->assertCount(1, $users);
}
/**
* Test sending the assignment due digest notification to a user.
*/
public function test_send_due_digest_notification_to_user(): void {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$clock = $this->mock_clock_with_frozen();
$sink = $this->redirectMessages();
// Create a course and enrol a user.
$course = $generator->create_course();
$user1 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
// Suspended user, should not receive notification.
$user2 = $generator->create_user(['suspended' => 1]);
$generator->enrol_user($user2->id, $course->id, 'student');
// Nologin user, should not receive notification.
$user3 = $generator->create_user(['auth' => 'nologin']);
$generator->enrol_user($user3->id, $course->id, 'student');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create a few assignments with different due dates.
$duedate1 = $clock->time() + WEEKSECS;
$assignment1 = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate1,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$duedate2 = $clock->time() + WEEKSECS;
$assignment2 = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate2,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$duedate3 = $clock->time() + WEEKSECS + DAYSECS;
$assignment3 = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate3,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
// Create an assignment with a visibility restriction.
$duedate4 = $clock->time() + WEEKSECS;
$assignment4 = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $duedate4,
'visible' => 0,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$clock->bump(5);
// Run the tasks.
$this->run_due_digest_notification_helper_tasks();
// Get the notifications that should have been created during the adhoc task.
$messages = $sink->get_messages_by_component('mod_assign');
$this->assertCount(1, $messages);
// Check the message for the expected assignments.
$message = reset($messages);
$this->assertStringContainsString($assignment1->name, $message->fullmessagehtml);
$this->assertStringContainsString($assignment2->name, $message->fullmessagehtml);
$this->assertStringNotContainsString($assignment3->name, $message->fullmessagehtml);
$this->assertStringNotContainsString($assignment4->name, $message->fullmessagehtml);
// Check the message contains the formatted due date.
$formatteddate = userdate($duedate1, get_string('strftimedaydate', 'langconfig'));
$this->assertStringContainsString($formatteddate, $message->fullmessagehtml);
// Check the subject matches.
$expectedsubject = get_string('assignmentduedigestsubject', 'mod_assign', $message->subject);
$this->assertEquals($expectedsubject, $message->subject);
// Clear sink.
$sink->clear();
// Let's modify the due date for one of the assignment.
$updatedata = new \stdClass();
$updatedata->id = $assignment1->id;
$updatedata->duedate = $duedate1 + DAYSECS;
$DB->update_record('assign', $updatedata);
// Run the tasks again.
$this->run_due_digest_notification_helper_tasks();
// Check the message for the expected assignments.
$messages = $sink->get_messages_by_component('mod_assign');
$message = reset($messages);
$this->assertStringNotContainsString($assignment1->name, $message->fullmessagehtml);
$this->assertStringContainsString($assignment2->name, $message->fullmessagehtml);
$this->assertStringNotContainsString($assignment3->name, $message->fullmessagehtml);
$this->assertStringNotContainsString($assignment4->name, $message->fullmessagehtml);
// Clear sink.
$sink->clear();
// This time, the user will submit an assignment.
$assignmentgenerator->create_submission([
'userid' => $user1->id,
'cmid' => $assignment2->cmid,
'status' => 'submitted',
'timemodified' => $clock->time(),
'onlinetext' => 'Some text',
'assignsubmission_onlinetext_enabled' => 1,
]);
$clock->bump(5);
// Run the tasks again.
$this->run_due_digest_notification_helper_tasks();
// There are no assignments left to report, so no notification should have been sent.
// There should only be one notifcation for submission.
$messages = $sink->get_messages_by_component('mod_assign');
$this->assertCount(1, $messages);
$expectedsubject = get_string('submissionreceiptsmall', 'mod_assign', ['assignment' => $assignment2->name]);
$this->assertEquals($expectedsubject, reset($messages)->subject);
// Clear sink.
$sink->clear();
}
/**
* Test sending the assignment due digest notification to users in groups with restricted access.
*/
public function test_send_due_digest_notification_to_users_in_groups(): void {
global $DB;
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$clock = $this->mock_clock_with_incrementing();
$sink = $this->redirectMessages();
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create a course, users and enrol users.
$course = $generator->create_course();
$user1 = $generator->create_user();
$user2 = $generator->create_user();
$user3 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
$generator->enrol_user($user2->id, $course->id, 'student');
$generator->enrol_user($user3->id, $course->id, 'student');
// Create groups and add users to groups.
$group1 = $generator->create_group(['courseid' => $course->id]);
$group2 = $generator->create_group(['courseid' => $course->id]);
$generator->create_group_member(['groupid' => $group1->id, 'userid' => $user1->id]);
$generator->create_group_member(['groupid' => $group2->id, 'userid' => $user2->id]);
// Create assignments.
$assignment1 = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $clock->time() + WEEKSECS,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$assignment2 = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $clock->time() + WEEKSECS,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
$assignment3 = $assignmentgenerator->create_instance([
'course' => $course->id,
'duedate' => $clock->time() + WEEKSECS,
'submissiondrafts' => 0,
'assignsubmission_onlinetext_enabled' => 1,
]);
// Set restricted access for assignment1 and assignment2 to groups.
$availability = [
'op' => '&',
'showc' => [true],
'c' => [
[
'type' => 'group',
'id' => (int) $group1->id,
],
],
];
$cm = get_coursemodule_from_instance('assign', $assignment1->id, $course->id);
$DB->set_field('course_modules', 'availability', json_encode($availability), ['id' => $cm->id]);
$availability = [
'op' => '&',
'showc' => [true],
'c' => [
[
'type' => 'group',
'id' => (int) $group2->id,
],
],
];
$cm = get_coursemodule_from_instance('assign', $assignment2->id, $course->id);
$DB->set_field('course_modules', 'availability', json_encode($availability), ['id' => $cm->id]);
// Rebuild course cache to apply changes.
rebuild_course_cache($course->id, true);
// Run the tasks. We want to run all the adhoc tasks at the same time. So we will use the normal task runner.
$this->execute_task('\mod_assign\task\queue_all_assignment_due_digest_notification_tasks');
// Execute the remaining ad-hoc backup task.
$this->start_output_buffering();
$this->runAdhocTasks('\mod_assign\task\send_assignment_due_digest_notification_to_user');
$this->stop_output_buffering();
$messages = $sink->get_messages_by_component('mod_assign');
// Process the messages.
$processedmessages = [];
foreach ($messages as $message) {
$processedmessages[$message->useridto] = $message;
}
// Verify the messages.
$this->assertCount(3, $processedmessages);
// User1 should receive a message for assignment1 and assignment3.
$this->assertArrayHasKey($user1->id, $processedmessages);
$this->assertStringContainsString($assignment1->name, $processedmessages[$user1->id]->fullmessagehtml);
$this->assertStringContainsString($assignment3->name, $processedmessages[$user1->id]->fullmessagehtml);
$this->assertStringNotContainsString($assignment2->name, $processedmessages[$user1->id]->fullmessagehtml);
// User2 should receive a message for assignment2 and assignment3.
$this->assertArrayHasKey($user2->id, $processedmessages);
$this->assertStringContainsString($assignment2->name, $processedmessages[$user2->id]->fullmessagehtml);
$this->assertStringContainsString($assignment3->name, $processedmessages[$user2->id]->fullmessagehtml);
$this->assertStringNotContainsString($assignment1->name, $processedmessages[$user2->id]->fullmessagehtml);
// User3 should receive a message for assignment3 only.
$this->assertArrayHasKey($user3->id, $processedmessages);
$this->assertStringContainsString($assignment3->name, $processedmessages[$user3->id]->fullmessagehtml);
$this->assertStringNotContainsString($assignment1->name, $processedmessages[$user3->id]->fullmessagehtml);
$this->assertStringNotContainsString($assignment2->name, $processedmessages[$user3->id]->fullmessagehtml);
}
/**
* Test sending the assignment notification to a user with a list of the submitted files.
*/
public function test_send_notification_with_summary_to_user(): void {
$this->resetAfterTest();
$generator = $this->getDataGenerator();
$sink = $this->redirectMessages();
// Create a course and enrol a user.
$course = $generator->create_course(['shortname' => 'A100']);
$user1 = $generator->create_user();
$generator->enrol_user($user1->id, $course->id, 'student');
/** @var \mod_assign_generator $assignmentgenerator */
$assignmentgenerator = $generator->get_plugin_generator('mod_assign');
// Create activity.
$assignment = $assignmentgenerator->create_instance([
'course' => $course->id,
'name' => 'Assignment 1',
'submissiondrafts' => 0,
'assignsubmission_file_enabled' => 1,
'assignsubmission_file_maxfiles' => 12,
'assignsubmission_file_maxsizebytes' => 1024 * 1024,
'assignsubmission_onlinetext_enabled' => 1,
]);
$filename1 = 'submissionsample01.txt';
$filename2 = 'submissionsample02.txt';
$files = [
"mod/assign/tests/fixtures/" . $filename1,
"mod/assign/tests/fixtures/" . $filename2,
];
// Generate submissions.
$assignmentgenerator->create_submission([
'userid' => $user1->id,
'cmid' => $assignment->cmid,
'status' => 'submitted',
'file' => implode(',', $files),
'onlinetext' => 'Some text example',
]);
// Get the notifications.
$messages = $sink->get_messages_by_component('mod_assign');
$this->assertCount(1, $messages);
$message = reset($messages);
// Check the subject line and short message.
$this->assertEquals('Assignment submission confirmation - Assignment 1', $message->subject);
$this->assertEquals('Assignment submission confirmation - Assignment 1', $message->smallmessage);
// Check the plain text message.
$this->assertEquals('A100 -> Assignment -> Assignment 1
---------------------------------------------------------------------
You have submitted an assignment submission for \'Assignment 1\'.
You can see the status of your assignment submission:
https://www.example.com/moodle/mod/assign/view.php?id=' . $assignment->cmid . '
Your submission contains:
Online text
(3 words)
File submissions
* submissionsample01.txt (42 bytes)
* submissionsample02.txt (42 bytes)
---------------------------------------------------------------------
', $message->fullmessage);
$expectedfragments = [
'<p>Your assignment submission for \'Assignment 1\' has been submitted.</p>',
'<p>You can view your submission and check its status on the <a href="' .
'https://www.example.com/moodle/mod/assign/view.php?id=' .
$assignment->cmid . '">assignment page</a>.</p>',
'<h2>Your submission contains:</h2>',
'<h3>Online text</h3>',
'<p>(3 words)</p>',
'<h3>File submissions</h3>',
'<li>submissionsample01.txt (42 bytes)</li>',
'<li>submissionsample02.txt (42 bytes)</li>',
];
foreach ($expectedfragments as $html) {
$this->assertStringContainsString($html, $message->fullmessagehtml);
}
// Clear sink.
$sink->clear();
}
}