697 lines
22 KiB
JavaScript
697 lines
22 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
https://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
const { AppConstants } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/AppConstants.sys.mjs"
|
|
);
|
|
const { JsonSchema } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/JsonSchema.sys.mjs"
|
|
);
|
|
const { UIState } = ChromeUtils.importESModule(
|
|
"resource://services-sync/UIState.sys.mjs"
|
|
);
|
|
const { ClientID } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ClientID.sys.mjs"
|
|
);
|
|
|
|
const LAST_BACKUP_TIMESTAMP_PREF_NAME =
|
|
"browser.backup.scheduled.last-backup-timestamp";
|
|
const LAST_BACKUP_FILE_NAME_PREF_NAME =
|
|
"browser.backup.scheduled.last-backup-file";
|
|
|
|
/** @type {nsIToolkitProfile} */
|
|
let currentProfile;
|
|
|
|
add_setup(function () {
|
|
// FOG needs to be initialized in order for data to flow.
|
|
Services.fog.initializeFOG();
|
|
|
|
// Much of this setup is copied from toolkit/profile/xpcshell/head.js. It is
|
|
// needed in order to put the xpcshell test environment into the state where
|
|
// it thinks its profile is the one pointed at by
|
|
// nsIToolkitProfileService.currentProfile.
|
|
let gProfD = do_get_profile();
|
|
let gDataHome = gProfD.clone();
|
|
gDataHome.append("data");
|
|
gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
|
let gDataHomeLocal = gProfD.clone();
|
|
gDataHomeLocal.append("local");
|
|
gDataHomeLocal.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
|
|
|
|
let xreDirProvider = Cc["@mozilla.org/xre/directory-provider;1"].getService(
|
|
Ci.nsIXREDirProvider
|
|
);
|
|
xreDirProvider.setUserDataDirectory(gDataHome, false);
|
|
xreDirProvider.setUserDataDirectory(gDataHomeLocal, true);
|
|
|
|
let profileSvc = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
|
|
Ci.nsIToolkitProfileService
|
|
);
|
|
|
|
let createdProfile = {};
|
|
let didCreate = profileSvc.selectStartupProfile(
|
|
["xpcshell"],
|
|
false,
|
|
AppConstants.UPDATE_CHANNEL,
|
|
"",
|
|
{},
|
|
{},
|
|
createdProfile
|
|
);
|
|
Assert.ok(didCreate, "Created a testing profile and set it to current.");
|
|
Assert.equal(
|
|
profileSvc.currentProfile,
|
|
createdProfile.value,
|
|
"Profile set to current"
|
|
);
|
|
|
|
currentProfile = createdProfile.value;
|
|
});
|
|
|
|
/**
|
|
* A utility function for testing BackupService.createBackup. This helper
|
|
* function:
|
|
*
|
|
* 1. Produces a backup of fake resources
|
|
* 2. Recovers the backup into a new profile directory
|
|
* 3. Ensures that the resources had their backup/recovery methods called
|
|
*
|
|
* @param {object} sandbox
|
|
* The Sinon sandbox to be used stubs and mocks. The test using this helper
|
|
* is responsible for creating and resetting this sandbox.
|
|
* @param {function(BackupService, BackupManifest): void} taskFn
|
|
* A function that is run once all default checks are done.
|
|
* After this function returns, all resources will be cleaned up.
|
|
* @returns {Promise<undefined>}
|
|
*/
|
|
async function testCreateBackupHelper(sandbox, taskFn) {
|
|
Services.telemetry.clearEvents();
|
|
Services.fog.testResetFOG();
|
|
|
|
// Handle for the metric for total byte size of staging folder
|
|
let totalBackupSizeHistogram = TelemetryTestUtils.getAndClearHistogram(
|
|
"BROWSER_BACKUP_TOTAL_BACKUP_SIZE"
|
|
);
|
|
// Handle for the metric for total byte size of single-file archive
|
|
let compressedArchiveSizeHistogram = TelemetryTestUtils.getAndClearHistogram(
|
|
"BROWSER_BACKUP_COMPRESSED_ARCHIVE_SIZE"
|
|
);
|
|
// Handle for the metric for total time taking by profile backup
|
|
let backupTimerHistogram = TelemetryTestUtils.getAndClearHistogram(
|
|
"BROWSER_BACKUP_TOTAL_BACKUP_TIME_MS"
|
|
);
|
|
|
|
const EXPECTED_CLIENT_ID = await ClientID.getClientID();
|
|
const EXPECTED_PROFILE_GROUP_ID = await ClientID.getProfileGroupID();
|
|
|
|
// Enable the scheduled backups pref so that backups can be deleted. We're
|
|
// not calling initBackupScheduler on the BackupService that we're
|
|
// constructing, so there's no danger of accidentally having a backup be
|
|
// created during this test if there's an idle period.
|
|
Services.prefs.setBoolPref("browser.backup.scheduled.enabled", true);
|
|
registerCleanupFunction(() => {
|
|
Services.prefs.clearUserPref("browser.backup.scheduled.enabled");
|
|
});
|
|
|
|
let fake1ManifestEntry = { fake1: "hello from 1" };
|
|
sandbox
|
|
.stub(FakeBackupResource1.prototype, "backup")
|
|
.resolves(fake1ManifestEntry);
|
|
sandbox.stub(FakeBackupResource1.prototype, "recover").resolves();
|
|
|
|
sandbox
|
|
.stub(FakeBackupResource2.prototype, "backup")
|
|
.rejects(new Error("Some failure to backup"));
|
|
sandbox.stub(FakeBackupResource2.prototype, "recover");
|
|
|
|
let fake3ManifestEntry = { fake3: "hello from 3" };
|
|
let fake3PostRecoveryEntry = { someData: "hello again from 3" };
|
|
sandbox
|
|
.stub(FakeBackupResource3.prototype, "backup")
|
|
.resolves(fake3ManifestEntry);
|
|
sandbox
|
|
.stub(FakeBackupResource3.prototype, "recover")
|
|
.resolves(fake3PostRecoveryEntry);
|
|
|
|
let bs = new BackupService({
|
|
FakeBackupResource1,
|
|
FakeBackupResource2,
|
|
FakeBackupResource3,
|
|
});
|
|
|
|
let fakeProfilePath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"createBackupTest"
|
|
);
|
|
|
|
let testTelemetryStateObject = {
|
|
clientID: "ed209123-04a1-04a1-04a1-c0ffeec0ffee",
|
|
};
|
|
await IOUtils.writeJSON(
|
|
PathUtils.join(PathUtils.profileDir, "datareporting", "state.json"),
|
|
testTelemetryStateObject
|
|
);
|
|
|
|
Assert.ok(!bs.state.lastBackupDate, "No backup date is stored in state.");
|
|
let { manifest, archivePath: backupFilePath } = await bs.createBackup({
|
|
profilePath: fakeProfilePath,
|
|
});
|
|
Assert.ok(bs.state.lastBackupDate, "The backup date was recorded.");
|
|
|
|
let legacyEvents = TelemetryTestUtils.getEvents(
|
|
{ category: "browser.backup", method: "created", object: "BackupService" },
|
|
{ process: "parent" }
|
|
);
|
|
Assert.equal(legacyEvents.length, 1, "Found the created legacy event.");
|
|
let events = Glean.browserBackup.created.testGetValue();
|
|
Assert.equal(events.length, 1, "Found the created Glean event.");
|
|
|
|
// Validate total backup time metrics were recorded
|
|
assertSingleTimeMeasurement(
|
|
Glean.browserBackup.totalBackupTime.testGetValue()
|
|
);
|
|
assertHistogramMeasurementQuantity(
|
|
backupTimerHistogram,
|
|
1,
|
|
"Should have collected a single measurement for total backup time"
|
|
);
|
|
|
|
Assert.ok(await IOUtils.exists(backupFilePath), "The backup file exists");
|
|
|
|
let archiveDateSuffix = bs.generateArchiveDateSuffix(
|
|
new Date(manifest.meta.date)
|
|
);
|
|
|
|
// We also expect the HTML file to have been written to the folder pointed
|
|
// at by browser.backups.location, within backupDirPath folder.
|
|
const EXPECTED_ARCHIVE_PATH = PathUtils.join(
|
|
bs.state.backupDirPath,
|
|
`${BackupService.BACKUP_FILE_NAME}_${manifest.meta.profileName}_${archiveDateSuffix}.html`
|
|
);
|
|
Assert.ok(
|
|
await IOUtils.exists(EXPECTED_ARCHIVE_PATH),
|
|
"Single-file backup archive was written."
|
|
);
|
|
Assert.equal(
|
|
backupFilePath,
|
|
EXPECTED_ARCHIVE_PATH,
|
|
"Backup was written to the configured destination folder"
|
|
);
|
|
|
|
let snapshotsDirectoryPath = PathUtils.join(
|
|
fakeProfilePath,
|
|
BackupService.PROFILE_FOLDER_NAME,
|
|
BackupService.SNAPSHOTS_FOLDER_NAME
|
|
);
|
|
let snapshotsDirectoryContentsPaths = await IOUtils.getChildren(
|
|
snapshotsDirectoryPath
|
|
);
|
|
let snapshotsDirectoryContents = await Promise.all(
|
|
snapshotsDirectoryContentsPaths.map(IOUtils.stat)
|
|
);
|
|
let snapshotsDirectorySubdirectories = snapshotsDirectoryContents.filter(
|
|
file => file.type === "directory"
|
|
);
|
|
Assert.equal(
|
|
snapshotsDirectorySubdirectories.length,
|
|
0,
|
|
"Snapshots directory should have had all staging folders cleaned up"
|
|
);
|
|
|
|
// 1 mebibyte minimum recorded value if total data size is under 1 mebibyte
|
|
// This assumes that these BackupService tests do not create sizable fake files
|
|
const SMALLEST_BACKUP_SIZE_BYTES = 1048576;
|
|
const SMALLEST_BACKUP_SIZE_MEBIBYTES = 1;
|
|
|
|
// Validate total (uncompressed profile data) size
|
|
let totalBackupSize = Glean.browserBackup.totalBackupSize.testGetValue();
|
|
Assert.equal(
|
|
totalBackupSize.count,
|
|
1,
|
|
"Should have collected a single measurement for the total backup size"
|
|
);
|
|
Assert.equal(
|
|
totalBackupSize.sum,
|
|
SMALLEST_BACKUP_SIZE_BYTES,
|
|
"Should have collected the right value for the total backup size"
|
|
);
|
|
TelemetryTestUtils.assertHistogram(
|
|
totalBackupSizeHistogram,
|
|
SMALLEST_BACKUP_SIZE_MEBIBYTES,
|
|
1
|
|
);
|
|
|
|
// Validate final archive (compressed/encrypted profile data + HTML) size
|
|
let compressedArchiveSize =
|
|
Glean.browserBackup.compressedArchiveSize.testGetValue();
|
|
Assert.equal(
|
|
compressedArchiveSize.count,
|
|
1,
|
|
"Should have collected a single measurement for the backup compressed archive size"
|
|
);
|
|
Assert.equal(
|
|
compressedArchiveSize.sum,
|
|
SMALLEST_BACKUP_SIZE_BYTES,
|
|
"Should have collected the right value for the backup compressed archive size"
|
|
);
|
|
TelemetryTestUtils.assertHistogram(
|
|
compressedArchiveSizeHistogram,
|
|
SMALLEST_BACKUP_SIZE_MEBIBYTES,
|
|
1
|
|
);
|
|
|
|
// Check that resources were called from highest to lowest backup priority.
|
|
sinon.assert.callOrder(
|
|
FakeBackupResource3.prototype.backup,
|
|
FakeBackupResource2.prototype.backup,
|
|
FakeBackupResource1.prototype.backup
|
|
);
|
|
|
|
let schema = await BackupService.MANIFEST_SCHEMA;
|
|
let validationResult = JsonSchema.validate(manifest, schema);
|
|
Assert.ok(validationResult.valid, "Schema matches manifest");
|
|
Assert.deepEqual(
|
|
Object.keys(manifest.resources).sort(),
|
|
["fake1", "fake3"],
|
|
"Manifest contains all expected BackupResource keys"
|
|
);
|
|
Assert.deepEqual(
|
|
manifest.resources.fake1,
|
|
fake1ManifestEntry,
|
|
"Manifest contains the expected entry for FakeBackupResource1"
|
|
);
|
|
Assert.deepEqual(
|
|
manifest.resources.fake3,
|
|
fake3ManifestEntry,
|
|
"Manifest contains the expected entry for FakeBackupResource3"
|
|
);
|
|
Assert.equal(
|
|
manifest.meta.legacyClientID,
|
|
EXPECTED_CLIENT_ID,
|
|
"The client ID was stored properly."
|
|
);
|
|
Assert.equal(
|
|
manifest.meta.profileGroupID,
|
|
EXPECTED_PROFILE_GROUP_ID,
|
|
"The profile group ID was stored properly."
|
|
);
|
|
Assert.equal(
|
|
manifest.meta.profileName,
|
|
currentProfile.name,
|
|
"The profile name was stored properly"
|
|
);
|
|
|
|
let recoveredProfilePath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"createBackupTestRecoveredProfile"
|
|
);
|
|
|
|
let recoveredProfile = await bs.recoverFromBackupArchive(
|
|
backupFilePath,
|
|
null,
|
|
false,
|
|
fakeProfilePath,
|
|
recoveredProfilePath
|
|
);
|
|
|
|
Assert.ok(
|
|
recoveredProfile.name.startsWith(currentProfile.name),
|
|
"Should maintain profile name across backup and restore"
|
|
);
|
|
|
|
// Check that resources were recovered from highest to lowest backup priority.
|
|
sinon.assert.callOrder(
|
|
FakeBackupResource3.prototype.recover,
|
|
FakeBackupResource1.prototype.recover
|
|
);
|
|
|
|
let postRecoveryFilePath = PathUtils.join(
|
|
recoveredProfilePath,
|
|
BackupService.POST_RECOVERY_FILE_NAME
|
|
);
|
|
Assert.ok(
|
|
await IOUtils.exists(postRecoveryFilePath),
|
|
"Should have created post-recovery data file"
|
|
);
|
|
let postRecoveryData = await IOUtils.readJSON(postRecoveryFilePath);
|
|
Assert.deepEqual(
|
|
postRecoveryData.fake3,
|
|
fake3PostRecoveryEntry,
|
|
"Should have post-recovery data from fake backup 3"
|
|
);
|
|
|
|
let newProfileTelemetryStateObject = await IOUtils.readJSON(
|
|
PathUtils.join(recoveredProfilePath, "datareporting", "state.json")
|
|
);
|
|
Assert.deepEqual(
|
|
testTelemetryStateObject,
|
|
newProfileTelemetryStateObject,
|
|
"Recovered profile inherited telemetry state from the profile that " +
|
|
"initiated recovery"
|
|
);
|
|
|
|
taskFn(bs, manifest);
|
|
|
|
await maybeRemovePath(backupFilePath);
|
|
await maybeRemovePath(fakeProfilePath);
|
|
await maybeRemovePath(recoveredProfilePath);
|
|
await maybeRemovePath(EXPECTED_ARCHIVE_PATH);
|
|
}
|
|
|
|
/**
|
|
* A utility function for testing BackupService.deleteLastBackup. This helper
|
|
* function:
|
|
*
|
|
* 1. Clears any pre-existing cached preference values for the last backup
|
|
* date and file name.
|
|
* 2. Uses testCreateBackupHelper to create a backup file.
|
|
* 3. Ensures that the state has been updated to reflect the created backup,
|
|
* and that the backup date and file name are cached to preferences.
|
|
* 4. Runs an optional async taskFn
|
|
* 5. Calls deleteLastBackup on the testCreateBackupHelper BackupService
|
|
* instance.
|
|
* 6. Checks that the BackupService state for the last backup date and file name
|
|
* have been cleared, and that the preferences caches of those values have
|
|
* also been cleared.
|
|
*
|
|
* @param {function(string): Promise<void>|null} taskFn
|
|
* An optional function that is run after we've created a backup, but just
|
|
* before calling deleteLastBackup(). It is passed the path to the created
|
|
* backup file.
|
|
* @returns {Promise<undefined>}
|
|
*/
|
|
async function testDeleteLastBackupHelper(taskFn) {
|
|
let sandbox = sinon.createSandbox();
|
|
|
|
// Clear any last backup filenames and timestamps that might be lingering
|
|
// from prior tests.
|
|
Services.prefs.clearUserPref(LAST_BACKUP_TIMESTAMP_PREF_NAME);
|
|
Services.prefs.clearUserPref(LAST_BACKUP_FILE_NAME_PREF_NAME);
|
|
|
|
await testCreateBackupHelper(sandbox, async (bs, _manifest) => {
|
|
Assert.ok(
|
|
bs.state.lastBackupDate,
|
|
"Should have a last backup date recorded."
|
|
);
|
|
Assert.ok(
|
|
bs.state.lastBackupFileName,
|
|
"Should have a last backup file name recorded."
|
|
);
|
|
Assert.ok(
|
|
Services.prefs.prefHasUserValue(LAST_BACKUP_TIMESTAMP_PREF_NAME),
|
|
"Last backup date was cached in preferences."
|
|
);
|
|
Assert.ok(
|
|
Services.prefs.prefHasUserValue(LAST_BACKUP_FILE_NAME_PREF_NAME),
|
|
"Last backup file name was cached in preferences."
|
|
);
|
|
const LAST_BACKUP_FILE_PATH = PathUtils.join(
|
|
bs.state.backupDirPath,
|
|
bs.state.lastBackupFileName
|
|
);
|
|
Assert.ok(
|
|
await IOUtils.exists(LAST_BACKUP_FILE_PATH),
|
|
"The backup file was created and is still on the disk."
|
|
);
|
|
|
|
if (taskFn) {
|
|
await taskFn(LAST_BACKUP_FILE_PATH);
|
|
}
|
|
|
|
await bs.deleteLastBackup();
|
|
|
|
Assert.equal(
|
|
bs.state.lastBackupDate,
|
|
null,
|
|
"Should have cleared the last backup date"
|
|
);
|
|
Assert.equal(
|
|
bs.state.lastBackupFileName,
|
|
"",
|
|
"Should have cleared the last backup file name"
|
|
);
|
|
Assert.ok(
|
|
!Services.prefs.prefHasUserValue(LAST_BACKUP_TIMESTAMP_PREF_NAME),
|
|
"Last backup date was cleared in preferences."
|
|
);
|
|
Assert.ok(
|
|
!Services.prefs.prefHasUserValue(LAST_BACKUP_FILE_NAME_PREF_NAME),
|
|
"Last backup file name was cleared in preferences."
|
|
);
|
|
Assert.ok(
|
|
!(await IOUtils.exists(LAST_BACKUP_FILE_PATH)),
|
|
"The backup file was deleted."
|
|
);
|
|
});
|
|
|
|
sandbox.restore();
|
|
}
|
|
|
|
/**
|
|
* Tests that calling BackupService.createBackup will call backup on each
|
|
* registered BackupResource, and that each BackupResource will have a folder
|
|
* created for them to write into. Tests in the signed-out state.
|
|
*/
|
|
add_task(async function test_createBackup_signed_out() {
|
|
let sandbox = sinon.createSandbox();
|
|
|
|
sandbox
|
|
.stub(UIState, "get")
|
|
.returns({ status: UIState.STATUS_NOT_CONFIGURED });
|
|
await testCreateBackupHelper(sandbox, (_bs, manifest) => {
|
|
Assert.equal(
|
|
manifest.meta.accountID,
|
|
undefined,
|
|
"Account ID should be undefined."
|
|
);
|
|
Assert.equal(
|
|
manifest.meta.accountEmail,
|
|
undefined,
|
|
"Account email should be undefined."
|
|
);
|
|
});
|
|
|
|
sandbox.restore();
|
|
});
|
|
|
|
/**
|
|
* Tests that calling BackupService.createBackup will call backup on each
|
|
* registered BackupResource, and that each BackupResource will have a folder
|
|
* created for them to write into. Tests in the signed-in state.
|
|
*/
|
|
add_task(async function test_createBackup_signed_in() {
|
|
let sandbox = sinon.createSandbox();
|
|
|
|
const TEST_UID = "ThisIsMyTestUID";
|
|
const TEST_EMAIL = "foxy@mozilla.org";
|
|
|
|
sandbox.stub(UIState, "get").returns({
|
|
status: UIState.STATUS_SIGNED_IN,
|
|
uid: TEST_UID,
|
|
email: TEST_EMAIL,
|
|
});
|
|
|
|
await testCreateBackupHelper(sandbox, (_bs, manifest) => {
|
|
Assert.equal(
|
|
manifest.meta.accountID,
|
|
TEST_UID,
|
|
"Account ID should be set properly."
|
|
);
|
|
Assert.equal(
|
|
manifest.meta.accountEmail,
|
|
TEST_EMAIL,
|
|
"Account email should be set properly."
|
|
);
|
|
});
|
|
|
|
sandbox.restore();
|
|
});
|
|
|
|
/**
|
|
* Tests that any internal file system errors in BackupService.createBackup
|
|
* do not bubble up any errors.
|
|
*/
|
|
add_task(
|
|
{
|
|
// Bug 1905724 - Need to find a way to deny write access to backup directory on Windows
|
|
skip_if: () => AppConstants.platform == "win",
|
|
},
|
|
async function test_createBackup_robustToFileSystemErrors() {
|
|
let sandbox = sinon.createSandbox();
|
|
Services.fog.testResetFOG();
|
|
// Handle for the metric for total time taking by profile backup
|
|
let backupTimerHistogram = TelemetryTestUtils.getAndClearHistogram(
|
|
"BROWSER_BACKUP_TOTAL_BACKUP_TIME_MS"
|
|
);
|
|
|
|
const TEST_UID = "ThisIsMyTestUID";
|
|
const TEST_EMAIL = "foxy@mozilla.org";
|
|
|
|
sandbox.stub(UIState, "get").returns({
|
|
status: UIState.STATUS_SIGNED_IN,
|
|
uid: TEST_UID,
|
|
email: TEST_EMAIL,
|
|
});
|
|
|
|
// Create a read-only fake profile folder to which the backup service
|
|
// won't be able to make writes
|
|
let inaccessibleProfilePath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"createBackupErrorInaccessible"
|
|
);
|
|
IOUtils.setPermissions(inaccessibleProfilePath, 0o444);
|
|
|
|
const bs = new BackupService({});
|
|
|
|
await bs
|
|
.createBackup({ profilePath: inaccessibleProfilePath })
|
|
.then(result => {
|
|
Assert.equal(result, null, "Should return null on error");
|
|
|
|
// Validate total backup time metrics were recorded
|
|
const totalBackupTime =
|
|
Glean.browserBackup.totalBackupTime.testGetValue();
|
|
Assert.equal(
|
|
totalBackupTime,
|
|
null,
|
|
"Should not have measured total backup time for failed backup"
|
|
);
|
|
assertHistogramMeasurementQuantity(backupTimerHistogram, 0);
|
|
})
|
|
.catch(() => {
|
|
// Trigger failure if there was an uncaught error
|
|
Assert.ok(false, "Should not have bubbled up an error");
|
|
})
|
|
.finally(async () => {
|
|
await IOUtils.remove(inaccessibleProfilePath, { recursive: true });
|
|
sandbox.restore();
|
|
});
|
|
}
|
|
);
|
|
|
|
/**
|
|
* Tests that if there's a post-recovery.json file in the profile directory
|
|
* when checkForPostRecovery() is called, that it is processed, and the
|
|
* postRecovery methods on the associated BackupResources are called with the
|
|
* entry values from the file.
|
|
*/
|
|
add_task(async function test_checkForPostRecovery() {
|
|
let sandbox = sinon.createSandbox();
|
|
|
|
let testProfilePath = await IOUtils.createUniqueDirectory(
|
|
PathUtils.tempDir,
|
|
"checkForPostRecoveryTest"
|
|
);
|
|
let fakePostRecoveryObject = {
|
|
[FakeBackupResource1.key]: "test 1",
|
|
[FakeBackupResource3.key]: "test 3",
|
|
};
|
|
await IOUtils.writeJSON(
|
|
PathUtils.join(testProfilePath, BackupService.POST_RECOVERY_FILE_NAME),
|
|
fakePostRecoveryObject
|
|
);
|
|
|
|
sandbox.stub(FakeBackupResource1.prototype, "postRecovery").resolves();
|
|
sandbox.stub(FakeBackupResource2.prototype, "postRecovery").resolves();
|
|
sandbox.stub(FakeBackupResource3.prototype, "postRecovery").resolves();
|
|
|
|
let bs = new BackupService({
|
|
FakeBackupResource1,
|
|
FakeBackupResource2,
|
|
FakeBackupResource3,
|
|
});
|
|
|
|
await bs.checkForPostRecovery(testProfilePath);
|
|
await bs.postRecoveryComplete;
|
|
|
|
Assert.ok(
|
|
FakeBackupResource1.prototype.postRecovery.calledOnce,
|
|
"FakeBackupResource1.postRecovery was called once"
|
|
);
|
|
Assert.ok(
|
|
FakeBackupResource2.prototype.postRecovery.notCalled,
|
|
"FakeBackupResource2.postRecovery was not called"
|
|
);
|
|
Assert.ok(
|
|
FakeBackupResource3.prototype.postRecovery.calledOnce,
|
|
"FakeBackupResource3.postRecovery was called once"
|
|
);
|
|
Assert.ok(
|
|
FakeBackupResource1.prototype.postRecovery.calledWith(
|
|
fakePostRecoveryObject[FakeBackupResource1.key]
|
|
),
|
|
"FakeBackupResource1.postRecovery was called with the expected argument"
|
|
);
|
|
Assert.ok(
|
|
FakeBackupResource3.prototype.postRecovery.calledWith(
|
|
fakePostRecoveryObject[FakeBackupResource3.key]
|
|
),
|
|
"FakeBackupResource3.postRecovery was called with the expected argument"
|
|
);
|
|
|
|
await IOUtils.remove(testProfilePath, { recursive: true });
|
|
sandbox.restore();
|
|
});
|
|
|
|
/**
|
|
* Tests that getBackupFileInfo updates backupFileInfo in the state with a subset
|
|
* of info from the fake SampleArchiveResult returned by sampleArchive().
|
|
*/
|
|
add_task(async function test_getBackupFileInfo() {
|
|
let sandbox = sinon.createSandbox();
|
|
|
|
const DATE = "2024-06-25T21:59:11.777Z";
|
|
const IS_ENCRYPTED = true;
|
|
|
|
let fakeSampleArchiveResult = {
|
|
isEncrypted: IS_ENCRYPTED,
|
|
startByteOffset: 26985,
|
|
contentType: "multipart/mixed",
|
|
archiveJSON: { version: 1, meta: { date: DATE }, encConfig: {} },
|
|
};
|
|
|
|
sandbox
|
|
.stub(BackupService.prototype, "sampleArchive")
|
|
.resolves(fakeSampleArchiveResult);
|
|
|
|
let bs = new BackupService();
|
|
|
|
await bs.getBackupFileInfo("fake-archive.html");
|
|
|
|
Assert.ok(
|
|
BackupService.prototype.sampleArchive.calledOnce,
|
|
"sampleArchive was called once"
|
|
);
|
|
|
|
Assert.deepEqual(
|
|
bs.state.backupFileInfo,
|
|
{ isEncrypted: IS_ENCRYPTED, date: DATE },
|
|
"State should match a subset from the archive sample."
|
|
);
|
|
|
|
sandbox.restore();
|
|
});
|
|
|
|
/**
|
|
* Tests that deleting the last backup will delete the last known backup file if
|
|
* it exists, and will clear the last backup timestamp and filename state
|
|
* properties and preferences.
|
|
*/
|
|
add_task(async function test_deleteLastBackup_file_exists() {
|
|
await testDeleteLastBackupHelper();
|
|
});
|
|
|
|
/**
|
|
* Tests that deleting the last backup does not reject if the last backup file
|
|
* does not exist, and will still clear the last backup timestamp and filename
|
|
* state properties and preferences.
|
|
*/
|
|
add_task(async function test__deleteLastBackup_file_does_not_exist() {
|
|
// Now delete the file ourselves before we call deleteLastBackup,
|
|
// so that it's missing from the disk.
|
|
await testDeleteLastBackupHelper(async lastBackupFilePath => {
|
|
await IOUtils.remove(lastBackupFilePath);
|
|
});
|
|
});
|