From bc9c536335cee32bca87bf45f000ec8cd62c5d95 Mon Sep 17 00:00:00 2001 From: Cameron Ball Date: Thu, 12 May 2022 09:53:53 +0800 Subject: [PATCH] MDL-74548 backup: Unit tests for course copy refactor --- backup/controller/tests/controller_test.php | 13 ++ .../helper/tests/copy_helper_test.php} | 220 +++++++++++++++++- 2 files changed, 227 insertions(+), 6 deletions(-) rename backup/{tests/course_copy_test.php => util/helper/tests/copy_helper_test.php} (71%) diff --git a/backup/controller/tests/controller_test.php b/backup/controller/tests/controller_test.php index f1271aca32a6e..f2ade3d613674 100644 --- a/backup/controller/tests/controller_test.php +++ b/backup/controller/tests/controller_test.php @@ -71,6 +71,19 @@ protected function setUp(): void { $CFG->backup_file_logger_level_extra = backup::LOG_NONE; } + /** + * Test get_copy + * + * @covers \restore_controller::get_copy + */ + public function test_restore_controller_get_copy() { + $copydata = (object)["some" => "copydata"]; + $rc = new \restore_controller(1729, $this->courseid, backup::INTERACTIVE_NO, backup::MODE_COPY, + $this->userid, backup::TARGET_NEW_COURSE, null, backup::RELEASESESSION_NO, $copydata); + + $this->assertEquals($copydata, $rc->get_copy()); + } + /** * Test instantiating a restore controller for a course copy without providing copy data. * diff --git a/backup/tests/course_copy_test.php b/backup/util/helper/tests/copy_helper_test.php similarity index 71% rename from backup/tests/course_copy_test.php rename to backup/util/helper/tests/copy_helper_test.php index 3280f8e2b9fa7..1080fe56e66cd 100644 --- a/backup/tests/course_copy_test.php +++ b/backup/util/helper/tests/copy_helper_test.php @@ -32,8 +32,9 @@ * @copyright 2020 onward The Moodle Users Association * @author Matt Porritt * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \copy_helper */ -class course_copy_test extends \advanced_testcase { +class copy_helper_test extends \advanced_testcase { /** * @@ -143,8 +144,150 @@ protected function setUp(): void { $CFG->backup_file_logger_level_extra = backup::LOG_NONE; } + /** + * Test process form data with invalid data. + * + * @covers ::process_formdata + */ + public function test_process_formdata_missing_fields() { + $this->expectException(\moodle_exception::class); + \copy_helper::process_formdata(new \stdClass); + } + + /** + * Test processing form data. + * + * @covers ::process_formdata + */ + public function test_process_formdata() { + $validformdata = [ + 'courseid' => 1729, + 'fullname' => 'Taxicab Numbers', + 'shortname' => 'Taxi101', + 'category' => 2, + 'visible' => 1, + 'startdate' => 87539319, + 'enddate' => 6963472309248, + 'idnumber' => 1730, + 'userdata' => 1 + ]; + + $roles = [ + 'role_one' => 1, + 'role_two' => 2, + 'role_three' => 0 + ]; + + $expected = (object)array_merge($validformdata, ['keptroles' => []]); + $expected->keptroles = [1, 2]; + $processed = \copy_helper::process_formdata( + (object)array_merge( + $validformdata, + $roles, + ['extra' => 'stuff', 'remove' => 'this']) + ); + + $this->assertEquals($expected, $processed); + } + + /** + * Test orphaned controller cleanup. + * + * @covers ::cleanup_orphaned_copy_controllers + */ + public function test_cleanup_orphaned_copy_controllers() { + global $DB; + + // Mock up the form data. + $formdata = new \stdClass; + $formdata->courseid = $this->course->id; + $formdata->fullname = 'foo'; + $formdata->shortname = 'data1'; + $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; + + $copies = []; + for ($i = 0; $i < 5; $i++) { + $formdata->shortname = 'data' . $i; + $copies[] = \copy_helper::create_copy($formdata); + } + + // Delete one of the restore controllers. Simulates a situation where copy creation + // interrupted and the restore controller never gets created. + $DB->delete_records('backup_controllers', ['backupid' => $copies[0]['restoreid']]); + + // Set a backup/restore controller pair to be in an intermediate state. + \backup_controller::load_controller($copies[2]['backupid'])->set_status(backup::STATUS_FINISHED_OK); + + // Set a backup/restore controller pair to completed. + \backup_controller::load_controller($copies[3]['backupid'])->set_status(backup::STATUS_FINISHED_OK); + \restore_controller::load_controller($copies[3]['restoreid'])->set_status(backup::STATUS_FINISHED_OK); + + // Set a backup/restore controller pair to have a failed backup. + \backup_controller::load_controller($copies[4]['backupid'])->set_status(backup::STATUS_FINISHED_ERR); + + // Create some backup/restore controllers that are unrelated to course copies. + $bc = new \backup_controller(backup::TYPE_1COURSE, 1, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_ASYNC, + 2, backup::RELEASESESSION_YES); + $rc = new \restore_controller('restore-test-1729', 1, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 1, 2); + $rc->save_controller(); + $unrelatedvanillacontrollers = ['backupid' => $bc->get_backupid(), 'restoreid' => $rc->get_restoreid()]; + + $bc = new \backup_controller(backup::TYPE_1COURSE, 1, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_ASYNC, + 2, backup::RELEASESESSION_YES); + $rc = new \restore_controller('restore-test-1729', 1, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 1, 2); + $bc->set_status(backup::STATUS_FINISHED_OK); + $rc->set_status(backup::STATUS_FINISHED_OK); + $unrelatedfinishedcontrollers = ['backupid' => $bc->get_backupid(), 'restoreid' => $rc->get_restoreid()]; + + $bc = new \backup_controller(backup::TYPE_1COURSE, 1, backup::FORMAT_MOODLE, backup::INTERACTIVE_NO, backup::MODE_ASYNC, + 2, backup::RELEASESESSION_YES); + $rc = new \restore_controller('restore-test-1729', 1, backup::INTERACTIVE_NO, backup::MODE_ASYNC, 1, 2); + $bc->set_status(backup::STATUS_FINISHED_ERR); + $rc->set_status(backup::STATUS_FINISHED_ERR); + $unrelatedfailedcontrollers = ['backupid' => $bc->get_backupid(), 'restoreid' => $rc->get_restoreid()]; + + // Clean up the backup_controllers table. + $records = $DB->get_records('backup_controllers', null, '', 'id, backupid, status, operation, purpose, timecreated'); + \copy_helper::cleanup_orphaned_copy_controllers($records, 0); + + // Retrieve them again and check. + $records = $DB->get_records('backup_controllers', null, '', 'backupid, status'); + + // Verify the backup associated with the deleted restore is marked as failed. + $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$copies[0]['backupid']]->status); + + // Verify other controllers remain untouched. + $this->assertEquals(backup::STATUS_AWAITING, $records[$copies[1]['backupid']]->status); + $this->assertEquals(backup::STATUS_REQUIRE_CONV, $records[$copies[1]['restoreid']]->status); + $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$copies[2]['backupid']]->status); + $this->assertEquals(backup::STATUS_REQUIRE_CONV, $records[$copies[2]['restoreid']]->status); + $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$copies[3]['restoreid']]->status); + $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$copies[3]['backupid']]->status); + + // Verify that the restore associated with the failed backup is also marked as failed. + $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$copies[4]['restoreid']]->status); + + // Verify that the unrelated controllers remain unchanged. + $this->assertEquals(backup::STATUS_AWAITING, $records[$unrelatedvanillacontrollers['backupid']]->status); + $this->assertEquals(backup::STATUS_REQUIRE_CONV, $records[$unrelatedvanillacontrollers['restoreid']]->status); + $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$unrelatedfinishedcontrollers['backupid']]->status); + $this->assertEquals(backup::STATUS_FINISHED_OK, $records[$unrelatedfinishedcontrollers['restoreid']]->status); + $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$unrelatedfailedcontrollers['backupid']]->status); + $this->assertEquals(backup::STATUS_FINISHED_ERR, $records[$unrelatedfailedcontrollers['restoreid']]->status); + } + /** * Test creating a course copy. + * + * @covers ::create_copy */ public function test_create_copy() { @@ -197,6 +340,8 @@ public function test_create_copy() { /** * Test getting the current copies. + * + * @covers ::get_copies */ public function test_get_copies() { global $USER; @@ -267,8 +412,69 @@ public function test_get_copies() { $this->assertEmpty($copies); } + /** + * Test getting the current copies when they are in an invalid state. + * + * @covers ::get_copies + */ + public function test_get_copies_invalid_state() { + global $DB, $USER; + + // Mock up the form data. + $formdata = new \stdClass; + $formdata->courseid = $this->course->id; + $formdata->fullname = 'foo'; + $formdata->shortname = 'bar'; + $formdata->category = 1; + $formdata->visible = 1; + $formdata->startdate = 1582376400; + $formdata->enddate = 0; + $formdata->idnumber = ''; + $formdata->userdata = 1; + $formdata->role_1 = 1; + $formdata->role_3 = 3; + $formdata->role_5 = 5; + + $formdata2 = clone ($formdata); + $formdata2->shortname = 'tree'; + + // Create some copies. + $copydata = \copy_helper::process_formdata($formdata); + $result = \copy_helper::create_copy($copydata); + $copydata2 = \copy_helper::process_formdata($formdata2); + $result2 = \copy_helper::create_copy($copydata2); + + $copies = \copy_helper::get_copies($USER->id); + + // Verify get_copies gives back both backup controllers. + $this->assertEqualsCanonicalizing([$result['backupid'], $result2['backupid']], array_column($copies, 'backupid')); + + // Set one of the backup controllers to failed, this should cause it to not be present. + \backup_controller::load_controller($result['backupid'])->set_status(backup::STATUS_FINISHED_ERR); + $copies = \copy_helper::get_copies($USER->id); + + // Verify there is only one backup listed, and that it is not the failed one. + $this->assertEqualsCanonicalizing([$result2['backupid']], array_column($copies, 'backupid')); + + // Set the controller back to awaiting. + \backup_controller::load_controller($result['backupid'])->set_status(backup::STATUS_AWAITING); + $copies = \copy_helper::get_copies($USER->id); + + // Verify both backup controllers are back. + $this->assertEqualsCanonicalizing([$result['backupid'], $result2['backupid']], array_column($copies, 'backupid')); + + // Delete the restore controller for one of the copies, this should cause it to not be present. + $DB->delete_records('backup_controllers', ['backupid' => $result['restoreid']]); + $copies = \copy_helper::get_copies($USER->id); + + // Verify there is only one backup listed, and that it is not the failed one. + $this->assertEqualsCanonicalizing([$result2['backupid']], array_column($copies, 'backupid')); + } + /** * Test getting the current copies for specific course. + * + * @covers ::get_copies */ public function test_get_copies_course() { global $USER; @@ -299,6 +505,8 @@ public function test_get_copies_course() { /** * Test getting the current copies if course has been deleted. + * + * @covers ::get_copies */ public function test_get_copies_course_deleted() { global $USER; @@ -329,7 +537,7 @@ public function test_get_copies_course_deleted() { $this->assertEmpty($copies); } - /* + /** * Test course copy. */ public function test_course_copy() { @@ -406,7 +614,7 @@ public function test_course_copy() { $this->assertEquals(2, count($discussions)); } - /* + /** * Test course copy, not including any users (or data). */ public function test_course_copy_no_users() { @@ -475,7 +683,7 @@ public function test_course_copy_no_users() { } - /* + /** * Test course copy, including students and their data. */ public function test_course_copy_students_data() { @@ -544,7 +752,7 @@ public function test_course_copy_students_data() { $this->assertEquals($this->courseusers[0], $users[$this->courseusers[0]]->id); } - /* + /** * Test course copy, not including any users (or data). */ public function test_course_copy_no_data() { @@ -612,7 +820,7 @@ public function test_course_copy_no_data() { $this->assertEquals(count($this->courseusers), count($users)); } - /* + /** * Test instantiation with incomplete formdata. */ public function test_malformed_instantiation() {