Files
moodle/backup/util/dbops/tests/restore_dbops_test.php
T
Eloy Lafuente (stronk7) 01148a0816 MDL-81522 phpunit: Add missing void return type to all tests
While this change is not 100% required now, it's good habit
and we are checking for it since Moodle 4.4.

All the changes in this commit have been applied automatically
using the moodle.PHPUnit.TestReturnType sniff and are, exclusively
adding the ": void" return types when missing.
2024-06-11 11:55:07 +02:00

365 lines
15 KiB
PHP

<?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 restore_controller_dbops;
use restore_dbops;
defined('MOODLE_INTERNAL') || die();
// Include all the needed stuff
global $CFG;
require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
/**
* @package core_backup
* @category test
* @copyright 2010 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_dbops_test extends \advanced_testcase {
/**
* Verify the xxx_ids_cached (in-memory backup_ids cache) stuff works as expected.
*
* Note that those private implementations are tested here by using the public
* backup_ids API and later performing low-level tests.
*/
public function test_backup_ids_cached(): void {
global $DB;
$dbman = $DB->get_manager(); // We are going to use database_manager services.
$this->resetAfterTest(true); // Playing with temp tables, better reset once finished.
// Some variables and objects for testing.
$restoreid = 'testrestoreid';
$mapping = new \stdClass();
$mapping->itemname = 'user';
$mapping->itemid = 1;
$mapping->newitemid = 2;
$mapping->parentitemid = 3;
$mapping->info = 'info';
// Create the backup_ids temp tables used by restore.
restore_controller_dbops::create_restore_temp_tables($restoreid);
// Send one mapping using the public api with defaults.
restore_dbops::set_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
// Get that mapping and verify everything is returned as expected.
$result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
$this->assertSame($mapping->itemname, $result->itemname);
$this->assertSame($mapping->itemid, $result->itemid);
$this->assertSame(0, $result->newitemid);
$this->assertSame(null, $result->parentitemid);
$this->assertSame(null, $result->info);
// Drop the backup_xxx_temp temptables manually, so memory cache won't be invalidated.
$dbman->drop_table(new \xmldb_table('backup_ids_temp'));
$dbman->drop_table(new \xmldb_table('backup_files_temp'));
// Verify the mapping continues returning the same info,
// now from cache (the table does not exist).
$result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
$this->assertSame($mapping->itemname, $result->itemname);
$this->assertSame($mapping->itemid, $result->itemid);
$this->assertSame(0, $result->newitemid);
$this->assertSame(null, $result->parentitemid);
$this->assertSame(null, $result->info);
// Recreate the temp table, just to drop it using the restore API in
// order to check that, then, the cache becomes invalid for the same request.
restore_controller_dbops::create_restore_temp_tables($restoreid);
restore_controller_dbops::drop_restore_temp_tables($restoreid);
// No cached info anymore, so the mapping request will arrive to
// DB leading to error (temp table does not exist).
try {
$result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
$this->fail('Expecting an exception, none occurred');
} catch (\Exception $e) {
$this->assertTrue($e instanceof \dml_exception);
$this->assertSame('Table "backup_ids_temp" does not exist', $e->getMessage());
}
// Create the backup_ids temp tables once more.
restore_controller_dbops::create_restore_temp_tables($restoreid);
// Send one mapping using the public api with complete values.
restore_dbops::set_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid,
$mapping->newitemid, $mapping->parentitemid, $mapping->info);
// Get that mapping and verify everything is returned as expected.
$result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
$this->assertSame($mapping->itemname, $result->itemname);
$this->assertSame($mapping->itemid, $result->itemid);
$this->assertSame($mapping->newitemid, $result->newitemid);
$this->assertSame($mapping->parentitemid, $result->parentitemid);
$this->assertSame($mapping->info, $result->info);
// Finally, drop the temp tables properly and get the DB error again (memory caches empty).
restore_controller_dbops::drop_restore_temp_tables($restoreid);
try {
$result = restore_dbops::get_backup_ids_record($restoreid, $mapping->itemname, $mapping->itemid);
$this->fail('Expecting an exception, none occurred');
} catch (\Exception $e) {
$this->assertTrue($e instanceof \dml_exception);
$this->assertSame('Table "backup_ids_temp" does not exist', $e->getMessage());
}
}
/**
* Data provider for {@link test_precheck_user()}
*/
public function precheck_user_provider() {
$emailmultiplier = [
'shortmail' => 'normalusername@example.com',
'longmail' => str_repeat('a', 100) // It's not validated, hence any string is ok.
];
$providercases = [];
foreach ($emailmultiplier as $emailk => $email) {
// Get the related cases.
$cases = $this->precheck_user_cases($email);
// Rename them (keys).
foreach ($cases as $key => $case) {
$providercases[$key . ' - ' . $emailk] = $case;
}
}
return $providercases;
}
/**
* Get all the cases implemented in {@link restore_dbops::precheck_users()}
*
* @param string $email
*/
private function precheck_user_cases($email) {
global $CFG;
$baseuserarr = [
'username' => 'normalusername',
'email' => $email,
'mnethostid' => $CFG->mnet_localhost_id,
'firstaccess' => 123456789,
'deleted' => 0,
'forceemailcleanup' => false, // Hack to force the DB record to have empty mail.
'forceduplicateadminallowed' => false]; // Hack to enable import_general_duplicate_admin_allowed.
return [
// Cases with samesite = true.
'samesite match existing (1A)' => [
'dbuser' => $baseuserarr,
'backupuser' => $baseuserarr,
'samesite' => true,
'outcome' => 'match'
],
'samesite match existing anon (1B)' => [
'dbuser' => array_merge($baseuserarr, [
'username' => 'anon01']),
'backupuser' => array_merge($baseuserarr, [
'id' => -1, 'username' => 'anon01', 'firstname' => 'anonfirstname01',
'lastname' => 'anonlastname01', 'email' => 'anon01@doesntexist.invalid']),
'samesite' => true,
'outcome' => 'match'
],
'samesite match existing deleted in db, alive in backup, by db username (1C)' => [
'dbuser' => array_merge($baseuserarr, [
'deleted' => 1]),
'backupuser' => array_merge($baseuserarr, [
'username' => 'this_wont_match']),
'samesite' => true,
'outcome' => 'match'
],
'samesite match existing deleted in db, alive in backup, by db email (1C)' => [
'dbuser' => array_merge($baseuserarr, [
'deleted' => 1]),
'backupuser' => array_merge($baseuserarr, [
'email' => 'this_wont_match']),
'samesite' => true,
'outcome' => 'match'
],
'samesite match existing alive in db, deleted in backup (1D)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, [
'deleted' => 1]),
'samesite' => true,
'outcome' => 'match'
],
'samesite conflict (1E)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, ['id' => -1]),
'samesite' => true,
'outcome' => false
],
'samesite create user (1F)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, [
'username' => 'newusername']),
'samesite' => false,
'outcome' => true
],
// Cases with samesite = false.
'no samesite match existing, by db email (2A1)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, [
'firstaccess' => 0]),
'samesite' => false,
'outcome' => 'match'
],
'no samesite match existing, by db firstaccess (2A1)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, [
'email' => 'this_wont_match@example.con']),
'samesite' => false,
'outcome' => 'match'
],
'no samesite match existing anon (2A1 too)' => [
'dbuser' => array_merge($baseuserarr, [
'username' => 'anon01']),
'backupuser' => array_merge($baseuserarr, [
'id' => -1, 'username' => 'anon01', 'firstname' => 'anonfirstname01',
'lastname' => 'anonlastname01', 'email' => 'anon01@doesntexist.invalid']),
'samesite' => false,
'outcome' => 'match'
],
'no samesite match dupe admin (2A2)' => [
'dbuser' => array_merge($baseuserarr, [
'username' => 'admin_old_site_id',
'forceduplicateadminallowed' => true]),
'backupuser' => array_merge($baseuserarr, [
'username' => 'admin']),
'samesite' => false,
'outcome' => 'match'
],
'no samesite match existing deleted in db, alive in backup, by db username (2B1)' => [
'dbuser' => array_merge($baseuserarr, [
'deleted' => 1]),
'backupuser' => array_merge($baseuserarr, [
'firstaccess' => 0]),
'samesite' => false,
'outcome' => 'match'
],
'no samesite match existing deleted in db, alive in backup, by db firstaccess (2B1)' => [
'dbuser' => array_merge($baseuserarr, [
'deleted' => 1]),
'backupuser' => array_merge($baseuserarr, [
'mail' => 'this_wont_match']),
'samesite' => false,
'outcome' => 'match'
],
'no samesite match existing deleted in db, alive in backup (2B2)' => [
'dbuser' => array_merge($baseuserarr, [
'deleted' => 1,
'forceemailcleanup' => true]),
'backupuser' => $baseuserarr,
'samesite' => false,
'outcome' => 'match'
],
'no samesite match existing alive in db, deleted in backup (2C)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, [
'deleted' => 1]),
'samesite' => false,
'outcome' => 'match'
],
'no samesite conflict (2D)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, [
'email' => 'anotheruser@example.com', 'firstaccess' => 0]),
'samesite' => false,
'outcome' => false
],
'no samesite create user (2E)' => [
'dbuser' => $baseuserarr,
'backupuser' => array_merge($baseuserarr, [
'username' => 'newusername']),
'samesite' => false,
'outcome' => true
],
];
}
/**
* Test restore precheck_user method
*
* @dataProvider precheck_user_provider
* @covers \restore_dbops::precheck_user()
*
* @param array $dbuser
* @param array $backupuser
* @param bool $samesite
* @param mixed $outcome
**/
public function test_precheck_user($dbuser, $backupuser, $samesite, $outcome): void {
global $DB;
$this->resetAfterTest();
$dbuser = (object)$dbuser;
$backupuser = (object)$backupuser;
$siteid = null;
// If the backup user must be deleted, simulate it (by temp inserting to DB, deleting and fetching it back).
if ($backupuser->deleted) {
$backupuser->id = $DB->insert_record('user', array_merge((array)$backupuser, ['deleted' => 0]));
delete_user($backupuser);
$backupuser = $DB->get_record('user', ['id' => $backupuser->id]);
$DB->delete_records('user', ['id' => $backupuser->id]);
unset($backupuser->id);
}
// Create the db user, normally.
$dbuser->id = $DB->insert_record('user', array_merge((array)$dbuser, ['deleted' => 0]));
$backupuser->id = $backupuser->id ?? $dbuser->id;
// We may want to enable the import_general_duplicate_admin_allowed setting and look for old admin records.
if ($dbuser->forceduplicateadminallowed) {
set_config('import_general_duplicate_admin_allowed', true, 'backup');
$siteid = 'old_site_id';
}
// If the DB user must be deleted, do it and fetch it back.
if ($dbuser->deleted) {
delete_user($dbuser);
// We may want to clean the mail field (old behavior, not containing the current md5(username).
if ($dbuser->forceemailcleanup) {
$DB->set_field('user', 'email', '', ['id' => $dbuser->id]);
}
}
// Get the dbuser record, because we may have changed it above.
$dbuser = $DB->get_record('user', ['id' => $dbuser->id]);
$method = (new \ReflectionClass('restore_dbops'))->getMethod('precheck_user');
$result = $method->invoke(null, $backupuser, $samesite, $siteid);
if (is_bool($result)) {
$this->assertSame($outcome, $result);
} else {
$outcome = $dbuser; // Outcome is not bool, matching found, so it must be the dbuser,
// Just check ids, it means the expected match has been found in database.
$this->assertSame($outcome->id, $result->id);
}
}
}