Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'android' OR os == 'android'
- Manifest: toolkit/components/passwordmgr/test/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
/**
* Tests the LoginStore object.
*/
"use strict";
// Globals
ChromeUtils.defineESModuleGetters(this, {
LoginStore: "resource://gre/modules/LoginStore.sys.mjs",
});
const TEST_STORE_FILE_NAME = "test-logins.json";
const MAX_DATE_MS = 8640000000000000;
// Tests
/**
* Saves login data to a file, then reloads it.
*/
add_task(async function test_save_reload() {
let storeForSave = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
// The "load" method must be called before preparing the data to be saved.
await storeForSave.load();
let rawLoginData = {
id: storeForSave.data.nextId++,
httpRealm: null,
usernameField: "field_" + String.fromCharCode(533, 537, 7570, 345),
passwordField: "field_" + String.fromCharCode(421, 259, 349, 537),
encryptedUsername: "(test)",
encryptedPassword: "(test)",
guid: "(test)",
encType: Ci.nsILoginManagerCrypto.ENCTYPE_SDR,
timeCreated: Date.now(),
timeLastUsed: Date.now(),
timePasswordChanged: Date.now(),
timesUsed: 1,
};
storeForSave.data.logins.push(rawLoginData);
await storeForSave._save();
// Test the asynchronous initialization path.
let storeForLoad = new LoginStore(storeForSave.path);
await storeForLoad.load();
Assert.equal(storeForLoad.data.logins.length, 1);
Assert.deepEqual(storeForLoad.data.logins[0], rawLoginData);
// Test the synchronous initialization path.
storeForLoad = new LoginStore(storeForSave.path);
storeForLoad.ensureDataReady();
Assert.equal(storeForLoad.data.logins.length, 1);
Assert.deepEqual(storeForLoad.data.logins[0], rawLoginData);
});
/**
* Checks that loading from a missing file results in empty arrays.
*/
add_task(async function test_load_empty() {
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
Assert.equal(false, await IOUtils.exists(store.path));
await store.load();
Assert.equal(false, await IOUtils.exists(store.path));
Assert.equal(store.data.logins.length, 0);
});
/**
* Checks that saving empty data still overwrites any existing file.
*/
add_task(async function test_save_empty() {
const store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
await store.load();
await IOUtils.writeUTF8(store.path, "", { writeMode: "create" });
await store._save();
Assert.ok(await IOUtils.exists(store.path));
});
/**
* Loads data from a string in a predefined format. The purpose of this test is
* to verify that the JSON format used in previous versions can be loaded.
*/
add_task(async function test_load_string_predefined() {
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
let string =
'{"logins":[{' +
'"id":1,' +
'"httpRealm":null,' +
'"usernameField":"usernameField",' +
'"passwordField":"passwordField",' +
'"encryptedUsername":"(test)",' +
'"encryptedPassword":"(test)",' +
'"guid":"(test)",' +
'"encType":1,' +
'"timeCreated":1262304000000,' +
'"timeLastUsed":1262390400000,' +
'"timePasswordChanged":1262476800000,' +
'"timesUsed":1}],"disabledHosts":[' +
await IOUtils.writeUTF8(store.path, string, {
tmpPath: store.path + ".tmp",
});
await store.load();
Assert.equal(store.data.logins.length, 1);
Assert.deepEqual(store.data.logins[0], {
id: 1,
httpRealm: null,
usernameField: "usernameField",
passwordField: "passwordField",
encryptedUsername: "(test)",
encryptedPassword: "(test)",
guid: "(test)",
encType: Ci.nsILoginManagerCrypto.ENCTYPE_SDR,
timeCreated: 1262304000000,
timeLastUsed: 1262390400000,
timePasswordChanged: 1262476800000,
timesUsed: 1,
});
});
/**
* Loads login data from a malformed JSON string.
*/
add_task(async function test_load_string_malformed() {
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
await IOUtils.writeUTF8(store.path, string, {
tmpPath: store.path + ".tmp",
});
await store.load();
// A backup file should have been created.
Assert.ok(await IOUtils.exists(store.path + ".corrupt"));
await IOUtils.remove(store.path + ".corrupt");
// The store should be ready to accept new data.
Assert.equal(store.data.logins.length, 0);
});
/**
* Loads login data from a malformed JSON string, using the synchronous
* initialization path.
*/
add_task(async function test_load_string_malformed_sync() {
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
await IOUtils.writeUTF8(store.path, string, {
tmpPath: store.path + ".tmp",
});
store.ensureDataReady();
// A backup file should have been created.
Assert.ok(await IOUtils.exists(store.path + ".corrupt"));
await IOUtils.remove(store.path + ".corrupt");
// The store should be ready to accept new data.
Assert.equal(store.data.logins.length, 0);
});
/**
* Fix bad dates when loading login data
*/
add_task(async function test_load_bad_dates() {
let rawLoginData = {
encType: 1,
encryptedPassword: "(test)",
encryptedUsername: "(test)",
guid: "{2a97313f-873b-4048-9a3d-4f442b46c1e5}",
httpRealm: null,
id: 1,
passwordField: "pass",
timesUsed: 1,
usernameField: "email",
};
let rawStoreData = {
dismissedBreachAlertsByLoginGUID: {},
logins: [],
nextId: 2,
potentiallyVulnerablePasswords: [],
version: 2,
};
/**
* test that:
* - bogus (0 or out-of-range) date values in any of the date fields are replaced with the
* earliest time marked by the other date fields
* - bogus bogus (0 or out-of-range) date values in all date fields are replaced with the time of import
*/
let tests = [
{
name: "Out-of-range time values",
savedProps: {
timePasswordChanged: MAX_DATE_MS + 1,
timeLastUsed: MAX_DATE_MS + 1,
timeCreated: MAX_DATE_MS + 1,
},
expectedProps: {
timePasswordChanged: "now",
timeLastUsed: "now",
timeCreated: "now",
},
},
{
name: "All zero time values",
savedProps: {
timePasswordChanged: 0,
timeLastUsed: 0,
timeCreated: 0,
},
expectedProps: {
timePasswordChanged: "now",
timeLastUsed: "now",
timeCreated: "now",
},
},
{
name: "Only timeCreated has value",
savedProps: {
timePasswordChanged: 0,
timeLastUsed: 0,
timeCreated: 946713600000,
},
expectedProps: {
timePasswordChanged: 946713600000,
timeLastUsed: 946713600000,
timeCreated: 946713600000,
},
},
{
name: "timeCreated has 0 value",
savedProps: {
timePasswordChanged: 946713600000,
timeLastUsed: 946713600000,
timeCreated: 0,
},
expectedProps: {
timePasswordChanged: 946713600000,
timeLastUsed: 946713600000,
timeCreated: 946713600000,
},
},
{
name: "timeCreated has out-of-range value",
savedProps: {
timePasswordChanged: 946713600000,
timeLastUsed: 946713600000,
timeCreated: MAX_DATE_MS + 1,
},
expectedProps: {
timePasswordChanged: 946713600000,
timeLastUsed: 946713600000,
timeCreated: 946713600000,
},
},
{
name: "Use earliest time for missing value",
savedProps: {
timePasswordChanged: 0,
timeLastUsed: 946713600000,
timeCreated: 946540800000,
},
expectedProps: {
timePasswordChanged: 946540800000,
timeLastUsed: 946713600000,
timeCreated: 946540800000,
},
},
];
for (let testData of tests) {
let store = new LoginStore(getTempFile(TEST_STORE_FILE_NAME).path);
let string = JSON.stringify(
Object.assign({}, rawStoreData, {
logins: [Object.assign({}, rawLoginData, testData.savedProps)],
})
);
await IOUtils.writeUTF8(store.path, string, {
tmpPath: store.path + ".tmp",
});
let now = Date.now();
await store.load();
Assert.equal(
store.data.logins.length,
1,
`${testData.name}: Expected a single login`
);
let login = store.data.logins[0];
for (let pname of ["timeCreated", "timeLastUsed", "timePasswordChanged"]) {
if (testData.expectedProps[pname] === "now") {
Assert.ok(
login[pname] >= now,
`${testData.name}: Check ${pname} is at/near now`
);
} else {
Assert.equal(
login[pname],
testData.expectedProps[pname],
`${testData.name}: Check expected ${pname}`
);
}
}
Assert.equal(store.data.version, 3, "Check version was bumped");
}
});