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
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
/**
* Tests the LoginExport module.
*/
"use strict";
let { LoginExport } = ChromeUtils.importESModule(
"resource://gre/modules/LoginExport.sys.mjs"
);
let { sinon } = ChromeUtils.importESModule(
);
/**
* Saves the logins to a temporary CSV file, reads the lines and returns the CSV lines.
* After extracting the CSV lines, it deletes the tmp file.
*/
async function exportAsCSVInTmpFile() {
const tmpFilePath = FileTestUtils.getTempFile("logins.csv").path;
await LoginExport.exportAsCSV(tmpFilePath);
const csvString = await IOUtils.readUTF8(tmpFilePath);
await IOUtils.remove(tmpFilePath);
// CSV uses CRLF
return csvString.split(/\r\n/);
}
const COMMON_LOGIN_MODS = {
guid: "{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}",
timeCreated: 1589617814635,
timeLastUsed: 1589710449871,
timePasswordChanged: 1589617846802,
timesUsed: 1,
username: "joe@example.com",
password: "qwerty",
};
/**
* Generates a new login object with all the form login fields populated.
*/
function exportFormLogin(modifications) {
return LoginTestUtils.testData.formLogin({
...COMMON_LOGIN_MODS,
...modifications,
});
}
function exportAuthLogin(modifications) {
return LoginTestUtils.testData.authLogin({
...COMMON_LOGIN_MODS,
httpRealm: "My realm",
...modifications,
});
}
add_setup(async () => {
let oldLogins = Services.logins;
Services.logins = { getAllLogins: sinon.stub() };
registerCleanupFunction(() => {
Services.logins = oldLogins;
});
});
add_task(async function test_buildCSVRow() {
let testObject = {
null: null,
emptyString: "",
number: 99,
string: "Foo",
};
Assert.deepEqual(
LoginExport._buildCSVRow(testObject, [
"null",
"emptyString",
"number",
"string",
]),
["", `""`, `"99"`, `"Foo"`],
"Check _buildCSVRow with different types"
);
});
add_task(async function test_no_new_properties_to_export() {
let login = exportFormLogin();
Assert.deepEqual(
Object.keys(login),
[
"QueryInterface",
"displayOrigin",
"origin",
"hostname",
"formActionOrigin",
"formSubmitURL",
"httpRealm",
"username",
"usernameField",
"password",
"passwordField",
"unknownFields",
"everSynced",
"syncCounter",
"init",
"equals",
"matches",
"clone",
"guid",
"timeCreated",
"timeLastUsed",
"timePasswordChanged",
"timesUsed",
],
"Check that no new properties were added to a login that should maybe be exported"
);
});
add_task(async function test_export_one_form_login() {
let login = exportFormLogin();
Services.logins.getAllLogins.returns([login]);
let rows = await exportAsCSVInTmpFile();
Assert.equal(
rows[0],
'"url","username","password","httpRealm","formActionOrigin","guid","timeCreated","timeLastUsed","timePasswordChanged"',
"checking csv headers"
);
Assert.equal(
rows[1],
`checking login is saved as CSV row\n${JSON.stringify(login)}\n`
);
});
add_task(async function test_export_one_auth_login() {
let login = exportAuthLogin();
Services.logins.getAllLogins.returns([login]);
let rows = await exportAsCSVInTmpFile();
Assert.equal(
rows[0],
'"url","username","password","httpRealm","formActionOrigin","guid","timeCreated","timeLastUsed","timePasswordChanged"',
"checking csv headers"
);
Assert.equal(
rows[1],
'"https://example.com","joe@example.com","qwerty","My realm",,"{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}","1589617814635","1589710449871","1589617846802"',
`checking login is saved as CSV row\n${JSON.stringify(login)}\n`
);
});
add_task(async function test_export_escapes_values() {
let login = exportFormLogin({
password: "!@#$%^&*()_+,'",
});
Services.logins.getAllLogins.returns([login]);
let rows = await exportAsCSVInTmpFile();
Assert.equal(
rows[1],
`checking login correctly escapes CSV characters \n${JSON.stringify(login)}`
);
});
add_task(async function test_export_multiple_rows() {
let logins = await LoginTestUtils.testData.loginList();
// Note, because we're stubbing this method and avoiding the actual login manager logic,
// login de-duplication does not occur
Services.logins.getAllLogins.returns(logins);
let actualRows = await exportAsCSVInTmpFile();
let expectedRows = [
'"url","username","password","httpRealm","formActionOrigin","guid","timeCreated","timeLastUsed","timePasswordChanged"',
'"http://www.example.com","the username","the password for www.example.com",,"http://www.example.com",,,,',
];
Assert.equal(actualRows.length, expectedRows.length, "Check number of lines");
for (let i = 0; i < logins.length; i++) {
let login = logins[i];
Assert.equal(
actualRows[i],
expectedRows[i],
`checking CSV correctly writes row at index=${i} \n${JSON.stringify(
login
)}\n`
);
}
});