See Release Notes
Long Term Support Release
Differences Between: [Versions 400 and 401] [Versions 401 and 403]
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 declare(strict_types=1); 18 19 namespace core_reportbuilder\task; 20 21 use advanced_testcase; 22 use core_collator; 23 use core_reportbuilder_generator; 24 use core_reportbuilder\manager; 25 use core_reportbuilder\local\filters\user; 26 use core_reportbuilder\local\models\schedule; 27 use core_reportbuilder\local\filters\text; 28 use core_reportbuilder\reportbuilder\audience\manual; 29 use core_user; 30 use core_user\reportbuilder\datasource\users; 31 32 /** 33 * Unit tests for ad-hoc task for sending report schedule 34 * 35 * @package core_reportbuilder 36 * @covers \core_reportbuilder\task\send_schedule 37 * @copyright 2021 Paul Holden <paulh@moodle.com> 38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 39 */ 40 class send_schedule_test extends advanced_testcase { 41 42 /** 43 * Data provider for {@see test_execute_viewas_user} 44 * 45 * @return array[] 46 */ 47 public function execute_report_viewas_user_provider(): array { 48 return [ 49 'View report as schedule creator' => [schedule::REPORT_VIEWAS_CREATOR, null, 'admin', 'admin'], 50 'View report as schedule recipient' => [schedule::REPORT_VIEWAS_RECIPIENT, null, 'userone', 'usertwo'], 51 'View report as specific user' => [null, 'userone', 'userone', 'userone'], 52 ]; 53 } 54 55 /** 56 * Test executing task for a schedule with differing "View as user" configuration 57 * 58 * @param int|null $viewasuser 59 * @param string|null $viewasusername 60 * @param string $useronesees 61 * @param string $usertwosees 62 * 63 * @dataProvider execute_report_viewas_user_provider 64 */ 65 public function test_execute_report_viewas_user( 66 ?int $viewasuser, 67 ?string $viewasusername, 68 string $useronesees, 69 string $usertwosees 70 ): void { 71 $this->preventResetByRollback(); 72 $this->resetAfterTest(); 73 $this->setAdminUser(); 74 75 $userone = $this->getDataGenerator()->create_user([ 76 'username' => 'userone', 77 'email' => 'user1@example.com', 78 'firstname' => 'Zoe', 79 'lastname' => 'Zebra', 80 ]); 81 $usertwo = $this->getDataGenerator()->create_user([ 82 'username' => 'usertwo', 83 'email' => 'user2@example.com', 84 'firstname' => 'Henrietta', 85 'lastname' => 'Hamster', 86 ]); 87 88 /** @var core_reportbuilder_generator $generator */ 89 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 90 91 // Create a report, with a single column and condition that the current user only sees themselves. 92 $report = $generator->create_report(['name' => 'Myself', 'source' => users::class, 'default' => false]); 93 $generator->create_column(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']); 94 95 $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:userselect']); 96 manager::get_report_from_persistent($report) 97 ->set_condition_values(['user:userselect_operator' => user::USER_CURRENT]); 98 99 // Add audience/schedule for our two test users. 100 $audience = $generator->create_audience([ 101 'reportid' => $report->get('id'), 102 'classname' => manual::class, 103 'configdata' => [ 104 'users' => [$userone->id, $usertwo->id], 105 ], 106 ]); 107 108 // If "View as user" isn't specified, it should be the ID of the given "View as username". 109 if ($viewasuser === null) { 110 $viewasuser = core_user::get_user_by_username($viewasusername, '*', null, MUST_EXIST)->id; 111 } 112 $schedule = $generator->create_schedule([ 113 'reportid' => $report->get('id'), 114 'name' => 'My schedule', 115 'userviewas' => $viewasuser, 116 'audiences' => json_encode([$audience->get_persistent()->get('id')]), 117 ]); 118 119 // Send the schedule, catch emails in sink (noting the users are sorted alphabetically). 120 $sink = $this->redirectEmails(); 121 122 $this->expectOutputRegex("/^Sending schedule: My schedule\n" . 123 " Sending to: " . fullname($usertwo) . "\n" . 124 " Sending to: " . fullname($userone) . "\n" . 125 "Sending schedule complete\n/" 126 ); 127 $sendschedule = new send_schedule(); 128 $sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]); 129 $sendschedule->execute(); 130 131 $messages = $sink->get_messages(); 132 $this->assertCount(2, $messages); 133 134 $sink->close(); 135 136 // Ensure caught messages are consistently ordered by recipient email prior to assertions. 137 core_collator::asort_objects_by_property($messages, 'to'); 138 $messages = array_values($messages); 139 140 $messageoneattachment = self::extract_message_attachment($messages[0]->body); 141 $this->assertEquals($userone->email, $messages[0]->to); 142 $this->assertStringEndsWith("Username\n{$useronesees}\n", $messageoneattachment); 143 144 $messagetwoattachment = self::extract_message_attachment($messages[1]->body); 145 $this->assertEquals($usertwo->email, $messages[1]->to); 146 $this->assertStringEndsWith("Username\n{$usertwosees}\n", $messagetwoattachment); 147 } 148 149 /** 150 * Test executing task where the schedule "View as user" is an inactive account 151 */ 152 public function test_execute_report_viewas_user_invalid(): void { 153 $this->resetAfterTest(); 154 $this->setAdminUser(); 155 156 /** @var core_reportbuilder_generator $generator */ 157 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 158 159 $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); 160 $audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]); 161 162 $schedule = $generator->create_schedule([ 163 'reportid' => $report->get('id'), 164 'name' => 'My schedule', 165 'userviewas' => 42, 166 'audiences' => json_encode([$audience->get_persistent()->get('id')]), 167 ]); 168 169 $this->expectOutputRegex("/^Sending schedule: My schedule\nInvalid schedule view as user: Invalid user/"); 170 $sendschedule = new send_schedule(); 171 $sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]); 172 $sendschedule->execute(); 173 } 174 175 /** 176 * Test executing task for a schedule that is configured to not send empty reports 177 */ 178 public function test_execute_report_empty(): void { 179 $this->resetAfterTest(); 180 $this->setAdminUser(); 181 182 /** @var core_reportbuilder_generator $generator */ 183 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 184 185 // Create a report that won't return any data. 186 $report = $generator->create_report(['name' => 'Myself', 'source' => users::class]); 187 188 $generator->create_condition(['reportid' => $report->get('id'), 'uniqueidentifier' => 'user:username']); 189 manager::get_report_from_persistent($report)->set_condition_values([ 190 'user:username_operator' => text::IS_EQUAL_TO, 191 'user:username_value' => 'baconlettucetomato', 192 ]); 193 194 $audience = $generator->create_audience(['reportid' => $report->get('id'), 'configdata' => []]); 195 $schedule = $generator->create_schedule([ 196 'reportid' => $report->get('id'), 197 'name' => 'My schedule', 198 'audiences' => json_encode([$audience->get_persistent()->get('id')]), 199 'reportempty' => schedule::REPORT_EMPTY_DONT_SEND, 200 ]); 201 202 $this->expectOutputString("Sending schedule: My schedule\n" . 203 " Empty report, skipping\n" . 204 "Sending schedule complete\n"); 205 $sendschedule = new send_schedule(); 206 $sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]); 207 $sendschedule->execute(); 208 } 209 210 /** 211 * Test executing task for a schedule that contains no recipients 212 */ 213 public function test_execute_schedule_no_recipients(): void { 214 $this->resetAfterTest(); 215 $this->setAdminUser(); 216 217 /** @var core_reportbuilder_generator $generator */ 218 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 219 220 $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); 221 $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule']); 222 223 $this->expectOutputString("Sending schedule: My schedule\n" . 224 "Sending schedule complete\n"); 225 $sendschedule = new send_schedule(); 226 $sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]); 227 $sendschedule->execute(); 228 } 229 230 /** 231 * Test executing task where the schedule creator is an inactive account 232 */ 233 public function test_execute_schedule_creator_invalid(): void { 234 $this->resetAfterTest(); 235 236 /** @var core_reportbuilder_generator $generator */ 237 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 238 239 $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); 240 $schedule = $generator->create_schedule(['reportid' => $report->get('id'), 'name' => 'My schedule', 'usercreated' => 42]); 241 242 $this->expectOutputRegex("/^Sending schedule: My schedule\nInvalid schedule creator: Invalid user/"); 243 $sendschedule = new send_schedule(); 244 $sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => $schedule->get('id')]); 245 $sendschedule->execute(); 246 } 247 248 /** 249 * Test executing task given invalid schedule data 250 */ 251 public function test_execute_schedule_invalid(): void { 252 $this->resetAfterTest(); 253 $this->setAdminUser(); 254 255 /** @var core_reportbuilder_generator $generator */ 256 $generator = $this->getDataGenerator()->get_plugin_generator('core_reportbuilder'); 257 $report = $generator->create_report(['name' => 'My report', 'source' => users::class]); 258 259 $this->expectOutputString("Invalid schedule\n"); 260 $sendschedule = new send_schedule(); 261 $sendschedule->set_custom_data(['reportid' => $report->get('id'), 'scheduleid' => 42]); 262 $sendschedule->execute(); 263 } 264 265 /** 266 * Given a multi-part message in MIME format, return the base64 encoded attachment contained within 267 * 268 * @param string $messagebody 269 * @return string 270 */ 271 private static function extract_message_attachment(string $messagebody): string { 272 $mimepart = preg_split('/Content-Disposition: attachment; filename="My schedule.csv"\s+/m', $messagebody); 273 274 // Extract the base64 encoded content after the "Content-Disposition" header. 275 preg_match_all('/^([A-Z0-9\/\+=]+)\s/im', $mimepart[1], $matches); 276 277 return base64_decode(implode($matches[0])); 278 } 279 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body