Proyectos de Subversion Moodle

Rev

Rev 1 | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |

<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.

namespace core_course\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->dirroot . '/course/format/tests/fixtures/format_theunittest.php');

/**
 * Course restore testcase.
 *
 * @package    core_course
 * @copyright  2016 Frédéric Massart - FMCorz.net
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
class restore_test extends \advanced_testcase {

    /**
     * Backup a course and return its backup ID.
     *
     * @param int $courseid The course ID.
     * @param int $userid The user doing the backup.
     * @return string
     */
    protected function backup_course($courseid, $userid = 2) {
        $backuptempdir = make_backup_temp_directory('');
        $packer = get_file_packer('application/vnd.moodle.backup');

        $bc = new backup_controller(backup::TYPE_1COURSE, $courseid, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO,
            backup::MODE_GENERAL, $userid);
        $bc->execute_plan();

        $results = $bc->get_results();
        $results['backup_destination']->extract_to_pathname($packer, "$backuptempdir/core_course_testcase");

        $bc->destroy();
        unset($bc);
        return 'core_course_testcase';
    }

    /**
     * Create a role with capabilities and permissions.
     *
     * @param string|array $caps Capability names.
     * @param int $perm Constant CAP_* to apply to the capabilities.
     * @return int The new role ID.
     */
    protected function create_role_with_caps($caps, $perm) {
        $caps = (array) $caps;
        $dg = $this->getDataGenerator();
        $roleid = $dg->create_role();
        foreach ($caps as $cap) {
            assign_capability($cap, $perm, $roleid, \context_system::instance()->id, true);
        }
        accesslib_clear_all_caches_for_unit_testing();
        return $roleid;
    }

    /**
     * Restore a course.
     *
     * @param int $backupid The backup ID.
     * @param int $courseid The course ID to restore in, or 0.
     * @param int $userid The ID of the user performing the restore.
     * @return stdClass The updated course object.
     */
    protected function restore_course($backupid, $courseid, $userid) {
        global $DB;

        $target = backup::TARGET_CURRENT_ADDING;
        if (!$courseid) {
            $target = backup::TARGET_NEW_COURSE;
            $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
            $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
        }

        $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_GENERAL, $userid, $target);
        $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
        $this->assertTrue($rc->execute_precheck());
        $rc->execute_plan();

        $course = $DB->get_record('course', array('id' => $rc->get_courseid()));

        $rc->destroy();
        unset($rc);
        return $course;
    }

    /**
     * Restore a course to an existing course.
     *
     * @param int $backupid The backup ID.
     * @param int $courseid The course ID to restore in.
     * @param int $userid The ID of the user performing the restore.
     * @return stdClass The updated course object.
     */
    protected function restore_to_existing_course($backupid, $courseid, $userid = 2) {
        return $this->restore_course($backupid, $courseid, $userid);
    }

    /**
     * Restore a course to a new course.
     *
     * @param int $backupid The backup ID.
     * @param int $userid The ID of the user performing the restore.
     * @return stdClass The new course object.
     */
    protected function restore_to_new_course($backupid, $userid = 2) {
        return $this->restore_course($backupid, 0, $userid);
    }

    /**
     * Restore a course.
     *
     * @param int $backupid The backup ID.
     * @param int $courseid The course ID to restore in, or 0.
     * @param int $userid The ID of the user performing the restore.
     * @param int $target THe target of the restore.
     *
     * @return stdClass The updated course object.
     */
    protected function async_restore_course($backupid, $courseid, $userid, $target) {
        global $DB;

        if (!$courseid) {
            $target = backup::TARGET_NEW_COURSE;
            $categoryid = $DB->get_field_sql("SELECT MIN(id) FROM {course_categories}");
            $courseid = restore_dbops::create_new_course('Tmp', 'tmp', $categoryid);
        }

        $rc = new restore_controller($backupid, $courseid, backup::INTERACTIVE_NO, backup::MODE_ASYNC, $userid, $target);
        $target == backup::TARGET_NEW_COURSE ?: $rc->get_plan()->get_setting('overwrite_conf')->set_value(true);
        $this->assertTrue($rc->execute_precheck());

        $restoreid = $rc->get_restoreid();
        $rc->destroy();

        // Create the adhoc task.
        $asynctask = new \core\task\asynchronous_restore_task();
        $asynctask->set_custom_data(array('backupid' => $restoreid));
        \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);

        $course = $DB->get_record('course', array('id' => $rc->get_courseid()));

        return $course;
    }

    /**
     * Restore a course to an existing course.
     *
     * @param int $backupid The backup ID.
     * @param int $courseid The course ID to restore in.
     * @param int $userid The ID of the user performing the restore.
     * @param int $target The type of restore we are performing.
     * @return stdClass The updated course object.
     */
    protected function async_restore_to_existing_course($backupid, $courseid,
        $userid = 2, $target = backup::TARGET_CURRENT_ADDING) {
        return $this->async_restore_course($backupid, $courseid, $userid, $target);
    }

    /**
     * Restore a course to a new course.
     *
     * @param int $backupid The backup ID.
     * @param int $userid The ID of the user performing the restore.
     * @return stdClass The new course object.
     */
    protected function async_restore_to_new_course($backupid, $userid = 2) {
        return $this->async_restore_course($backupid, 0, $userid, 0);
    }

    public function test_async_restore_existing_idnumber_in_new_course(): void {
        $this->resetAfterTest();

        $dg = $this->getDataGenerator();
        $c1 = $dg->create_course(['idnumber' => 'ABC']);
        $backupid = $this->backup_course($c1->id);
        $c2 = $this->async_restore_to_new_course($backupid);

        // The ID number is set empty.
        $this->assertEquals('', $c2->idnumber);
    }

    public function test_async_restore_course_info_in_existing_course(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();

        $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
        $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
        $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));

        $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.

        // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
            'startdate' => $startdate]);
        $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
        $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
            'startdate' => $startdate + 2 * WEEKSECS]);
        $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
        $backupid = $this->backup_course($c1->id);

        // The information is restored but adapted because names are already taken.
        $c2 = $this->async_restore_to_existing_course($backupid, $c2->id);
        $this->assertEquals('SN_1', $c2->shortname);
        $this->assertEquals('FN copy 1', $c2->fullname);
        $this->assertEquals('DESC', $c2->summary);
        $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
        $this->assertEquals($startdate, $c2->startdate);

        // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
        // Their dates are exactly the same as they were in the original modules.
        $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
        $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
        $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
        $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
    }

    public function test_async_restore_course_info_in_existing_course_delete_first(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();

        $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
        $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
        $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));

        $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.

        // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
            'startdate' => $startdate]);
        $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
        $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
            'startdate' => $startdate + 2 * WEEKSECS]);
        $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
        $backupid = $this->backup_course($c1->id);

        // The information is restored and the existing course settings is modified.
        $c2 = $this->async_restore_to_existing_course($backupid, $c2->id, 2, backup::TARGET_CURRENT_DELETING);
        $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);

        // Now course2 should have a new forum with the original forum deleted.
        $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
        $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
        $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
        $this->assertEmpty($restoredchat2);
    }

    public function test_restore_existing_idnumber_in_new_course(): void {
        $this->resetAfterTest();

        $dg = $this->getDataGenerator();
        $c1 = $dg->create_course(['idnumber' => 'ABC']);
        $backupid = $this->backup_course($c1->id);
        $c2 = $this->restore_to_new_course($backupid);

        // The ID number is set empty.
        $this->assertEquals('', $c2->idnumber);
    }

    public function test_restore_non_existing_idnumber_in_new_course(): void {
        global $DB;
        $this->resetAfterTest();

        $dg = $this->getDataGenerator();
        $c1 = $dg->create_course(['idnumber' => 'ABC']);
        $backupid = $this->backup_course($c1->id);

        $c1->idnumber = 'BCD';
        $DB->update_record('course', $c1);

        // The ID number changed.
        $c2 = $this->restore_to_new_course($backupid);
        $this->assertEquals('ABC', $c2->idnumber);
    }

    public function test_restore_existing_idnumber_in_existing_course(): void {
        global $DB;
        $this->resetAfterTest();

        $dg = $this->getDataGenerator();
        $c1 = $dg->create_course(['idnumber' => 'ABC']);
        $c2 = $dg->create_course(['idnumber' => 'DEF']);
        $backupid = $this->backup_course($c1->id);

        // The ID number does not change.
        $c2 = $this->restore_to_existing_course($backupid, $c2->id);
        $this->assertEquals('DEF', $c2->idnumber);

        $c1 = $DB->get_record('course', array('id' => $c1->id));
        $this->assertEquals('ABC', $c1->idnumber);
    }

    public function test_restore_non_existing_idnumber_in_existing_course(): void {
        global $DB;
        $this->resetAfterTest();

        $dg = $this->getDataGenerator();
        $c1 = $dg->create_course(['idnumber' => 'ABC']);
        $c2 = $dg->create_course(['idnumber' => 'DEF']);
        $backupid = $this->backup_course($c1->id);

        $c1->idnumber = 'XXX';
        $DB->update_record('course', $c1);

        // The ID number has changed.
        $c2 = $this->restore_to_existing_course($backupid, $c2->id);
        $this->assertEquals('ABC', $c2->idnumber);
    }

    public function test_restore_idnumber_in_existing_course_without_permissions(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();
        $u1 = $dg->create_user();

        $managers = get_archetype_roles('manager');
        $manager = array_shift($managers);
        $roleid = $this->create_role_with_caps('moodle/course:changeidnumber', CAP_PROHIBIT);
        $dg->role_assign($manager->id, $u1->id);
        $dg->role_assign($roleid, $u1->id);

        $c1 = $dg->create_course(['idnumber' => 'ABC']);
        $c2 = $dg->create_course(['idnumber' => 'DEF']);
        $backupid = $this->backup_course($c1->id);

        $c1->idnumber = 'XXX';
        $DB->update_record('course', $c1);

        // The ID number does not change.
        $c2 = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
        $this->assertEquals('DEF', $c2->idnumber);
    }

    public function test_restore_course_info_in_new_course(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();

        $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.

        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'startdate' => $startdate,
            'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
        $backupid = $this->backup_course($c1->id);

        // The information is restored but adapted because names are already taken.
        $c2 = $this->restore_to_new_course($backupid);
        $this->assertEquals('SN_1', $c2->shortname);
        $this->assertEquals('FN copy 1', $c2->fullname);
        $this->assertEquals('DESC', $c2->summary);
        $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
        $this->assertEquals($startdate, $c2->startdate);
    }

    public function test_restore_course_with_users(): void {
        global $DB;
        $this->resetAfterTest();
        $this->setAdminUser();
        $dg = $this->getDataGenerator();

        // Create a user and a course, enrol user in the course. Backup this course.
        $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
        $u1 = $dg->create_user(['firstname' => 'Olivia']);
        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'startdate' => $startdate,
            'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
        $dg->enrol_user($u1->id, $c1->id, 'student');
        $backupid = $this->backup_course($c1->id);

        // Delete the course and the user completely.
        delete_course($c1, false);
        delete_user($u1);
        $DB->delete_records('user', ['id' => $u1->id]);

        // Now restore this course, the user will be created and event user_created event will be triggered.
        $sink = $this->redirectEvents();
        $c2 = $this->restore_to_new_course($backupid);
        $events = $sink->get_events();
        $sink->close();

        $user = $DB->get_record('user', ['firstname' => 'Olivia'], '*', MUST_EXIST);
        $events = array_values(array_filter($events, function(\core\event\base $event) {
            return is_a($event, \core\event\user_created::class);
        }));
        $this->assertEquals(1, count($events));
        $this->assertEquals($user->id, $events[0]->relateduserid);
        $this->assertEquals($c2->id, $events[0]->other['courseid']);
        $this->assertStringContainsString("during restore of the course with id '{$c2->id}'",
            $events[0]->get_description());
    }

    public function test_restore_course_info_in_existing_course(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();

        $this->assertEquals(1, get_config('restore', 'restore_merge_course_shortname'));
        $this->assertEquals(1, get_config('restore', 'restore_merge_course_fullname'));
        $this->assertEquals(1, get_config('restore', 'restore_merge_course_startdate'));

        $startdate = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.

        // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
            'startdate' => $startdate]);
        $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
        $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
            'startdate' => $startdate + 2 * WEEKSECS]);
        $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);
        $backupid = $this->backup_course($c1->id);

        // The information is restored but adapted because names are already taken.
        $c2 = $this->restore_to_existing_course($backupid, $c2->id);
        $this->assertEquals('SN_1', $c2->shortname);
        $this->assertEquals('FN copy 1', $c2->fullname);
        $this->assertEquals('DESC', $c2->summary);
        $this->assertEquals(FORMAT_MOODLE, $c2->summaryformat);
        $this->assertEquals($startdate, $c2->startdate);

        // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
        // Their dates are exactly the same as they were in the original modules.
        $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
        $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
        $this->assertEquals($chat1->chattime, $restoredchat1->chattime);
        $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
    }

    public function test_restore_course_shortname_in_existing_course_without_permissions(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();
        $u1 = $dg->create_user();

        $managers = get_archetype_roles('manager');
        $manager = array_shift($managers);
        $roleid = $this->create_role_with_caps('moodle/course:changeshortname', CAP_PROHIBIT);
        $dg->role_assign($manager->id, $u1->id);
        $dg->role_assign($roleid, $u1->id);

        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
        $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);

        // The shortname does not change.
        $backupid = $this->backup_course($c1->id);
        $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
        $this->assertEquals($c2->shortname, $restored->shortname);
        $this->assertEquals('FN copy 1', $restored->fullname);
        $this->assertEquals('DESC', $restored->summary);
        $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
    }

    public function test_restore_course_fullname_in_existing_course_without_permissions(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();
        $u1 = $dg->create_user();

        $managers = get_archetype_roles('manager');
        $manager = array_shift($managers);
        $roleid = $this->create_role_with_caps('moodle/course:changefullname', CAP_PROHIBIT);
        $dg->role_assign($manager->id, $u1->id);
        $dg->role_assign($roleid, $u1->id);

        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
        $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);

        // The fullname does not change.
        $backupid = $this->backup_course($c1->id);
        $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
        $this->assertEquals('SN_1', $restored->shortname);
        $this->assertEquals($c2->fullname, $restored->fullname);
        $this->assertEquals('DESC', $restored->summary);
        $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
    }

    public function test_restore_course_summary_in_existing_course_without_permissions(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();
        $u1 = $dg->create_user();

        $managers = get_archetype_roles('manager');
        $manager = array_shift($managers);
        $roleid = $this->create_role_with_caps('moodle/course:changesummary', CAP_PROHIBIT);
        $dg->role_assign($manager->id, $u1->id);
        $dg->role_assign($roleid, $u1->id);

        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE]);
        $c2 = $dg->create_course(['shortname' => 'A1', 'fullname' => 'B1', 'summary' => 'C1', 'summaryformat' => FORMAT_PLAIN]);

        // The summary and format do not change.
        $backupid = $this->backup_course($c1->id);
        $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
        $this->assertEquals('SN_1', $restored->shortname);
        $this->assertEquals('FN copy 1', $restored->fullname);
        $this->assertEquals($c2->summary, $restored->summary);
        $this->assertEquals($c2->summaryformat, $restored->summaryformat);
    }

    public function test_restore_course_startdate_in_existing_course_without_permissions(): void {
        global $DB;
        $this->resetAfterTest();
        $dg = $this->getDataGenerator();

        $u1 = $dg->create_user();
        $managers = get_archetype_roles('manager');
        $manager = array_shift($managers);
        $roleid = $this->create_role_with_caps('moodle/restore:rolldates', CAP_PROHIBIT);
        $dg->role_assign($manager->id, $u1->id);
        $dg->role_assign($roleid, $u1->id);

        // Create two courses with different start dates,in each course create a chat that opens 1 week after the course start date.
        $startdate1 = mktime(12, 0, 0, 7, 1, 2016); // 01-Jul-2016.
        $startdate2 = mktime(12, 0, 0, 1, 13, 2000); // 13-Jan-2000.
        $c1 = $dg->create_course(['shortname' => 'SN', 'fullname' => 'FN', 'summary' => 'DESC', 'summaryformat' => FORMAT_MOODLE,
            'startdate' => $startdate1]);
        $chat1 = $dg->create_module('chat', ['name' => 'First', 'course' => $c1->id, 'chattime' => $c1->startdate + 1 * WEEKSECS]);
        $c2 = $dg->create_course(['shortname' => 'A', 'fullname' => 'B', 'summary' => 'C', 'summaryformat' => FORMAT_PLAIN,
            'startdate' => $startdate2]);
        $chat2 = $dg->create_module('chat', ['name' => 'Second', 'course' => $c2->id, 'chattime' => $c2->startdate + 1 * WEEKSECS]);

        // The startdate does not change.
        $backupid = $this->backup_course($c1->id);
        $restored = $this->restore_to_existing_course($backupid, $c2->id, $u1->id);
        $this->assertEquals('SN_1', $restored->shortname);
        $this->assertEquals('FN copy 1', $restored->fullname);
        $this->assertEquals('DESC', $restored->summary);
        $this->assertEquals(FORMAT_MOODLE, $restored->summaryformat);
        $this->assertEquals($startdate2, $restored->startdate);

        // Now course c2 has two chats - one ('Second') was already there and one ('First') was restored from the backup.
        // Start date of the restored chat ('First') was changed to be 1 week after the c2 start date.
        $restoredchat1 = $DB->get_record('chat', ['name' => 'First', 'course' => $c2->id]);
        $restoredchat2 = $DB->get_record('chat', ['name' => 'Second', 'course' => $c2->id]);
        $this->assertNotEquals($chat1->chattime, $restoredchat1->chattime);
        $this->assertEquals($chat2->chattime, $restoredchat2->chattime);
        $this->assertEquals($c2->startdate + 1 * WEEKSECS, $restoredchat2->chattime);
    }

    /**
     * Tests course restore with editor in course format.
     *
     * @author Matthew Hilton
     * @covers \core_courseformat
     */
    public function test_restore_editor_courseformat(): void {
        $this->resetAfterTest();

        // Setup user with restore permissions.
        $dg = $this->getDataGenerator();
        $u1 = $dg->create_user();

        $managers = get_archetype_roles('manager');
        $manager = array_shift($managers);
        $dg->role_assign($manager->id, $u1->id);

        // Create a course with an editor item in the course format.
        $courseformatoptiondata = (object) [
            "hideoddsections" => 1,
            'summary_editor' => [
                'text' => '<p>Somewhere over the rainbow</p><p>The <b>quick</b> brown fox jumpos over the lazy dog.</p>',
                'format' => 1
            ]
        ];
        $course1 = $dg->create_course(['format' => 'theunittest']);
        $course2 = $dg->create_course(['format' => 'theunittest']);
        $this->assertEquals('theunittest', $course1->format);
        course_create_sections_if_missing($course1, array(0, 1));

        // Set the course format.
        $courseformat = course_get_format($course1);
        $courseformat->update_course_format_options($courseformatoptiondata);

        // Backup and restore the course.
        $backupid = $this->backup_course($course1->id);
        $this->restore_to_existing_course($backupid, $course2->id, $u1->id);

        // Get the restored course format.
        $restoredformat = course_get_format($course2);
        $restoredformatoptions = $restoredformat->get_format_options();

        $this->assertEqualsCanonicalizing($courseformatoptiondata, (object) $restoredformatoptions);
    }
}