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/** 18 * Automated backup tests. 19 * 20 * @package core_backup 21 * @copyright 2019 John Yao <johnyao@catalyst-au.net> 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25defined('MOODLE_INTERNAL') || die(); 26 27global $CFG; 28require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php'); 29require_once($CFG->libdir.'/cronlib.php'); 30require_once($CFG->libdir . '/completionlib.php'); 31 32/** 33 * Automated backup tests. 34 * 35 * @copyright 2019 John Yao <johnyao@catalyst-au.net> 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38class core_backup_automated_backup_testcase extends advanced_testcase { 39 /** 40 * @var \backup_cron_automated_helper 41 */ 42 protected $backupcronautomatedhelper; 43 44 /** 45 * @var stdClass $course 46 */ 47 protected $course; 48 49 protected function setUp(): void { 50 global $DB, $CFG; 51 52 $this->resetAfterTest(true); 53 $this->setAdminUser(); 54 $CFG->enableavailability = true; 55 $CFG->enablecompletion = true; 56 57 // Getting a testable backup_cron_automated_helper class. 58 $this->backupcronautomatedhelper = new test_backup_cron_automated_helper(); 59 60 $generator = $this->getDataGenerator(); 61 $this->course = $generator->create_course( 62 array('format' => 'topics', 'numsections' => 3, 63 'enablecompletion' => COMPLETION_ENABLED), 64 array('createsections' => true)); 65 $forum = $generator->create_module('forum', array( 66 'course' => $this->course->id)); 67 $forum2 = $generator->create_module('forum', array( 68 'course' => $this->course->id, 'completion' => COMPLETION_TRACKING_MANUAL)); 69 70 // We need a grade, easiest is to add an assignment. 71 $assignrow = $generator->create_module('assign', array( 72 'course' => $this->course->id)); 73 $assign = new assign(context_module::instance($assignrow->cmid), false, false); 74 $item = $assign->get_grade_item(); 75 76 // Make a test grouping as well. 77 $grouping = $generator->create_grouping(array('courseid' => $this->course->id, 78 'name' => 'Grouping!')); 79 80 $availability = '{"op":"|","show":false,"c":[' . 81 '{"type":"completion","cm":' . $forum2->cmid .',"e":1},' . 82 '{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' . 83 '{"type":"grouping","id":' . $grouping->id . '}' . 84 ']}'; 85 $DB->set_field('course_modules', 'availability', $availability, array( 86 'id' => $forum->cmid)); 87 $DB->set_field('course_sections', 'availability', $availability, array( 88 'course' => $this->course->id, 'section' => 1)); 89 } 90 91 /** 92 * Tests the automated backup run when the there is course backup should be skipped. 93 */ 94 public function test_automated_backup_skipped_run() { 95 global $DB; 96 97 // Enable automated back up. 98 set_config('backup_auto_active', true, 'backup'); 99 set_config('backup_auto_weekdays', '1111111', 'backup'); 100 101 // Start backup process. 102 $admin = get_admin(); 103 104 // Backup entry should not exist. 105 $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id)); 106 $this->assertFalse($backupcourse); 107 $this->assertInstanceOf( 108 backup_cron_automated_helper::class, 109 $this->backupcronautomatedhelper->return_this() 110 ); 111 112 $classobject = $this->backupcronautomatedhelper->return_this(); 113 114 $method = new ReflectionMethod('\backup_cron_automated_helper', 'get_courses'); 115 $method->setAccessible(true); // Allow accessing of private method. 116 $courses = $method->invoke($classobject); 117 118 $method = new ReflectionMethod('\backup_cron_automated_helper', 'check_and_push_automated_backups'); 119 $method->setAccessible(true); // Allow accessing of private method. 120 $emailpending = $method->invokeArgs($classobject, [$courses, $admin]); 121 122 $coursename = $this->course->fullname; 123 $this->expectOutputRegex("/Skipping $coursename \(Not scheduled for backup until/"); 124 $this->assertFalse($emailpending); 125 126 $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id)); 127 $this->assertNotNull($backupcourse->laststatus); 128 } 129 130 /** 131 * Tests the automated backup run when the there is course backup can be pushed to adhoc task. 132 */ 133 public function test_automated_backup_push_run() { 134 global $DB; 135 136 // Enable automated back up. 137 set_config('backup_auto_active', true, 'backup'); 138 set_config('backup_auto_weekdays', '1111111', 'backup'); 139 140 $admin = get_admin(); 141 142 $classobject = $this->backupcronautomatedhelper->return_this(); 143 144 $method = new ReflectionMethod('\backup_cron_automated_helper', 'get_courses'); 145 $method->setAccessible(true); // Allow accessing of private method. 146 $courses = $method->invoke($classobject); 147 148 // Create this backup course. 149 $backupcourse = new stdClass; 150 $backupcourse->courseid = $this->course->id; 151 $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN; 152 $DB->insert_record('backup_courses', $backupcourse); 153 $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id)); 154 155 // We now manually trigger a backup pushed to adhoc task. 156 // Make sure is in the past, which means should run now. 157 $backupcourse->nextstarttime = time() - 10; 158 $DB->update_record('backup_courses', $backupcourse); 159 160 $method = new ReflectionMethod('\backup_cron_automated_helper', 'check_and_push_automated_backups'); 161 $method->setAccessible(true); // Allow accessing of private method. 162 $emailpending = $method->invokeArgs($classobject, [$courses, $admin]); 163 $this->assertTrue($emailpending); 164 165 $coursename = $this->course->fullname; 166 $this->expectOutputRegex("/Putting backup of $coursename in adhoc task queue/"); 167 168 $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id)); 169 // Now this backup course status should be queued. 170 $this->assertEquals(backup_cron_automated_helper::BACKUP_STATUS_QUEUED, $backupcourse->laststatus); 171 } 172 173 /** 174 * Tests the automated backup inactive run. 175 */ 176 public function test_inactive_run() { 177 backup_cron_automated_helper::run_automated_backup(); 178 $this->expectOutputString("Checking automated backup status...INACTIVE\n"); 179 } 180 181 /** 182 * Tests the invisible course being skipped. 183 */ 184 public function test_should_skip_invisible_course() { 185 global $DB; 186 187 set_config('backup_auto_active', true, 'backup'); 188 set_config('backup_auto_skip_hidden', true, 'backup'); 189 set_config('backup_auto_weekdays', '1111111', 'backup'); 190 // Create this backup course. 191 $backupcourse = new stdClass; 192 $backupcourse->courseid = $this->course->id; 193 // This is the status we believe last run was OK. 194 $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED; 195 $DB->insert_record('backup_courses', $backupcourse); 196 $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id)); 197 198 $this->assertTrue(course_change_visibility($this->course->id, false)); 199 $course = $DB->get_record('course', array('id' => $this->course->id)); 200 $this->assertEquals('0', $course->visible); 201 $classobject = $this->backupcronautomatedhelper->return_this(); 202 $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time()); 203 204 $method = new ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup'); 205 $method->setAccessible(true); // Allow accessing of private method. 206 $skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]); 207 208 $this->assertTrue($skipped); 209 $this->expectOutputRegex("/Skipping $course->fullname \(Not visible\)/"); 210 } 211 212 /** 213 * Tests the not modified course being skipped. 214 */ 215 public function test_should_skip_not_modified_course_in_days() { 216 global $DB; 217 218 set_config('backup_auto_active', true, 'backup'); 219 // Skip if not modified in two days. 220 set_config('backup_auto_skip_modif_days', 2, 'backup'); 221 set_config('backup_auto_weekdays', '1111111', 'backup'); 222 223 // Create this backup course. 224 $backupcourse = new stdClass; 225 $backupcourse->courseid = $this->course->id; 226 // This is the status we believe last run was OK. 227 $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED; 228 $backupcourse->laststarttime = time() - 2 * DAYSECS; 229 $backupcourse->lastendtime = time() - 1 * DAYSECS; 230 $DB->insert_record('backup_courses', $backupcourse); 231 $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id)); 232 $course = $DB->get_record('course', array('id' => $this->course->id)); 233 234 $course->timemodified = time() - 2 * DAYSECS - 1; 235 236 $classobject = $this->backupcronautomatedhelper->return_this(); 237 $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time()); 238 239 $method = new ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup'); 240 $method->setAccessible(true); // Allow accessing of private method. 241 $skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]); 242 243 $this->assertTrue($skipped); 244 $this->expectOutputRegex("/Skipping $course->fullname \(Not modified in the past 2 days\)/"); 245 } 246 247 /** 248 * Tests the backup not modified course being skipped. 249 */ 250 public function test_should_skip_not_modified_course_since_prev() { 251 global $DB; 252 253 set_config('backup_auto_active', true, 'backup'); 254 // Skip if not modified in two days. 255 set_config('backup_auto_skip_modif_prev', 2, 'backup'); 256 set_config('backup_auto_weekdays', '1111111', 'backup'); 257 258 // Create this backup course. 259 $backupcourse = new stdClass; 260 $backupcourse->courseid = $this->course->id; 261 // This is the status we believe last run was OK. 262 $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED; 263 $backupcourse->laststarttime = time() - 2 * DAYSECS; 264 $backupcourse->lastendtime = time() - 1 * DAYSECS; 265 $DB->insert_record('backup_courses', $backupcourse); 266 $backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id)); 267 $course = $DB->get_record('course', array('id' => $this->course->id)); 268 269 $course->timemodified = time() - 2 * DAYSECS - 1; 270 271 $classobject = $this->backupcronautomatedhelper->return_this(); 272 $nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time()); 273 274 $method = new ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup'); 275 $method->setAccessible(true); // Allow accessing of private method. 276 $skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]); 277 278 $this->assertTrue($skipped); 279 $this->expectOutputRegex("/Skipping $course->fullname \(Not modified since previous backup\)/"); 280 } 281 282 /** 283 * Test the task completes when coureid is missing. 284 */ 285 public function test_task_complete_when_courseid_is_missing() { 286 global $DB; 287 $admin = get_admin(); 288 $classobject = $this->backupcronautomatedhelper->return_this(); 289 290 // Create this backup course. 291 $backupcourse = new stdClass; 292 $backupcourse->courseid = $this->course->id; 293 $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN; 294 $DB->insert_record('backup_courses', $backupcourse); 295 $backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]); 296 297 // Create a backup task. 298 $method = new ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task'); 299 $method->setAccessible(true); // Allow accessing of private method. 300 $method->invokeArgs($classobject, [$backupcourse, $admin]); 301 302 // Delete course for this test. 303 delete_course($this->course->id, false); 304 305 $task = core\task\manager::get_next_adhoc_task(time()); 306 307 ob_start(); 308 $task->execute(); 309 $output = ob_get_clean(); 310 311 $this->assertStringContainsString('Invalid course id: ' . $this->course->id . ', task aborted.', $output); 312 core\task\manager::adhoc_task_complete($task); 313 } 314 315 /** 316 * Test the task completes when backup course is missing. 317 */ 318 public function test_task_complete_when_backup_course_is_missing() { 319 global $DB; 320 $admin = get_admin(); 321 $classobject = $this->backupcronautomatedhelper->return_this(); 322 323 // Create this backup course. 324 $backupcourse = new stdClass; 325 $backupcourse->courseid = $this->course->id; 326 $backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN; 327 $DB->insert_record('backup_courses', $backupcourse); 328 $backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]); 329 330 // Create a backup task. 331 $method = new ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task'); 332 $method->setAccessible(true); // Allow accessing of private method. 333 $method->invokeArgs($classobject, [$backupcourse, $admin]); 334 335 // Delete backup course for this test. 336 $DB->delete_records('backup_courses', ['courseid' => $this->course->id]); 337 338 $task = core\task\manager::get_next_adhoc_task(time()); 339 340 ob_start(); 341 $task->execute(); 342 $output = ob_get_clean(); 343 344 $this->assertStringContainsString('Automated backup for course: ' . $this->course->fullname . ' encounters an error.', 345 $output); 346 core\task\manager::adhoc_task_complete($task); 347 } 348} 349 350/** 351 * New backup_cron_automated_helper class for testing. 352 * 353 * This class extends the helper backup_cron_automated_helper class 354 * in order to utilise abstract class for testing. 355 * 356 * @package core 357 * @copyright 2019 John Yao <johnyao@catalyst-au.net> 358 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 359 */ 360class test_backup_cron_automated_helper extends backup_cron_automated_helper { 361 /** 362 * Returning this for testing. 363 */ 364 public function return_this() { 365 return $this; 366 } 367} 368