diff options
Diffstat (limited to 'toolkit/components/passwordmgr/test/unit/test_module_LoginStore.js')
-rw-r--r-- | toolkit/components/passwordmgr/test/unit/test_module_LoginStore.js | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/toolkit/components/passwordmgr/test/unit/test_module_LoginStore.js b/toolkit/components/passwordmgr/test/unit/test_module_LoginStore.js new file mode 100644 index 0000000000..f7e764c789 --- /dev/null +++ b/toolkit/components/passwordmgr/test/unit/test_module_LoginStore.js @@ -0,0 +1,337 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * 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++, + hostname: "http://www.example.com", + httpRealm: null, + formSubmitURL: "http://www.example.com", + 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,' + + '"hostname":"http://www.example.com",' + + '"httpRealm":null,' + + '"formSubmitURL":"http://www.example.com",' + + '"usernameField":"usernameField",' + + '"passwordField":"passwordField",' + + '"encryptedUsername":"(test)",' + + '"encryptedPassword":"(test)",' + + '"guid":"(test)",' + + '"encType":1,' + + '"timeCreated":1262304000000,' + + '"timeLastUsed":1262390400000,' + + '"timePasswordChanged":1262476800000,' + + '"timesUsed":1}],"disabledHosts":[' + + '"http://www.example.org"]}'; + + 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, + hostname: "http://www.example.com", + httpRealm: null, + formSubmitURL: "http://www.example.com", + 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); + + let string = '{"logins":[{"hostname":"http://www.example.com","id":1,'; + + 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); + + let string = '{"logins":[{"hostname":"http://www.example.com","id":1,'; + + 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)", + formSubmitURL: "https://www.example.com", + guid: "{2a97313f-873b-4048-9a3d-4f442b46c1e5}", + hostname: "https://www.example.com", + 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"); + } +}); |