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_quiz;/*** Test class for the quiz notification helper.** @package mod_quiz* @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_quiz\notification_helper*/final class notification_helper_test extends \advanced_testcase {/*** Run all the tasks related to the notifications.*/protected function run_notification_helper_tasks(): void {$task = \core\task\manager::get_scheduled_task(\mod_quiz\task\queue_all_quiz_open_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_quiz\task\queue_quiz_open_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_quiz\task\send_quiz_open_soon_notification_to_user::class, $adhoctask);$adhoctask->execute();\core\task\manager::adhoc_task_complete($adhoctask);}}/*** Test getting quizzes with a 'timeopen' date within the date range.*/public function test_get_quizzes_within_date_range(): void {$this->resetAfterTest();$generator = $this->getDataGenerator();$helper = \core\di::get(notification_helper::class);$clock = $this->mock_clock_with_frozen();// Create a quiz with an open date < 48 hours.$course = $generator->create_course();$generator->create_module('quiz', ['course' => $course->id, 'timeopen' => $clock->time() + DAYSECS]);// Check that we have a result returned.$result = $helper::get_quizzes_within_date_range();$this->assertTrue($result->valid());$result->close();// Time travel 3 days into the future. We should have no quizzes in range.$clock->bump(DAYSECS * 3);$result = $helper::get_quizzes_within_date_range();$this->assertFalse($result->valid());$result->close();}/*** Test getting users within a quiz that are within our date range.*/public function test_get_users_within_quiz(): void {global $DB;$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(['suspended' => 1]);$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, 'teacher');$generator->enrol_user($user6->id, $course->id, 'student');/** @var \mod_quiz_generator $quizgenerator */$quizgenerator = $generator->get_plugin_generator('mod_quiz');// Create a quiz with an open date < 48 hours.$timeopen = $clock->time() + DAYSECS;$quiz = $quizgenerator->create_instance(['course' => $course->id,'timeopen' => $timeopen,]);// User1 will have a user specific override, giving them an extra 1 hour for 'timeopen'.$usertimeopen = $timeopen + HOURSECS;$quizgenerator->create_override(['quiz' => $quiz->id,'userid' => $user1->id,'timeopen' => $usertimeopen,]);// User2 and user3 will have a group override, giving them an extra 2 hours for 'timeopen'.$grouptimeopen = $timeopen + (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]);$quizgenerator->create_override(['quiz' => $quiz->id,'groupid' => $group->id,'timeopen' => $grouptimeopen,]);// Get the users within the date range.$quizzes = $helper::get_quizzes_within_date_range();foreach ($quizzes as $q) {$users = $helper::get_users_within_quiz($q->id);}$quizzes->close();// User1 has the 'user' override and its 'timeopen' date has been updated.$this->assertEquals($usertimeopen, $users[$user1->id]->timeopen);$this->assertEquals('user', $users[$user1->id]->overridetype);// User2 and user3 have the 'group' override and their 'timeopen' date has been updated.$this->assertEquals($grouptimeopen, $users[$user2->id]->timeopen);$this->assertEquals('group', $users[$user2->id]->overridetype);$this->assertEquals($grouptimeopen, $users[$user3->id]->timeopen);$this->assertEquals('group', $users[$user3->id]->overridetype);// User4 is unchanged.$this->assertEquals($timeopen, $users[$user4->id]->timeopen);$this->assertEquals('none', $users[$user4->id]->overridetype);// User5 should not be in the returned users because they are a teacher.$this->assertArrayNotHasKey($user5->id, $users);// User6 should not be in the returned users because it is suspended.$this->assertArrayNotHasKey($user6->id, $users);// Let's add some availability conditions.$availability =['op' => '&','showc' => [true],'c' => [['type' => 'group','id' => (int)$group->id,],],];$cm = get_coursemodule_from_instance('quiz', $quiz->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);// Get the users after availability conditions of the given quiz.$users = notification_helper::get_users_within_quiz($quiz->id);// Returns only users matching availability conditions who are in the specified group.$this->assertCount(2, $users);ksort($users);$this->assertEquals([$user2->id, $user3->id], array_keys($users));}/*** Test sending the quiz open soon notification to a user.*/public function test_send_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');/** @var \mod_quiz_generator $quizgenerator */$quizgenerator = $generator->get_plugin_generator('mod_quiz');// Create a quiz with an open date < 48 hours.$timeopen = $clock->time() + DAYSECS;$quiz = $quizgenerator->create_instance(['course' => $course->id,'timeopen' => $timeopen,]);$clock->bump(5);// Get the users within the date range.$quizzes = $helper::get_quizzes_within_date_range();foreach ($quizzes as $q) {$users = $helper::get_users_within_quiz($q->id);}$quizzes->close();// Run the tasks.$this->run_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_quiz');$message = reset($messages);$stringparams = ['timeopen' => userdate($users[$user1->id]->timeopen), 'quizname' => $quiz->name];$expectedsubject = get_string('quizopendatesoonsubject', 'mod_quiz', $stringparams);$this->assertEquals($expectedsubject, $message->subject);// Clear sink.$sink->clear();// Run the tasks again.$this->run_notification_helper_tasks();// There should be no notification because nothing has changed.$this->assertEmpty($sink->get_messages_by_component('mod_quiz'));// Let's modify the 'timeopen' for the quiz (it will still be within the 48 hour range).$updatedata = new \stdClass();$updatedata->id = $quiz->id;$updatedata->timeopen = $timeopen + HOURSECS;$DB->update_record('quiz', $updatedata);// Run the tasks again.$this->run_notification_helper_tasks();// There should be a new notification because the 'timeopen' has been updated.$this->assertCount(1, $sink->get_messages_by_component('mod_quiz'));// Clear sink.$sink->clear();// Let's modify the 'timeopen' one more time and change the visibility.$cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);$DB->set_field('course_modules', 'visible', 0, ['id' => $cm->id]);$updatedata = new \stdClass();$updatedata->id = $quiz->id;$updatedata->timeopen = $timeopen + DAYSECS;$DB->update_record('quiz', $updatedata);// Run the tasks again.$this->run_notification_helper_tasks();// There should not be a new notification because the quiz is not visible.$this->assertCount(0, $sink->get_messages_by_component('mod_quiz'));// Set back the visibility.$DB->set_field('course_modules', 'visible', 1, ['id' => $cm->id]);// Clear sink.$sink->clear();// Let's modify the 'timeopen' one more time.$updatedata = new \stdClass();$updatedata->id = $quiz->id;$updatedata->timeopen = $timeopen + (HOURSECS * 2);$DB->update_record('quiz', $updatedata);// This time, the user will submit an attempt.$DB->insert_record('quiz_attempts', ['quiz' => $quiz->id,'userid' => $user1->id,'state' => 'finished','timestart' => $clock->time(),'timecheckstate' => 0,'layout' => '','uniqueid' => 123,]);$clock->bump(5);// Run the tasks again.$this->run_notification_helper_tasks();// No new notification should have been sent.$this->assertEmpty($sink->get_messages_by_component('mod_quiz'));// Clear sink.$sink->clear();}/*** Test content filtering in the quiz open soon notification.*/public function test_send_notification_to_user_filter_content(): void {$this->resetAfterTest();$generator = $this->getDataGenerator();$clock = $this->mock_clock_with_frozen();$sink = $this->redirectMessages();filter_set_global_state('multilang', TEXTFILTER_ON);filter_set_applies_to_strings('multilang', true);// Create a course and enrol a student.$course = $generator->create_course(['fullname' => '<span class="multilang" lang="en">A&B (en)</span><span class="multilang" lang="es">A&B (es)</span>']);$student = $generator->create_user();$generator->enrol_user($student->id, $course->id, 'student');$quizgenerator = $generator->get_plugin_generator('mod_quiz');// Create a quiz with an open date < 48 hours.$quiz = $quizgenerator->create_instance(['name' => '<span class="multilang" lang="en">C&D (en)</span><span class="multilang" lang="es">C&D (es)</span>','course' => $course->id,'timeopen' => $clock->time() + DAYSECS,]);$clock->bump(5);// Run the tasks.$this->run_notification_helper_tasks();// Get the notifications that should have been created during the adhoc task.$messages = $sink->get_messages_by_component('mod_quiz');$message = reset($messages);$subjectstringparams = ['timeopen' => userdate($quiz->timeopen),'quizname' => 'C&D (en)'];$expectedsubject = get_string('quizopendatesoonsubject', 'mod_quiz', $subjectstringparams);$fullmessagestringparams = array_merge($subjectstringparams, ['firstname' => $student->firstname,'coursename' => 'A&B (en)','timeclose' => !empty($quiz->timeclose) ? userdate($quiz->timeclose) : get_string('statusna'),'url' => new \moodle_url('/mod/quiz/view.php', ['id' => $quiz->cmid])]);$expectedfullmessage = get_string('quizopendatesoonhtml', 'mod_quiz', $fullmessagestringparams);// Validate$this->assertEquals($expectedsubject, $message->subject);$this->assertEquals($expectedfullmessage, $message->fullmessagehtml);// Clear sink.$sink->clear();}}