first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+259
View File
@@ -0,0 +1,259 @@
<?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 core_backup;
use backup;
use backup_controller;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->libdir . '/completionlib.php');
/**
* Asyncronhous backup tests.
*
* @package core_backup
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class async_backup_test extends \advanced_testcase {
/**
* Tests the asynchronous backup.
*/
public function test_async_backup(): void {
global $CFG, $DB, $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;
$CFG->enablecompletion = true;
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'topics', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$forum = $generator->create_module('forum', array(
'course' => $course->id));
$forum2 = $generator->create_module('forum', array(
'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
// Create a teacher user to call the backup.
$teacher = $generator->create_user();
$generator->enrol_user($teacher->id, $course->id, 'editingteacher');
// We need a grade, easiest is to add an assignment.
$assignrow = $generator->create_module('assign', array(
'course' => $course->id));
$assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
$item = $assign->get_grade_item();
// Make a test grouping as well.
$grouping = $generator->create_grouping(array('courseid' => $course->id,
'name' => 'Grouping!'));
$availability = '{"op":"|","show":false,"c":[' .
'{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
'{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
'{"type":"grouping","id":' . $grouping->id . '}' .
']}';
$DB->set_field('course_modules', 'availability', $availability, array(
'id' => $forum->cmid));
$DB->set_field('course_sections', 'availability', $availability, array(
'course' => $course->id, 'section' => 1));
// Enable logging.
$this->preventResetByRollback();
set_config('enabled_stores', 'logstore_standard', 'tool_log');
set_config('buffersize', 0, 'logstore_standard');
get_log_manager(true);
// Case 1: Make a course backup without users.
$this->setUser($teacher->id);
// Make the backup controller for an async backup.
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_ASYNC, $USER->id);
$bc->finish_ui();
$backupid = $bc->get_backupid();
$bc->destroy();
$prebackuprec = $DB->get_record('backup_controllers', array('backupid' => $backupid));
// Check the initial backup controller was created correctly.
$this->assertEquals(backup::STATUS_AWAITING, $prebackuprec->status);
$this->assertEquals(2, $prebackuprec->execution);
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(['backupid' => $backupid]);
$asynctask->set_userid($USER->id);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting trace output during this test.
$this->expectOutputRegex("/$backupid/");
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
// Check backup was created successfully.
$this->assertEquals(backup::STATUS_FINISHED_OK, $postbackuprec->status);
$this->assertEquals(1.0, $postbackuprec->progress);
$this->assertEquals($teacher->id, $postbackuprec->userid);
// Check that the backupid was logged correctly.
$logrec = $DB->get_record('logstore_standard_log', ['userid' => $postbackuprec->userid,
'target' => 'course_backup'], '*', MUST_EXIST);
$otherdata = json_decode($logrec->other);
$this->assertEquals($backupid, $otherdata->backupid);
// Check backup was stored in correct area.
$usercontextid = $DB->get_field('context', 'id', ['contextlevel' => CONTEXT_USER, 'instanceid' => $teacher->id]);
$this->assertEquals(1, $DB->count_records('files', ['contextid' => $usercontextid,
'component' => 'user', 'filearea' => 'backup', 'filename' => 'backup.mbz']));
// Case 2: Make a second backup with users and not anonymised.
$this->setAdminUser();
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_ASYNC, $USER->id);
$bc->get_plan()->get_setting('users')->set_status(\backup_setting::NOT_LOCKED);
$bc->get_plan()->get_setting('users')->set_value(true);
$bc->get_plan()->get_setting('anonymize')->set_value(false);
$bc->finish_ui();
$backupid = $bc->get_backupid();
$bc->destroy();
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(['backupid' => $backupid]);
\core\task\manager::queue_adhoc_task($asynctask);
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
$postbackuprec = $DB->get_record('backup_controllers', ['backupid' => $backupid]);
// Check backup was created successfully.
$this->assertEquals(backup::STATUS_FINISHED_OK, $postbackuprec->status);
$this->assertEquals(1.0, $postbackuprec->progress);
$this->assertEquals($USER->id, $postbackuprec->userid);
// Check that the backupid was logged correctly.
$logrec = $DB->get_record('logstore_standard_log', ['userid' => $postbackuprec->userid,
'target' => 'course_backup'], '*', MUST_EXIST);
$otherdata = json_decode($logrec->other);
$this->assertEquals($backupid, $otherdata->backupid);
// Check backup was stored in correct area.
$coursecontextid = $DB->get_field('context', 'id', ['contextlevel' => CONTEXT_COURSE, 'instanceid' => $course->id]);
$this->assertEquals(1, $DB->count_records('files', ['contextid' => $coursecontextid,
'component' => 'backup', 'filearea' => 'course', 'filename' => 'backup.mbz']));
}
/**
* Tests the asynchronous backup will resolve in duplicate cases.
*/
public function test_complete_async_backup(): void {
global $CFG, $DB, $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;
$CFG->enablecompletion = true;
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'topics', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$forum = $generator->create_module('forum', array(
'course' => $course->id));
$forum2 = $generator->create_module('forum', array(
'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
// We need a grade, easiest is to add an assignment.
$assignrow = $generator->create_module('assign', array(
'course' => $course->id));
$assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
$item = $assign->get_grade_item();
// Make a test grouping as well.
$grouping = $generator->create_grouping(array('courseid' => $course->id,
'name' => 'Grouping!'));
$availability = '{"op":"|","show":false,"c":[' .
'{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
'{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
'{"type":"grouping","id":' . $grouping->id . '}' .
']}';
$DB->set_field('course_modules', 'availability', $availability, array(
'id' => $forum->cmid));
$DB->set_field('course_sections', 'availability', $availability, array(
'course' => $course->id, 'section' => 1));
// Make the backup controller for an async backup.
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_ASYNC, $USER->id);
$bc->finish_ui();
$backupid = $bc->get_backupid();
$bc->destroy();
// Now hack the record to remove the controller and set the status fields to complete.
// This emulates a duplicate run for an already finished controller.
$id = $DB->get_field('backup_controllers', 'id', ['backupid' => $backupid]);
$data = [
'id' => $id,
'controller' => '',
'progress' => 1.0,
'status' => backup::STATUS_FINISHED_OK
];
$DB->update_record('backup_controllers', $data);
// Now queue an adhoc task and check it handles and completes gracefully.
$asynctask = new \core\task\asynchronous_backup_task();
$asynctask->set_custom_data(array('backupid' => $backupid));
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/invalid controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_backup_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
}
}
+262
View File
@@ -0,0 +1,262 @@
<?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 core_backup;
use backup;
use backup_controller;
use restore_controller;
use restore_dbops;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->libdir . '/completionlib.php');
/**
* Asyncronhous restore tests.
*
* @package core_backup
* @copyright 2018 Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class async_restore_test extends \advanced_testcase {
/**
* Tests the asynchronous backup.
*/
public function test_async_restore(): void {
global $CFG, $USER, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;
$CFG->enablecompletion = true;
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'topics', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$forum = $generator->create_module('forum', array(
'course' => $course->id));
$forum2 = $generator->create_module('forum', array(
'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
// We need a grade, easiest is to add an assignment.
$assignrow = $generator->create_module('assign', array(
'course' => $course->id));
$assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
$item = $assign->get_grade_item();
// Make a test grouping as well.
$grouping = $generator->create_grouping(array('courseid' => $course->id,
'name' => 'Grouping!'));
$availability = '{"op":"|","show":false,"c":[' .
'{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
'{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
'{"type":"grouping","id":' . $grouping->id . '}' .
']}';
$DB->set_field('course_modules', 'availability', $availability, array(
'id' => $forum->cmid));
$DB->set_field('course_sections', 'availability', $availability, array(
'course' => $course->id, 'section' => 1));
// Backup the course.
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $USER->id);
$bc->finish_ui();
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
// Get the backup file.
$coursecontext = \context_course::instance($course->id);
$fs = get_file_storage();
$files = $fs->get_area_files($coursecontext->id, 'backup', 'course', false, 'id ASC');
$backupfile = reset($files);
// Extract backup file.
$backupdir = "restore_" . uniqid();
$path = $CFG->tempdir . DIRECTORY_SEPARATOR . "backup" . DIRECTORY_SEPARATOR . $backupdir;
$fp = get_file_packer('application/vnd.moodle.backup');
$fp->extract_to_pathname($backupfile, $path);
// Create restore controller.
$newcourseid = restore_dbops::create_new_course(
$course->fullname, $course->shortname . '_2', $course->category);
$rc = new restore_controller($backupdir, $newcourseid,
backup::INTERACTIVE_NO, backup::MODE_ASYNC, $USER->id,
backup::TARGET_NEW_COURSE);
$this->assertTrue($rc->execute_precheck());
$restoreid = $rc->get_restoreid();
$prerestorerec = $DB->get_record('backup_controllers', array('backupid' => $restoreid));
$prerestorerec->controller = '';
$rc->destroy();
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_restore_task();
$asynctask->set_custom_data(array('backupid' => $restoreid));
$asynctask->set_userid($USER->id);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting trace output during this test.
$this->expectOutputRegex("/$restoreid/");
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
$postrestorerec = $DB->get_record('backup_controllers', array('backupid' => $restoreid));
// Check backup was created successfully.
$this->assertEquals(backup::STATUS_FINISHED_OK, $postrestorerec->status);
$this->assertEquals(1.0, $postrestorerec->progress);
$this->assertEquals($USER->id, $postrestorerec->userid);
}
/**
* Tests the asynchronous restore will resolve in duplicate cases where the controller is already removed.
*/
public function test_async_restore_missing_controller(): void {
global $CFG, $USER, $DB;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;
$CFG->enablecompletion = true;
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'topics', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$forum = $generator->create_module('forum', array(
'course' => $course->id));
$forum2 = $generator->create_module('forum', array(
'course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
// We need a grade, easiest is to add an assignment.
$assignrow = $generator->create_module('assign', array(
'course' => $course->id));
$assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
$item = $assign->get_grade_item();
// Make a test grouping as well.
$grouping = $generator->create_grouping(array('courseid' => $course->id,
'name' => 'Grouping!'));
$availability = '{"op":"|","show":false,"c":[' .
'{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
'{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
'{"type":"grouping","id":' . $grouping->id . '}' .
']}';
$DB->set_field('course_modules', 'availability', $availability, array(
'id' => $forum->cmid));
$DB->set_field('course_sections', 'availability', $availability, array(
'course' => $course->id, 'section' => 1));
// Backup the course.
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_YES, backup::MODE_GENERAL, $USER->id);
$bc->finish_ui();
$bc->execute_plan();
$bc->destroy();
// Get the backup file.
$coursecontext = \context_course::instance($course->id);
$fs = get_file_storage();
$files = $fs->get_area_files($coursecontext->id, 'backup', 'course', false, 'id ASC');
$backupfile = reset($files);
// Extract backup file.
$backupdir = "restore_" . uniqid();
$path = $CFG->tempdir . DIRECTORY_SEPARATOR . "backup" . DIRECTORY_SEPARATOR . $backupdir;
$fp = get_file_packer('application/vnd.moodle.backup');
$fp->extract_to_pathname($backupfile, $path);
// Create restore controller.
$newcourseid = restore_dbops::create_new_course(
$course->fullname, $course->shortname . '_2', $course->category);
$rc = new restore_controller($backupdir, $newcourseid,
backup::INTERACTIVE_NO, backup::MODE_ASYNC, $USER->id,
backup::TARGET_NEW_COURSE);
$restoreid = $rc->get_restoreid();
$controllerid = $DB->get_field('backup_controllers', 'id', ['backupid' => $restoreid]);
// Now hack the record to remove the controller and set the status fields to complete.
// This emulates a duplicate run for an already finished controller.
$data = [
'id' => $controllerid,
'controller' => '',
'progress' => 1.0,
'status' => backup::STATUS_FINISHED_OK
];
$DB->update_record('backup_controllers', $data);
$rc->destroy();
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_restore_task();
$asynctask->set_custom_data(['backupid' => $restoreid]);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/invalid controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
// Now delete the record and confirm an entirely missing controller is handled.
$DB->delete_records('backup_controllers');
// Create the adhoc task.
$asynctask = new \core\task\asynchronous_restore_task();
$asynctask->set_custom_data(['backupid' => $restoreid]);
\core\task\manager::queue_adhoc_task($asynctask);
// We are expecting a specific message output during this test.
$this->expectOutputRegex('/Unable to find restore controller/');
// Execute adhoc task.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_restore_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the task record is removed.
$this->assertEquals(0, $DB->count_records('task_adhoc'));
}
}
+360
View File
@@ -0,0 +1,360 @@
<?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/>.
/**
* Automated backup tests.
*
* @package core_backup
* @copyright 2019 John Yao <johnyao@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_backup;
use backup_cron_automated_helper;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/helper/backup_cron_helper.class.php');
require_once($CFG->libdir . '/completionlib.php');
/**
* Automated backup tests.
*
* @package core_backup
* @copyright 2019 John Yao <johnyao@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class automated_backup_test extends \advanced_testcase {
/**
* @var \backup_cron_automated_helper
*/
protected $backupcronautomatedhelper;
/**
* @var \stdClass $course
*/
protected $course;
protected function setUp(): void {
global $DB, $CFG;
$this->resetAfterTest(true);
$this->setAdminUser();
$CFG->enableavailability = true;
$CFG->enablecompletion = true;
// Getting a testable backup_cron_automated_helper class.
$this->backupcronautomatedhelper = new test_backup_cron_automated_helper();
$generator = $this->getDataGenerator();
$this->course = $generator->create_course(
array('format' => 'topics', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$forum = $generator->create_module('forum', array(
'course' => $this->course->id));
$forum2 = $generator->create_module('forum', array(
'course' => $this->course->id, 'completion' => COMPLETION_TRACKING_MANUAL));
// We need a grade, easiest is to add an assignment.
$assignrow = $generator->create_module('assign', array(
'course' => $this->course->id));
$assign = new \assign(\context_module::instance($assignrow->cmid), false, false);
$item = $assign->get_grade_item();
// Make a test grouping as well.
$grouping = $generator->create_grouping(array('courseid' => $this->course->id,
'name' => 'Grouping!'));
$availability = '{"op":"|","show":false,"c":[' .
'{"type":"completion","cm":' . $forum2->cmid .',"e":1},' .
'{"type":"grade","id":' . $item->id . ',"min":4,"max":94},' .
'{"type":"grouping","id":' . $grouping->id . '}' .
']}';
$DB->set_field('course_modules', 'availability', $availability, array(
'id' => $forum->cmid));
$DB->set_field('course_sections', 'availability', $availability, array(
'course' => $this->course->id, 'section' => 1));
}
/**
* Tests the automated backup run when the there is course backup should be skipped.
*/
public function test_automated_backup_skipped_run(): void {
global $DB;
// Enable automated back up.
set_config('backup_auto_active', true, 'backup');
set_config('backup_auto_weekdays', '1111111', 'backup');
// Start backup process.
$admin = get_admin();
// Backup entry should not exist.
$backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
$this->assertFalse($backupcourse);
$this->assertInstanceOf(
backup_cron_automated_helper::class,
$this->backupcronautomatedhelper->return_this()
);
$classobject = $this->backupcronautomatedhelper->return_this();
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'get_courses');
$courses = $method->invoke($classobject);
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'check_and_push_automated_backups');
$emailpending = $method->invokeArgs($classobject, [$courses, $admin]);
$this->expectOutputRegex('/Skipping course id ' . $this->course->id . ': Not scheduled for backup until/');
$this->assertFalse($emailpending);
$backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
$this->assertNotNull($backupcourse->laststatus);
}
/**
* Tests the automated backup run when the there is course backup can be pushed to adhoc task.
*/
public function test_automated_backup_push_run(): void {
global $DB;
// Enable automated back up.
set_config('backup_auto_active', true, 'backup');
set_config('backup_auto_weekdays', '1111111', 'backup');
$admin = get_admin();
$classobject = $this->backupcronautomatedhelper->return_this();
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'get_courses');
$courses = $method->invoke($classobject);
// Create this backup course.
$backupcourse = new \stdClass;
$backupcourse->courseid = $this->course->id;
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
// We now manually trigger a backup pushed to adhoc task.
// Make sure is in the past, which means should run now.
$backupcourse->nextstarttime = time() - 10;
$DB->update_record('backup_courses', $backupcourse);
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'check_and_push_automated_backups');
$emailpending = $method->invokeArgs($classobject, [$courses, $admin]);
$this->assertTrue($emailpending);
$this->expectOutputRegex('/Putting backup of course id ' . $this->course->id. ' in adhoc task queue/');
$backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
// Now this backup course status should be queued.
$this->assertEquals(backup_cron_automated_helper::BACKUP_STATUS_QUEUED, $backupcourse->laststatus);
}
/**
* Tests the automated backup inactive run.
*/
public function test_inactive_run(): void {
backup_cron_automated_helper::run_automated_backup();
$this->expectOutputString("Checking automated backup status...INACTIVE\n");
}
/**
* Tests the invisible course being skipped.
*/
public function test_should_skip_invisible_course(): void {
global $DB;
set_config('backup_auto_active', true, 'backup');
set_config('backup_auto_skip_hidden', true, 'backup');
set_config('backup_auto_weekdays', '1111111', 'backup');
// Create this backup course.
$backupcourse = new \stdClass;
$backupcourse->courseid = $this->course->id;
// This is the status we believe last run was OK.
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
$this->assertTrue(course_change_visibility($this->course->id, false));
$course = $DB->get_record('course', array('id' => $this->course->id));
$this->assertEquals('0', $course->visible);
$classobject = $this->backupcronautomatedhelper->return_this();
$nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time());
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup');
$skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]);
$this->assertTrue($skipped);
$this->expectOutputRegex('/Skipping course id ' . $this->course->id. ': Not visible/');
}
/**
* Tests the not modified course being skipped.
*/
public function test_should_skip_not_modified_course_in_days(): void {
global $DB;
set_config('backup_auto_active', true, 'backup');
// Skip if not modified in two days.
set_config('backup_auto_skip_modif_days', 2, 'backup');
set_config('backup_auto_weekdays', '1111111', 'backup');
// Create this backup course.
$backupcourse = new \stdClass;
$backupcourse->courseid = $this->course->id;
// This is the status we believe last run was OK.
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
$backupcourse->laststarttime = time() - 2 * DAYSECS;
$backupcourse->lastendtime = time() - 1 * DAYSECS;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
$course = $DB->get_record('course', array('id' => $this->course->id));
$course->timemodified = time() - 2 * DAYSECS - 1;
$classobject = $this->backupcronautomatedhelper->return_this();
$nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time());
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup');
$skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]);
$this->assertTrue($skipped);
$this->expectOutputRegex('/Skipping course id ' . $this->course->id . ': Not modified in the past 2 days/');
}
/**
* Tests the backup not modified course being skipped.
*/
public function test_should_skip_not_modified_course_since_prev(): void {
global $DB;
set_config('backup_auto_active', true, 'backup');
// Skip if not modified in two days.
set_config('backup_auto_skip_modif_prev', 2, 'backup');
set_config('backup_auto_weekdays', '1111111', 'backup');
// Create this backup course.
$backupcourse = new \stdClass;
$backupcourse->courseid = $this->course->id;
// This is the status we believe last run was OK.
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_SKIPPED;
$backupcourse->laststarttime = time() - 2 * DAYSECS;
$backupcourse->lastendtime = time() - 1 * DAYSECS;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', array('courseid' => $this->course->id));
$course = $DB->get_record('course', array('id' => $this->course->id));
$course->timemodified = time() - 2 * DAYSECS - 1;
$classobject = $this->backupcronautomatedhelper->return_this();
$nextstarttime = backup_cron_automated_helper::calculate_next_automated_backup(null, time());
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'should_skip_course_backup');
$skipped = $method->invokeArgs($classobject, [$backupcourse, $course, $nextstarttime]);
$this->assertTrue($skipped);
$this->expectOutputRegex('/Skipping course id ' . $this->course->id . ': Not modified since previous backup/');
}
/**
* Test the task completes when coureid is missing.
*/
public function test_task_complete_when_courseid_is_missing(): void {
global $DB;
$admin = get_admin();
$classobject = $this->backupcronautomatedhelper->return_this();
// Create this backup course.
$backupcourse = new \stdClass;
$backupcourse->courseid = $this->course->id;
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]);
// Create a backup task.
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task');
$method->invokeArgs($classobject, [$backupcourse, $admin]);
// Delete course for this test.
delete_course($this->course->id, false);
$task = \core\task\manager::get_next_adhoc_task(time());
ob_start();
$task->execute();
$output = ob_get_clean();
$this->assertStringContainsString('Invalid course id: ' . $this->course->id . ', task aborted.', $output);
\core\task\manager::adhoc_task_complete($task);
}
/**
* Test the task completes when backup course is missing.
*/
public function test_task_complete_when_backup_course_is_missing(): void {
global $DB;
$admin = get_admin();
$classobject = $this->backupcronautomatedhelper->return_this();
// Create this backup course.
$backupcourse = new \stdClass;
$backupcourse->courseid = $this->course->id;
$backupcourse->laststatus = backup_cron_automated_helper::BACKUP_STATUS_NOTYETRUN;
$DB->insert_record('backup_courses', $backupcourse);
$backupcourse = $DB->get_record('backup_courses', ['courseid' => $this->course->id]);
// Create a backup task.
$method = new \ReflectionMethod('\backup_cron_automated_helper', 'push_course_backup_adhoc_task');
$method->invokeArgs($classobject, [$backupcourse, $admin]);
// Delete backup course for this test.
$DB->delete_records('backup_courses', ['courseid' => $this->course->id]);
$task = \core\task\manager::get_next_adhoc_task(time());
ob_start();
$task->execute();
$output = ob_get_clean();
$this->assertStringContainsString('Automated backup for course: ' . $this->course->fullname . ' encounters an error.',
$output);
\core\task\manager::adhoc_task_complete($task);
}
}
/**
* New backup_cron_automated_helper class for testing.
*
* This class extends the helper backup_cron_automated_helper class
* in order to utilise abstract class for testing.
*
* @package core
* @copyright 2019 John Yao <johnyao@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class test_backup_cron_automated_helper extends backup_cron_automated_helper {
/**
* Returning this for testing.
*/
public function return_this() {
return $this;
}
}
+153
View File
@@ -0,0 +1,153 @@
<?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 core_backup;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
/**
* Tests for the \core\task\backup_cleanup_task scheduled task.
*
* @package core_backup
* @copyright 2021 Mikhail Golenkov <mikhailgolenkov@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_cleanup_task_test extends \advanced_testcase {
/**
* Set up tasks for all tests.
*/
protected function setUp(): void {
$this->resetAfterTest(true);
}
/**
* Take a backup of the course provided and return backup id.
*
* @param int $courseid Course id to be backed up.
* @return string Backup id.
*/
private function backup_course(int $courseid): string {
// Backup the course.
$user = get_admin();
$controller = new \backup_controller(
\backup::TYPE_1COURSE,
$courseid,
\backup::FORMAT_MOODLE,
\backup::INTERACTIVE_NO,
\backup::MODE_AUTOMATED,
$user->id
);
$controller->execute_plan();
$controller->destroy(); // Unset all structures, close files...
return $controller->get_backupid();
}
/**
* Test the task idle run. Nothing should explode.
*/
public function test_backup_cleanup_task_idle(): void {
$task = new \core\task\backup_cleanup_task();
$task->execute();
}
/**
* Test the task exits when backup | loglifetime setting is not set.
*/
public function test_backup_cleanup_task_exits(): void {
set_config('loglifetime', 0, 'backup');
$task = new \core\task\backup_cleanup_task();
ob_start();
$task->execute();
$output = ob_get_contents();
ob_end_clean();
$this->assertStringContainsString('config is not set', $output);
}
/**
* Test the task deletes records from DB.
*/
public function test_backup_cleanup_task_deletes_records(): void {
global $DB;
// Create a course.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
// Take two backups of the course.
$backupid1 = $this->backup_course($course->id);
$backupid2 = $this->backup_course($course->id);
// Emulate the first backup to be done 31 days ago.
$bcrecord = $DB->get_record('backup_controllers', ['backupid' => $backupid1]);
$bcrecord->timecreated -= DAYSECS * 31;
$DB->update_record('backup_controllers', $bcrecord);
// Run the task.
$task = new \core\task\backup_cleanup_task();
$task->execute();
// There should be no records related to the first backup.
$this->assertEquals(0, $DB->count_records('backup_controllers', ['backupid' => $backupid1]));
$this->assertEquals(0, $DB->count_records('backup_logs', ['backupid' => $backupid1]));
// Records related to the second backup should remain.
$this->assertGreaterThan(0, $DB->count_records('backup_controllers', ['backupid' => $backupid2]));
$this->assertGreaterThanOrEqual(0, $DB->count_records('backup_logs', ['backupid' => $backupid2]));
}
/**
* Test the task deletes files from file system.
*/
public function test_backup_cleanup_task_deletes_files(): void {
global $CFG;
// Create a course.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
// Take two backups of the course and get their logs.
$backupid1 = $this->backup_course($course->id);
$backupid2 = $this->backup_course($course->id);
$filepath1 = $CFG->backuptempdir . '/' . $backupid1 . '.log';
$filepath2 = $CFG->backuptempdir . '/' . $backupid2 . '.log';
// Create a subdirectory.
$subdir = $CFG->backuptempdir . '/subdir';
make_writable_directory($subdir);
// Both logs and the dir should exist.
$this->assertTrue(file_exists($filepath1));
$this->assertTrue(file_exists($filepath2));
$this->assertTrue(file_exists($subdir));
// Change modification time of the first log and the sub dir to be 8 days ago.
touch($filepath1, time() - 8 * DAYSECS);
touch($subdir, time() - 8 * DAYSECS);
// Run the task.
$task = new \core\task\backup_cleanup_task();
$task->execute();
// Files and directories older than a week are supposed to be removed.
$this->assertFalse(file_exists($filepath1));
$this->assertFalse(file_exists($subdir));
$this->assertTrue(file_exists($filepath2));
}
}
@@ -0,0 +1,123 @@
<?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/>.
/**
* Backup restore base tests.
*
* @package core_backup
* @copyright Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* Basic testcase class for backup / restore functionality.
*/
abstract class core_backup_backup_restore_base_testcase extends advanced_testcase {
/**
* Setup test data.
*/
protected function setUp(): void {
$this->resetAfterTest();
$this->setAdminUser();
}
/**
* Backup the course by general mode.
*
* @param stdClass $course Course for backup.
* @return string Hash string ID from the backup.
* @throws coding_exception
* @throws moodle_exception
*/
protected function perform_backup($course): string {
global $CFG, $USER;
$coursecontext = context_course::instance($course->id);
// Start backup process.
$bc = new backup_controller(backup::TYPE_1COURSE, $course->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id);
$bc->execute_plan();
$backupid = $bc->get_backupid();
$bc->destroy();
// Get the backup file.
$fs = get_file_storage();
$files = $fs->get_area_files($coursecontext->id, 'backup', 'course', false, 'id ASC');
$backupfile = reset($files);
// Extract backup file.
$path = $CFG->tempdir . DIRECTORY_SEPARATOR . "backup" . DIRECTORY_SEPARATOR . $backupid;
$fp = get_file_packer('application/vnd.moodle.backup');
$fp->extract_to_pathname($backupfile, $path);
return $backupid;
}
/**
* Restore from backupid to course.
*
* @param string $backupid Hash string ID from backup.
* @param stdClass $course Course which is restored for.
* @throws restore_controller_exception
*/
protected function perform_restore($backupid, $course): void {
global $USER;
// Set up restore.
$rc = new restore_controller($backupid, $course->id,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id, backup::TARGET_EXISTING_ADDING);
// Execute restore.
$rc->execute_precheck();
$rc->execute_plan();
$rc->destroy();
}
/**
* Import course from course1 to course2.
*
* @param stdClass $course1 Course to be backuped up.
* @param stdClass $course2 Course to be restored.
* @throws restore_controller_exception
*/
protected function perform_import($course1, $course2): void {
global $USER;
// Start backup process.
$bc = new backup_controller(backup::TYPE_1COURSE, $course1->id, backup::FORMAT_MOODLE,
backup::INTERACTIVE_NO, backup::MODE_IMPORT, $USER->id);
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
// Set up restore.
$rc = new restore_controller($backupid, $course2->id,
backup::INTERACTIVE_NO, backup::MODE_SAMESITE, $USER->id, backup::TARGET_EXISTING_ADDING);
// Execute restore.
$rc->execute_precheck();
$rc->execute_plan();
$rc->destroy();
}
}
+109
View File
@@ -0,0 +1,109 @@
<?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 core_backup;
use core_backup_backup_restore_base_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once('backup_restore_base_testcase.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* Backup restore permission tests.
*
* @package core_backup
* @author Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @copyright 2023 Catalyst IT Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_restore_group_test extends core_backup_backup_restore_base_testcase {
/**
* Test for backup/restore with customfields.
* @covers \backup_groups_structure_step
* @covers \restore_groups_structure_step
*/
public function test_backup_restore_group_with_customfields(): void {
$course1 = self::getDataGenerator()->create_course();
$course2 = self::getDataGenerator()->create_course();
$groupfieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_group',
'area' => 'group',
]);
$groupcustomfield = self::getDataGenerator()->create_custom_field([
'shortname' => 'testgroupcustomfield1',
'type' => 'text',
'categoryid' => $groupfieldcategory->get('id'),
]);
$groupingfieldcategory = self::getDataGenerator()->create_custom_field_category([
'component' => 'core_group',
'area' => 'grouping',
]);
$groupingcustomfield = self::getDataGenerator()->create_custom_field([
'shortname' => 'testgroupingcustomfield1',
'type' => 'text',
'categoryid' => $groupingfieldcategory->get('id'),
]);
$group1 = self::getDataGenerator()->create_group([
'courseid' => $course1->id,
'name' => 'Test group 1',
'customfield_testgroupcustomfield1' => 'Custom input for group1',
]);
$grouping1 = self::getDataGenerator()->create_grouping([
'courseid' => $course1->id,
'name' => 'Test grouping 1',
'customfield_testgroupingcustomfield1' => 'Custom input for grouping1',
]);
// Perform backup and restore.
$backupid = $this->perform_backup($course1);
$this->perform_restore($backupid, $course2);
// Test group.
$groups = groups_get_all_groups($course2->id);
$this->assertCount(1, $groups);
$group = reset($groups);
// Confirm the group is not same group as original one.
$this->assertNotEquals($group1->id, $group->id);
$this->assertEquals($group1->name, $group->name);
// Confirm custom field is restored in the new group.
$grouphandler = \core_group\customfield\group_handler::create();
$data = $grouphandler->export_instance_data_object($group->id);
$this->assertSame('Custom input for group1', $data->testgroupcustomfield1);
// Test grouping.
$groupings = groups_get_all_groupings($course2->id);
$this->assertCount(1, $groupings);
$grouping = reset($groupings);
// Confirm this is not same grouping as original one.
$this->assertNotEquals($grouping1->id, $grouping->id);
// Confirm custom field is restored in the new grouping.
$groupinghandler = \core_group\customfield\grouping_handler::create();
$data = $groupinghandler->export_instance_data_object($grouping->id);
$this->assertSame('Custom input for grouping1', $data->testgroupingcustomfield1);
}
}
@@ -0,0 +1,156 @@
<?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 core_backup;
use core_backup_backup_restore_base_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once('backup_restore_base_testcase.php');
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* Backup restore permission tests.
*
* @package core_backup
* @copyright Tomo Tsuyuki <tomotsuyuki@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_restore_permission_test extends core_backup_backup_restore_base_testcase {
/** @var stdClass A test course which is restored/imported from. */
protected $course1;
/** @var stdClass A test course which is restored/imported to. */
protected $course2;
/** @var stdClass A user for using in this test. */
protected $user;
/** @var string Capability name for using in this test. */
protected $capabilityname;
/** @var context_course Context instance for course1. */
protected $course1context;
/** @var context_course Context instance for course2. */
protected $course2context;
/**
* Setup test data.
*/
protected function setUp(): void {
global $DB;
parent::setUp();
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$this->course1 = $generator->create_course();
$this->course1context = \context_course::instance($this->course1->id);
$this->course2 = $generator->create_course();
$this->course2context = \context_course::instance($this->course2->id);
$this->capabilityname = 'enrol/manual:enrol';
$this->user = $generator->create_user();
// Set additional permission for course 1.
$teacherrole = $DB->get_record('role', ['shortname' => 'teacher'], '*', MUST_EXIST);
role_change_permission($teacherrole->id, $this->course1context, $this->capabilityname, CAP_ALLOW);
// Enrol to the courses.
$generator->enrol_user($this->user->id, $this->course1->id, $teacherrole->id);
$generator->enrol_user($this->user->id, $this->course2->id, $teacherrole->id);
}
/**
* Test having settings.
*/
public function test_having_settings(): void {
$this->assertEquals(0, get_config('backup', 'backup_import_permissions'));
$this->assertEquals(1, get_config('restore', 'restore_general_permissions'));
}
/**
* Test for restore with permission.
*/
public function test_backup_restore_with_permission(): void {
// Set default setting to restore with permission.
set_config('restore_general_permissions', 1, 'restore');
// Confirm course1 has the capability for the user.
$this->assertTrue(has_capability($this->capabilityname, $this->course1context, $this->user));
// Confirm course2 does not have the capability for the user.
$this->assertFalse(has_capability($this->capabilityname, $this->course2context, $this->user));
// Perform backup and restore.
$backupid = $this->perform_backup($this->course1);
$this->perform_restore($backupid, $this->course2);
// Confirm course2 has the capability for the user.
$this->assertTrue(has_capability($this->capabilityname, $this->course2context, $this->user));
}
/**
* Test for backup / restore without restore permission.
*/
public function test_backup_restore_without_permission(): void {
// Set default setting to restore without permission.
set_config('restore_general_permissions', 0, 'restore');
// Perform backup and restore.
$backupid = $this->perform_backup($this->course1);
$this->perform_restore($backupid, $this->course2);
// Confirm course2 does not have the capability for the user.
$this->assertFalse(has_capability($this->capabilityname, $this->course2context, $this->user));
}
/**
* Test for import with permission.
*/
public function test_backup_import_with_permission(): void {
// Set default setting to restore with permission.
set_config('backup_import_permissions', 1, 'backup');
// Perform import.
$this->perform_import($this->course1, $this->course2);
// Confirm course2 does not have the capability for the user.
$this->assertTrue(has_capability($this->capabilityname, $this->course2context, $this->user));
}
/**
* Test for import without permission.
*/
public function test_backup_import_without_permission(): void {
// Set default setting to restore without permission.
set_config('backup_import_permissions', 0, 'backup');
// Perform import.
$this->perform_import($this->course1, $this->course2);
// Confirm course2 does not have the capability for the user.
$this->assertFalse(has_capability($this->capabilityname, $this->course2context, $this->user));
}
}
+180
View File
@@ -0,0 +1,180 @@
<?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 core_backup;
use backup;
use core_backup_external;
use core_external\external_api;
use externallib_advanced_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/backup/externallib.php');
/**
* Backup webservice tests.
*
* @package core_backup
* @copyright 2020 onward The Moodle Users Association <https://moodleassociation.org/>
* @author Matt Porritt <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class externallib_test extends externallib_advanced_testcase {
/**
* Set up tasks for all tests.
*/
protected function setUp(): void {
global $CFG;
$this->resetAfterTest(true);
// Disable all loggers.
$CFG->backup_error_log_logger_level = backup::LOG_NONE;
$CFG->backup_output_indented_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level = backup::LOG_NONE;
$CFG->backup_database_logger_level = backup::LOG_NONE;
$CFG->backup_file_logger_level_extra = backup::LOG_NONE;
}
/**
* Test getting course copy progress.
*/
public function test_get_copy_progress(): void {
global $USER;
$this->setAdminUser();
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$courseid = $course->id;
// Mock up the form data for use in tests.
$formdata = new \stdClass;
$formdata->courseid = $courseid;
$formdata->fullname = 'foo';
$formdata->shortname = 'bar';
$formdata->category = 1;
$formdata->visible = 1;
$formdata->startdate = 1582376400;
$formdata->enddate = 0;
$formdata->idnumber = 123;
$formdata->userdata = 1;
$formdata->role_1 = 1;
$formdata->role_3 = 3;
$formdata->role_5 = 5;
$copydata = \copy_helper::process_formdata($formdata);
$copydetails = \copy_helper::create_copy($copydata);
$copydetails['operation'] = \backup::OPERATION_BACKUP;
$params = array('copies' => $copydetails);
$returnvalue = core_backup_external::get_copy_progress($params);
// We need to execute the return values cleaning process to simulate the web service server.
$returnvalue = external_api::clean_returnvalue(core_backup_external::get_copy_progress_returns(), $returnvalue);
$this->assertEquals(\backup::STATUS_AWAITING, $returnvalue[0]['status']);
$this->assertEquals(0, $returnvalue[0]['progress']);
$this->assertEquals($copydetails['backupid'], $returnvalue[0]['backupid']);
$this->assertEquals(\backup::OPERATION_BACKUP, $returnvalue[0]['operation']);
// We are expecting trace output during this test.
$this->expectOutputRegex("/$courseid/");
// Execute adhoc task and create the copy.
$now = time();
$task = \core\task\manager::get_next_adhoc_task($now);
$this->assertInstanceOf('\\core\\task\\asynchronous_copy_task', $task);
$task->execute();
\core\task\manager::adhoc_task_complete($task);
// Check the copy progress now.
$params = array('copies' => $copydetails);
$returnvalue = core_backup_external::get_copy_progress($params);
$returnvalue = external_api::clean_returnvalue(core_backup_external::get_copy_progress_returns(), $returnvalue);
$this->assertEquals(\backup::STATUS_FINISHED_OK, $returnvalue[0]['status']);
$this->assertEquals(1, $returnvalue[0]['progress']);
$this->assertEquals($copydetails['restoreid'], $returnvalue[0]['backupid']);
$this->assertEquals(\backup::OPERATION_RESTORE, $returnvalue[0]['operation']);
}
/**
* Test ajax submission of course copy process.
*/
public function test_submit_copy_form(): void {
global $DB;
$this->setAdminUser();
// Create a course with some availability data set.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$courseid = $course->id;
// Moodle form requires this for validation.
$sesskey = sesskey();
$_POST['sesskey'] = $sesskey;
// Mock up the form data for use in tests.
$formdata = new \stdClass;
$formdata->courseid = $courseid;
$formdata->returnto = '';
$formdata->returnurl = '';
$formdata->sesskey = $sesskey;
$formdata->_qf__core_backup_output_copy_form = 1;
$formdata->fullname = 'foo';
$formdata->shortname = 'bar';
$formdata->category = 1;
$formdata->visible = 1;
$formdata->startdate = array('day' => 5, 'month' => 5, 'year' => 2020, 'hour' => 0, 'minute' => 0);
$formdata->idnumber = 123;
$formdata->userdata = 1;
$formdata->role_1 = 1;
$formdata->role_3 = 3;
$formdata->role_5 = 5;
$urlform = http_build_query($formdata, '', '&'); // Take the form data and url encode it.
$jsonformdata = json_encode($urlform); // Take form string and JSON encode.
$returnvalue = core_backup_external::submit_copy_form($jsonformdata);
$returnjson = external_api::clean_returnvalue(core_backup_external::submit_copy_form_returns(), $returnvalue);
$copyids = json_decode($returnjson, true);
$backuprec = $DB->get_record('backup_controllers', array('backupid' => $copyids['backupid']));
$restorerec = $DB->get_record('backup_controllers', array('backupid' => $copyids['restoreid']));
// Check backup was completed successfully.
$this->assertEquals(backup::STATUS_AWAITING, $backuprec->status);
$this->assertEquals(0, $backuprec->progress);
$this->assertEquals('backup', $backuprec->operation);
// Check restore was completed successfully.
$this->assertEquals(backup::STATUS_REQUIRE_CONV, $restorerec->status);
$this->assertEquals(0, $restorerec->progress);
$this->assertEquals('restore', $restorerec->operation);
}
}
+540
View File
@@ -0,0 +1,540 @@
<?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/>.
/**
* Privacy provider tests.
*
* @package core_backup
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace core_backup\privacy;
use core_backup\privacy\provider;
use core_privacy\local\request\approved_userlist;
defined('MOODLE_INTERNAL') || die();
/**
* Privacy provider tests class.
*
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* Test getting the context for the user ID related to this plugin.
*/
public function test_get_contexts_for_userid(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user = $this->getDataGenerator()->create_user();
// Just insert directly into the 'backup_controllers' table.
$bcdata = (object) [
'backupid' => 1,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata);
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertCount(1, $contextlist);
$contextforuser = $contextlist->current();
$context = \context_course::instance($course->id);
$this->assertEquals($context->id, $contextforuser->id);
}
/**
* Test for provider::export_user_data().
*/
public function test_export_for_context(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user();
// Just insert directly into the 'backup_controllers' table.
$bcdata1 = (object) [
'backupid' => 1,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user1->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata1);
// Create another user who will perform a backup operation.
$user2 = $this->getDataGenerator()->create_user();
$bcdata2 = (object) [
'backupid' => 2,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user2->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata2);
// Create another backup_controllers record.
$bcdata3 = (object) [
'backupid' => 3,
'operation' => 'backup',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user1->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time() + DAYSECS,
'timemodified' => time() + DAYSECS,
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata3);
$coursecontext = \context_course::instance($course->id);
// Export all of the data for the context.
$this->export_context_data_for_user($user1->id, $coursecontext, 'core_backup');
$writer = \core_privacy\local\request\writer::with_context($coursecontext);
$this->assertTrue($writer->has_any_data());
$data = (array) $writer->get_data([get_string('backup'), $course->id]);
$this->assertCount(2, $data);
$bc1 = array_shift($data);
$this->assertEquals('restore', $bc1['operation']);
$bc2 = array_shift($data);
$this->assertEquals('backup', $bc2['operation']);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user();
// Just insert directly into the 'backup_controllers' table.
$bcdata1 = (object) [
'backupid' => 1,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user1->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata1);
// Create another user who will perform a backup operation.
$user2 = $this->getDataGenerator()->create_user();
$bcdata2 = (object) [
'backupid' => 2,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user2->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata2);
// Before deletion, we should have 2 operations.
$count = $DB->count_records('backup_controllers', ['itemid' => $course->id]);
$this->assertEquals(2, $count);
// Delete data based on context.
$coursecontext = \context_course::instance($course->id);
provider::delete_data_for_all_users_in_context($coursecontext);
// After deletion, the operations for that course should have been deleted.
$count = $DB->count_records('backup_controllers', ['itemid' => $course->id]);
$this->assertEquals(0, $count);
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user();
// Just insert directly into the 'backup_controllers' table.
$bcdata1 = (object) [
'backupid' => 1,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user1->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata1);
// Create another user who will perform a backup operation.
$user2 = $this->getDataGenerator()->create_user();
$bcdata2 = (object) [
'backupid' => 2,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user2->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata2);
// Before deletion, we should have 2 operations.
$count = $DB->count_records('backup_controllers', ['itemid' => $course->id]);
$this->assertEquals(2, $count);
$coursecontext = \context_course::instance($course->id);
$contextlist = new \core_privacy\local\request\approved_contextlist($user1, 'core_backup',
[\context_system::instance()->id, $coursecontext->id]);
provider::delete_data_for_user($contextlist);
// After deletion, the backup operation for the user should have been deleted.
$count = $DB->count_records('backup_controllers', ['itemid' => $course->id, 'userid' => $user1->id]);
$this->assertEquals(0, $count);
// Confirm we still have the other users record.
$bcs = $DB->get_records('backup_controllers');
$this->assertCount(1, $bcs);
$lastsubmission = reset($bcs);
$this->assertNotEquals($user1->id, $lastsubmission->userid);
}
/**
* Test that only users with a course and module context are fetched.
*/
public function test_get_users_in_context(): void {
global $DB;
$this->resetAfterTest();
$component = 'core_backup';
$course = $this->getDataGenerator()->create_course();
$activity = $this->getDataGenerator()->create_module('chat', ['course' => $course->id]);
$user = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$coursecontext = \context_course::instance($course->id);
$activitycontext = \context_module::instance($activity->cmid);
// The list of users for course context should return the user.
$userlist = new \core_privacy\local\request\userlist($coursecontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
// Create a course backup.
// Just insert directly into the 'backup_controllers' table.
$bcdata = (object) [
'backupid' => 1,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata);
// The list of users for the course context should return user.
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$expected = [$user->id];
$actual = $userlist->get_userids();
$this->assertEquals($expected, $actual);
// Create an activity backup.
// Just insert directly into the 'backup_controllers' table.
$bcdata = (object) [
'backupid' => 2,
'operation' => 'restore',
'type' => 'activity',
'itemid' => $activity->cmid,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user2->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata);
// The list of users for the course context should return user2.
$userlist = new \core_privacy\local\request\userlist($activitycontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(1, $userlist);
$expected = [$user2->id];
$actual = $userlist->get_userids();
$this->assertEquals($expected, $actual);
// The list of users for system context should not return any users.
$systemcontext = \context_system::instance();
$userlist = new \core_privacy\local\request\userlist($systemcontext, $component);
provider::get_users_in_context($userlist);
$this->assertCount(0, $userlist);
}
/**
* Test that data for users in approved userlist is deleted.
*/
public function test_delete_data_for_users(): void {
global $DB;
$this->resetAfterTest();
$component = 'core_backup';
// Create course1.
$course1 = $this->getDataGenerator()->create_course();
$coursecontext = \context_course::instance($course1->id);
// Create course2.
$course2 = $this->getDataGenerator()->create_course();
$coursecontext2 = \context_course::instance($course2->id);
// Create an activity.
$activity = $this->getDataGenerator()->create_module('chat', ['course' => $course1->id]);
$activitycontext = \context_module::instance($activity->cmid);
// Create user1.
$user1 = $this->getDataGenerator()->create_user();
// Create user2.
$user2 = $this->getDataGenerator()->create_user();
// Create user2.
$user3 = $this->getDataGenerator()->create_user();
// Just insert directly into the 'backup_controllers' table.
$bcdata1 = (object) [
'backupid' => 1,
'operation' => 'restore',
'type' => 'course',
'itemid' => $course1->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user1->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata1);
// Just insert directly into the 'backup_controllers' table.
$bcdata2 = (object) [
'backupid' => 2,
'operation' => 'backup',
'type' => 'course',
'itemid' => $course1->id,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user2->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata2);
// Just insert directly into the 'backup_controllers' table.
$bcdata3 = (object) [
'backupid' => 3,
'operation' => 'restore',
'type' => 'activity',
'itemid' => $activity->cmid,
'format' => 'moodle2',
'interactive' => 1,
'purpose' => 10,
'userid' => $user3->id,
'status' => 1000,
'execution' => 1,
'executiontime' => 0,
'checksum' => 'checksumyolo',
'timecreated' => time(),
'timemodified' => time(),
'controller' => ''
];
$DB->insert_record('backup_controllers', $bcdata3);
// The list of users for coursecontext should return user1 and user2.
$userlist1 = new \core_privacy\local\request\userlist($coursecontext, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(2, $userlist1);
$expected = [$user1->id, $user2->id];
$actual = $userlist1->get_userids();
$this->assertEqualsCanonicalizing($expected, $actual);
// The list of users for coursecontext2 should not return users.
$userlist2 = new \core_privacy\local\request\userlist($coursecontext2, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(0, $userlist2);
// The list of users for activitycontext should return user3.
$userlist3 = new \core_privacy\local\request\userlist($activitycontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(1, $userlist3);
$expected = [$user3->id];
$actual = $userlist3->get_userids();
$this->assertEquals($expected, $actual);
// Add user1 to the approved user list.
$approvedlist = new approved_userlist($coursecontext, $component, [$user1->id]);
// Delete user data using delete_data_for_user for usercontext1.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in coursecontext - The user list should now return only user2.
$userlist1 = new \core_privacy\local\request\userlist($coursecontext, $component);
provider::get_users_in_context($userlist1);
$this->assertCount(1, $userlist1);
$expected = [$user2->id];
$actual = $userlist1->get_userids();
$this->assertEquals($expected, $actual);
// Re-fetch users in activitycontext - The user list should not be empty (user3).
$userlist3 = new \core_privacy\local\request\userlist($activitycontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(1, $userlist3);
// Add user1 to the approved user list.
$approvedlist = new approved_userlist($activitycontext, $component, [$user3->id]);
// Delete user data using delete_data_for_user for usercontext1.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in activitycontext - The user list should not return any users.
$userlist3 = new \core_privacy\local\request\userlist($activitycontext, $component);
provider::get_users_in_context($userlist3);
$this->assertCount(0, $userlist3);
// User data should be only removed in the course context and module context.
$systemcontext = \context_system::instance();
// Add userlist2 to the approved user list in the system context.
$approvedlist = new approved_userlist($systemcontext, $component, $userlist2->get_userids());
// Delete user1 data using delete_data_for_user.
provider::delete_data_for_users($approvedlist);
// Re-fetch users in usercontext2 - The user list should not be empty (user2).
$userlist2 = new \core_privacy\local\request\userlist($coursecontext, $component);
provider::get_users_in_context($userlist2);
$this->assertCount(1, $userlist2);
}
}
@@ -0,0 +1,107 @@
<?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 core_backup;
defined('MOODLE_INTERNAL') || die();
// Include all the needed stuff.
global $CFG;
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
require_once($CFG->dirroot . '/question/engine/tests/helpers.php');
/**
* Decode links quiz restore tests.
*
* @package core_backup
* @copyright 2020 Ilya Tregubov <mattp@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class quiz_restore_decode_links_test extends \advanced_testcase {
/**
* Test restore_decode_rule class
*/
public function test_restore_quiz_decode_links(): void {
global $DB, $CFG, $USER;
$this->resetAfterTest(true);
$this->setAdminUser();
$generator = $this->getDataGenerator();
$course = $generator->create_course(
array('format' => 'topics', 'numsections' => 3,
'enablecompletion' => COMPLETION_ENABLED),
array('createsections' => true));
$quiz = $generator->create_module('quiz', array(
'course' => $course->id));
// Create questions.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$context = \context_course::instance($course->id);
$cat = $questiongenerator->create_question_category(array('contextid' => $context->id));
$question = $questiongenerator->create_question('multichoice', null, array('category' => $cat->id));
// Add to the quiz.
quiz_add_quiz_question($question->id, $quiz);
\mod_quiz\external\submit_question_version::execute(
$DB->get_field('quiz_slots', 'id', ['quizid' => $quiz->id, 'slot' => 1]), 1);
$questiondata = \question_bank::load_question_data($question->id);
$firstanswer = array_shift($questiondata->options->answers);
$DB->set_field('question_answers', 'answer', $CFG->wwwroot . '/course/view.php?id=' . $course->id,
['id' => $firstanswer->id]);
$secondanswer = array_shift($questiondata->options->answers);
$DB->set_field('question_answers', 'answer', $CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid,
['id' => $secondanswer->id]);
$thirdanswer = array_shift($questiondata->options->answers);
$DB->set_field('question_answers', 'answer', $CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid,
['id' => $thirdanswer->id]);
$fourthanswer = array_shift($questiondata->options->answers);
$DB->set_field('question_answers', 'answer', $CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid,
['id' => $fourthanswer->id]);
$newcm = duplicate_module($course, get_fast_modinfo($course)->get_cm($quiz->cmid));
$quizquestions = \mod_quiz\question\bank\qbank_helper::get_question_structure(
$newcm->instance, \context_module::instance($newcm->id));
$questionids = [];
foreach ($quizquestions as $quizquestion) {
if ($quizquestion->questionid) {
$questionids[] = $quizquestion->questionid;
}
}
list($condition, $param) = $DB->get_in_or_equal($questionids, SQL_PARAMS_NAMED, 'questionid');
$condition = 'WHERE qa.question ' . $condition;
$sql = "SELECT qa.id,
qa.answer
FROM {question_answers} qa
$condition";
$answers = $DB->get_records_sql($sql, $param);
$this->assertEquals($CFG->wwwroot . '/course/view.php?id=' . $course->id, $answers[$firstanswer->id]->answer);
$this->assertEquals($CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid, $answers[$secondanswer->id]->answer);
$this->assertEquals($CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid, $answers[$thirdanswer->id]->answer);
$this->assertEquals($CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid, $answers[$fourthanswer->id]->answer);
}
}
+180
View File
@@ -0,0 +1,180 @@
<?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/>.
defined('MOODLE_INTERNAL') || die();
// Include all the needed stuff.
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* Unit tests for how backup and restore handles role-related things.
*
* @package core_backup
* @copyright 2021 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class roles_backup_restore_test extends advanced_testcase {
/**
* Create a course where the (non-editing) Teacher role is overridden
* to have 'moodle/user:loginas' and 'moodle/site:accessallgroups'.
*
* @return stdClass the new course.
*/
protected function create_course_with_role_overrides(): stdClass {
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$teacher = $generator->create_user();
$context = context_course::instance($course->id);
$generator->enrol_user($teacher->id, $course->id, 'teacher');
$editingteacherrole = $this->get_role('teacher');
role_change_permission($editingteacherrole->id, $context, 'moodle/user:loginas', CAP_ALLOW);
role_change_permission($editingteacherrole->id, $context, 'moodle/site:accessallgroups', CAP_ALLOW);
return $course;
}
/**
* Get the role id from a shortname.
*
* @param string $shortname the role shortname.
* @return stdClass the role from the DB.
*/
protected function get_role(string $shortname): stdClass {
global $DB;
return $DB->get_record('role', ['shortname' => $shortname]);
}
/**
* Get an array capability => CAP_... constant for all the orverrides set for a given role on a given context.
*
* @param string $shortname role shortname.
* @param context $context context.
* @return array the overrides set here.
*/
protected function get_overrides_for_role_on_context(string $shortname, context $context): array {
$overridedata = get_capabilities_from_role_on_context($this->get_role($shortname), $context);
$overrides = [];
foreach ($overridedata as $override) {
$overrides[$override->capability] = $override->permission;
}
return $overrides;
}
/**
* Makes a backup of the course.
*
* @param stdClass $course The course object.
* @return string Unique identifier for this backup.
*/
protected function backup_course(\stdClass $course): string {
global $CFG, $USER;
// Turn off file logging, otherwise it can't delete the file (Windows).
$CFG->backup_file_logger_level = backup::LOG_NONE;
// Do backup with default settings. MODE_IMPORT means it will just
// create the directory and not zip it.
$bc = new \backup_controller(backup::TYPE_1COURSE, $course->id,
backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_IMPORT,
$USER->id);
$backupid = $bc->get_backupid();
$bc->execute_plan();
$bc->destroy();
return $backupid;
}
/**
* Restores a backup that has been made earlier.
*
* @param string $backupid The unique identifier of the backup.
* @param string $asroleshortname Which role in the new cousre the restorer should have.
* @return int The new course id.
*/
protected function restore_adding_to_course(string $backupid, string $asroleshortname): int {
global $CFG, $USER;
// Create course to restore into, and a user to do the restore.
$generator = $this->getDataGenerator();
$course = $generator->create_course();
$restorer = $generator->create_user();
$generator->enrol_user($restorer->id, $course->id, $asroleshortname);
$this->setUser($restorer);
// Turn off file logging, otherwise it can't delete the file (Windows).
$CFG->backup_file_logger_level = backup::LOG_NONE;
// Do restore to new course with default settings.
$rc = new \restore_controller($backupid, $course->id,
backup::INTERACTIVE_NO, backup::MODE_GENERAL, $USER->id,
backup::TARGET_CURRENT_ADDING);
$precheck = $rc->execute_precheck();
$this->assertTrue($precheck);
$rc->get_plan()->get_setting('role_assignments')->set_value(true);
$rc->get_plan()->get_setting('permissions')->set_value(true);
$rc->execute_plan();
$rc->destroy();
return $course->id;
}
public function test_restore_role_overrides_as_manager(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and back it up.
$course = $this->create_course_with_role_overrides();
$backupid = $this->backup_course($course);
// When manager restores, both role overrides should be restored.
$newcourseid = $this->restore_adding_to_course($backupid, 'manager');
// Verify.
$overrides = $this->get_overrides_for_role_on_context('teacher',
context_course::instance($newcourseid));
$this->assertArrayHasKey('moodle/user:loginas', $overrides);
$this->assertEquals(CAP_ALLOW, $overrides['moodle/user:loginas']);
$this->assertArrayHasKey('moodle/site:accessallgroups', $overrides);
$this->assertEquals(CAP_ALLOW, $overrides['moodle/site:accessallgroups']);
}
public function test_restore_role_overrides_as_teacher(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create a course and back it up.
$course = $this->create_course_with_role_overrides();
$backupid = $this->backup_course($course);
// When teacher restores, only the safe override should be restored.
$newcourseid = $this->restore_adding_to_course($backupid, 'editingteacher');
// Verify.
$overrides = $this->get_overrides_for_role_on_context('teacher',
context_course::instance($newcourseid));
$this->assertArrayNotHasKey('moodle/user:loginas', $overrides);
$this->assertArrayHasKey('moodle/site:accessallgroups', $overrides);
$this->assertEquals(CAP_ALLOW, $overrides['moodle/site:accessallgroups']);
}
}