/* 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Tests the LoginCSVImport module. */ "use strict"; const { LoginCSVImport } = ChromeUtils.importESModule( "resource://gre/modules/LoginCSVImport.sys.mjs" ); const { LoginExport } = ChromeUtils.importESModule( "resource://gre/modules/LoginExport.sys.mjs" ); const { TelemetryTestUtils: TTU } = ChromeUtils.importESModule( "resource://testing-common/TelemetryTestUtils.sys.mjs" ); // Enable the collection (during test) for all products so even products // that don't collect the data will be able to run the test without failure. Services.prefs.setBoolPref( "toolkit.telemetry.testing.overrideProductsCheck", true ); const CATEGORICAL_HISTOGRAM = "PWMGR_IMPORT_LOGINS_FROM_FILE_CATEGORICAL"; const IMPORT_TIMER_HISTOGRAM = "PWMGR_IMPORT_LOGINS_FROM_FILE_MS"; const IMPORT_JANK_HISTOGRAM = "PWMGR_IMPORT_LOGINS_FROM_FILE_JANK_MS"; /** * Given an array of strings it creates a temporary CSV file that has them as content. * * @param {string[]} csvLines * The lines that make up the CSV file. * @param {string} extension * Optional parameter. Either 'csv' or 'tsv'. Default is 'csv'. * @returns {string} The path to the CSV file that was created. */ async function setupCsv(csvLines, extension) { // Cleanup state. TTU.getAndClearHistogram(CATEGORICAL_HISTOGRAM); TTU.getAndClearHistogram(IMPORT_TIMER_HISTOGRAM); TTU.getAndClearHistogram(IMPORT_JANK_HISTOGRAM); Services.logins.removeAllUserFacingLogins(); let tmpFile = await LoginTestUtils.file.setupCsvFileWithLines( csvLines, extension ); return tmpFile.path; } function checkMetaInfo( actual, expected, props = ["timesUsed", "timeCreated", "timePasswordChanged", "timeLastUsed"] ) { for (let prop of props) { // This will throw if not equal. equal(actual[prop], expected[prop], `Check ${prop}`); } return true; } function checkLoginNewlyCreated(login) { // These will throw if not equal. LoginTestUtils.assertTimeIsAboutNow(login.timeCreated); LoginTestUtils.assertTimeIsAboutNow(login.timePasswordChanged); LoginTestUtils.assertTimeIsAboutNow(login.timeLastUsed); return true; } /** * Asserts histogram telemetry for the categories of logins * * @param {Object} histogram Histogram object returned from `TelemetryTestUtils.getAndClearHistogram()` * @param {Number} index Index representing one of the following values in order: ["added", "modified", "error", "no_change"]. See `toolkit/components/telemetry/Histogram.json` for more information * @param {Number} expected The expected number of entries in the histogram at the passed index */ function assertHistogramTelemetry(histogram, index, expected) { TTU.assertHistogram(histogram, index, expected); } /** * Ensure that an import works with TSV. */ add_task(async function test_import_tsv() { let histogram = TTU.getAndClearHistogram(CATEGORICAL_HISTOGRAM); let tsvFilePath = await setupCsv( [ "url\tusername\tpassword\thttpRealm\tformActionOrigin\tguid\ttimeCreated\ttimeLastUsed\ttimePasswordChanged", `https://example.com:8080\tjoe@example.com\tqwerty\tMy realm\t""\t{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}\t1589617814635\t1589710449871\t1589617846802`, ], "tsv" ); await LoginCSVImport.importFromCSV(tsvFilePath); assertHistogramTelemetry(histogram, 0, 1); LoginTestUtils.checkLogins( [ TestData.authLogin({ formActionOrigin: null, guid: "{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}", httpRealm: "My realm", origin: "https://example.com:8080", password: "qwerty", passwordField: "", timeCreated: 1589617814635, timeLastUsed: 1589710449871, timePasswordChanged: 1589617846802, timesUsed: 1, username: "joe@example.com", usernameField: "", }), ], "Check that a new login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Ensure that an import fails if there is no username column in a TSV file. */ add_task(async function test_import_tsv_with_missing_columns() { let csvFilePath = await setupCsv( [ "url\tusernameTypo\tpassword\thttpRealm\tformActionOrigin\tguid\ttimeCreated\ttimeLastUsed\ttimePasswordChanged", "https://example.com\tkramer@example.com\tqwerty\tMy realm\t\t{5ec0d12f-e194-4279-ae1b-d7d281bb46f7}\t1589617814635\t1589710449871\t1589617846802", ], "tsv" ); await Assert.rejects( LoginCSVImport.importFromCSV(csvFilePath), /FILE_FORMAT_ERROR/, "Ensure missing username throws" ); LoginTestUtils.checkLogins( [], "Check that no login was added without finding columns" ); }); /** * Ensure that an import fails if there is no username column. We don't want * to accidentally import duplicates due to a name mismatch for the username column. */ add_task(async function test_import_lacking_username_column() { let csvFilePath = await setupCsv([ "url,usernameTypo,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", `https://example.com,joe@example.com,qwerty,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814635,1589710449871,1589617846802`, ]); await Assert.rejects( LoginCSVImport.importFromCSV(csvFilePath), /FILE_FORMAT_ERROR/, "Ensure missing username throws" ); LoginTestUtils.checkLogins( [], "Check that no login was added without finding a username column" ); }); /** * Ensure that an import fails if there are two columns that map to one login field. */ add_task(async function test_import_with_duplicate_fields() { // Two origin columns (url & login_uri). // One row has different values and the other has the same. let csvFilePath = await setupCsv([ "url,login_uri,username,login_password", "https://example.com/path,https://example.com,john@example.com,azerty", "https://mozilla.org,https://mozilla.org,jdoe@example.com,qwerty", ]); await Assert.rejects( LoginCSVImport.importFromCSV(csvFilePath), /CONFLICTING_VALUES_ERROR/, "Check that the errorType is file format error" ); LoginTestUtils.checkLogins( [], "Check that no login was added from a file with duplicated columns" ); }); /** * Ensure that an import fails if there are two identical columns. */ add_task(async function test_import_with_duplicate_columns() { let csvFilePath = await setupCsv([ "url,username,password,password", "https://example.com/path,john@example.com,azerty,12345", ]); await Assert.rejects( LoginCSVImport.importFromCSV(csvFilePath), /CONFLICTING_VALUES_ERROR/, "Check that the errorType is file format error" ); LoginTestUtils.checkLogins( [], "Check that no login was added from a file with duplicated columns" ); }); /** * Ensure that import is allowed with only origin, username, password and that * one can mix and match column naming between conventions from different * password managers (so that we better support new/unknown password managers). */ add_task(async function test_import_minimal_with_mixed_naming() { let csvFilePath = await setupCsv([ "url,username,login_password", "ftp://example.com,john@example.com,azerty", ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.formLogin({ formActionOrigin: "", httpRealm: null, origin: "ftp://example.com", password: "azerty", passwordField: "", timesUsed: 1, username: "john@example.com", usernameField: "", }), ], "Check that a new login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e, ["timesUsed"]) && checkLoginNewlyCreated(a) ); }); /** * Imports login data from the latest Firefox CSV file for various logins from * LoginTestUtils.testData.loginList(). */ add_task(async function test_import_from_firefox_various_latest() { await setupCsv([]); info("Populate the login list for export"); let logins = LoginTestUtils.testData.loginList(); await Services.logins.addLogins(logins); let tmpFilePath = FileTestUtils.getTempFile("logins.csv").path; await LoginExport.exportAsCSV(tmpFilePath); await LoginCSVImport.importFromCSV(tmpFilePath); LoginTestUtils.checkLogins( logins, "Check that all of LoginTestUtils.testData.loginList can be re-imported" ); }); /** * Imports login data from a Firefox CSV file without quotes. */ add_task(async function test_import_from_firefox_auth() { let csvFilePath = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", `https://example.com:8080,joe@example.com,qwerty,My realm,"",{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814635,1589710449871,1589617846802`, ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.authLogin({ formActionOrigin: null, guid: "{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}", httpRealm: "My realm", origin: "https://example.com:8080", password: "qwerty", passwordField: "", timeCreated: 1589617814635, timeLastUsed: 1589710449871, timePasswordChanged: 1589617846802, timesUsed: 1, username: "joe@example.com", usernameField: "", }), ], "Check that a new login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Imports login data from a Firefox CSV file with quotes. */ add_task(async function test_import_from_firefox_auth_with_quotes() { let csvFilePath = await setupCsv([ '"url","username","password","httpRealm","formActionOrigin","guid","timeCreated","timeLastUsed","timePasswordChanged"', '"https://example.com","joe@example.com","qwerty2","My realm",,"{5ec0d12f-e194-4279-ae1b-d7d281bb46f0}","1589617814635","1589710449871","1589617846802"', ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.authLogin({ formActionOrigin: null, httpRealm: "My realm", origin: "https://example.com", password: "qwerty2", passwordField: "", timeCreated: 1589617814635, timeLastUsed: 1589710449871, timePasswordChanged: 1589617846802, timesUsed: 1, username: "joe@example.com", usernameField: "", }), ], "Check that a new login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Imports login data from a Firefox CSV file where only cells containing a comma are quoted. */ add_task(async function test_import_from_firefox_auth_some_quoted_fields() { let csvFilePath = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", 'https://example.com,joe@example.com,"one,two,tree","My realm",,{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814635,1589710449871,1589617846802', ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.authLogin({ formActionOrigin: null, httpRealm: "My realm", origin: "https://example.com", password: "one,two,tree", passwordField: "", timeCreated: 1589617814635, timePasswordChanged: 1589617846802, timeLastUsed: 1589710449871, timesUsed: 1, username: "joe@example.com", usernameField: "", }), ], "Check that a new login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Imports login data from a Firefox CSV file with an empty formActionOrigin and null httpRealm */ add_task(async function test_import_from_firefox_form_empty_formActionOrigin() { let csvFilePath = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://example.com,joe@example.com,s3cret1,,,{5ec0d12f-e194-4279-ae1b-d7d281bb46f0},1589617814636,1589710449872,1589617846803", ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.formLogin({ formActionOrigin: "", httpRealm: null, origin: "https://example.com", password: "s3cret1", passwordField: "", timeCreated: 1589617814636, timePasswordChanged: 1589617846803, timeLastUsed: 1589710449872, timesUsed: 1, username: "joe@example.com", usernameField: "", }), ], "Check that a new login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Imports login data from a Firefox CSV file with a non-empty formActionOrigin and null httpRealm. */ add_task(async function test_import_from_firefox_form_with_formActionOrigin() { let csvFilePath = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "http://example.com,joe@example.com,s3cret1,,https://other.example.com,{5ec0d12f-e194-4279-ae1b-d7d281bb46f1},1589617814635,1589710449871,1589617846802", ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.formLogin({ formActionOrigin: "https://other.example.com", httpRealm: null, origin: "http://example.com", password: "s3cret1", passwordField: "", timeCreated: 1589617814635, timePasswordChanged: 1589617846802, timeLastUsed: 1589710449871, timesUsed: 1, username: "joe@example.com", usernameField: "", }), ], "Check that a new login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Imports login data from a Bitwarden CSV file. * `name` is ignored until bug 1433770. */ add_task(async function test_import_from_bitwarden_csv() { let csvFilePath = await setupCsv([ "folder,favorite,type,name,notes,fields,login_uri,login_username,login_password,login_totp", `,,note,jane's note,"secret note, ignore me!",,,,,`, ",,login,example.com,,,https://example.com/login,jane@example.com,secret_password", ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.formLogin({ formActionOrigin: "", httpRealm: null, origin: "https://example.com", password: "secret_password", passwordField: "", timesUsed: 1, username: "jane@example.com", usernameField: "", }), ], "Check that a new Bitwarden login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e, ["timesUsed"]) && checkLoginNewlyCreated(a) ); }); /** * Imports login data from a Chrome CSV file. * `name` is ignored until bug 1433770. */ add_task(async function test_import_from_chrome_csv() { let csvFilePath = await setupCsv([ "name,url,username,password", "example.com,https://example.com/login,jane@example.com,secret_chrome_password", ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.formLogin({ formActionOrigin: "", httpRealm: null, origin: "https://example.com", password: "secret_chrome_password", passwordField: "", timesUsed: 1, username: "jane@example.com", usernameField: "", }), ], "Check that a new Chrome login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e, ["timesUsed"]) && checkLoginNewlyCreated(a) ); }); /** * Imports login data with an item without the username. */ add_task(async function test_import_login_without_username() { let csvFilePath = await setupCsv([ "url,username,password", "https://example.com/login,,secret_password", ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.formLogin({ formActionOrigin: "", httpRealm: null, origin: "https://example.com", password: "secret_password", passwordField: "", timesUsed: 1, username: "", usernameField: "", }), ], "Check that a Login is added without an username", (a, e) => a.equals(e) && checkMetaInfo(a, e, ["timesUsed"]) && checkLoginNewlyCreated(a) ); }); /** * Imports login data from a KeepassXC CSV file. * `Title` is ignored until bug 1433770. */ add_task(async function test_import_from_keepassxc_csv() { let csvFilePath = await setupCsv([ `"Group","Title","Username","Password","URL","Notes"`, `"NewDatabase/Internet","Amazing","test@example.com","","https://example.org",""`, ]); await LoginCSVImport.importFromCSV(csvFilePath); LoginTestUtils.checkLogins( [ TestData.formLogin({ formActionOrigin: "", httpRealm: null, origin: "https://example.org", password: "", passwordField: "", timesUsed: 1, username: "test@example.com", usernameField: "", }), ], "Check that a new KeepassXC login was added with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e, ["timesUsed"]) && checkLoginNewlyCreated(a) ); }); /** * Imports login data summary contains added logins. */ add_task(async function test_import_summary_contains_added_login() { let csvFilePath = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://added.example.com,jane@example.com,added_passwordd,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb0003},1589617814635,1589710449871,1589617846802", ]); let [added] = await LoginCSVImport.importFromCSV(csvFilePath); equal(added.result, "added", `Check that the login was added`); }); /** * Imports login data summary contains modified logins without guid. */ add_task(async function test_import_summary_modified_login_without_guid() { let histogram = TTU.getAndClearHistogram(CATEGORICAL_HISTOGRAM); let initialDataFile = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://modifiedwithoutguid.example.com,gini@example.com,initial_password,My realm,,,1589617814635,1589710449871,1589617846802", ]); await LoginCSVImport.importFromCSV(initialDataFile); assertHistogramTelemetry(histogram, 0, 1); histogram = TTU.getAndClearHistogram(CATEGORICAL_HISTOGRAM); let csvFile = await LoginTestUtils.file.setupCsvFileWithLines([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://modifiedwithoutguid.example.com,gini@example.com,modified_password,My realm,,,1589617814635,1589710449871,1589617846999", ]); let [modifiedWithoutGuid] = await LoginCSVImport.importFromCSV(csvFile.path); assertHistogramTelemetry(histogram, 1, 1); equal( modifiedWithoutGuid.result, "modified", `Check that the login was modified when there was no guid data` ); LoginTestUtils.checkLogins( [ TestData.authLogin({ formActionOrigin: null, guid: null, httpRealm: "My realm", origin: "https://modifiedwithoutguid.example.com", password: "modified_password", passwordField: "", timeCreated: 1589617814635, timeLastUsed: 1589710449871, timePasswordChanged: 1589617846999, timesUsed: 1, username: "gini@example.com", usernameField: "", }), ], "Check that logins were updated with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Imports login data summary contains modified logins with guid. */ add_task(async function test_import_summary_modified_login_with_guid() { let initialDataFile = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://modifiedwithguid.example.com,jane@example.com,initial_password,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb0001},1589617814635,1589710449871,1589617846802", ]); await LoginCSVImport.importFromCSV(initialDataFile); let csvFile = await LoginTestUtils.file.setupCsvFileWithLines([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://modified.example.com,jane@example.com,modified_password,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb0001},1589617814635,1589710449871,1589617846999", ]); let [modifiedWithGuid] = await LoginCSVImport.importFromCSV(csvFile.path); equal( modifiedWithGuid.result, "modified", `Check that the login was modified when it had the same guid` ); LoginTestUtils.checkLogins( [ TestData.authLogin({ formActionOrigin: null, guid: "{5ec0d12f-e194-4279-ae1b-d7d281bb0001}", httpRealm: "My realm", origin: "https://modified.example.com", password: "modified_password", passwordField: "", timeCreated: 1589617814635, timeLastUsed: 1589710449871, timePasswordChanged: 1589617846999, timesUsed: 1, username: "jane@example.com", usernameField: "", }), ], "Check that logins were updated with the correct fields", (a, e) => a.equals(e) && checkMetaInfo(a, e) ); }); /** * Imports login data summary contains unchanged logins. */ add_task(async function test_import_summary_contains_unchanged_login() { let histogram = TTU.getAndClearHistogram(CATEGORICAL_HISTOGRAM); let initialDataFile = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://nochange.example.com,jane@example.com,nochange_password,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb0002},1589617814635,1589710449871,1589617846802", ]); await LoginCSVImport.importFromCSV(initialDataFile); assertHistogramTelemetry(histogram, 0, 1); histogram = TTU.getAndClearHistogram(CATEGORICAL_HISTOGRAM); let csvFile = await LoginTestUtils.file.setupCsvFileWithLines([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://nochange.example.com,jane@example.com,nochange_password,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb0002},1589617814635,1589710449871,1589617846802", ]); let [noChange] = await LoginCSVImport.importFromCSV(csvFile.path); assertHistogramTelemetry(histogram, 3, 1); equal(noChange.result, "no_change", `Check that the login was not changed`); }); /** * Imports login data summary contains logins with errors in case of missing fields. */ add_task(async function test_import_summary_contains_missing_fields_errors() { let histogram = TTU.getAndClearHistogram(CATEGORICAL_HISTOGRAM); const missingFieldsToCheck = ["url", "password"]; const sourceObject = { url: "https://invalid.password.example.com", username: "jane@example.com", password: "qwerty", }; for (const missingField of missingFieldsToCheck) { const clonedUser = { ...sourceObject }; clonedUser[missingField] = ""; let csvFilePath = await setupCsv([ "url,username,password", `${clonedUser.url},${clonedUser.username},${clonedUser.password}`, ]); let [importLogin] = await LoginCSVImport.importFromCSV(csvFilePath); equal( importLogin.result, "error_missing_field", `Check that the missing field error is reported for ${missingField}` ); equal( importLogin.field_name, missingField, `Check that the invalid field name is correctly reported for the ${missingField}` ); } assertHistogramTelemetry(histogram, 2, 1); }); /** * Imports login with wrong file format will have correct errorType. */ add_task(async function test_import_summary_with_bad_format() { let csvFilePath = await setupCsv(["password", "123qwe!@#QWE"]); await Assert.rejects( LoginCSVImport.importFromCSV(csvFilePath), /FILE_FORMAT_ERROR/, "Check that the errorType is file format error" ); LoginTestUtils.checkLogins( [], "Check that no login was added with bad format" ); }); /** * Imports login with wrong file type will have correct errorType. */ add_task(async function test_import_summary_with_non_csv_file() { let csvFilePath = await setupCsv([ "this is totally not a csv file", ]); await Assert.rejects( LoginCSVImport.importFromCSV(csvFilePath), /FILE_FORMAT_ERROR/, "Check that the errorType is file format error" ); LoginTestUtils.checkLogins( [], "Check that no login was added with file of different format" ); }); /** * Imports login multiple url and user will import the first and skip the second. */ add_task(async function test_import_summary_with_url_user_multiple_values() { let csvFilePath = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://example.com,jane@example.com,password1,My realm", "https://example.com,jane@example.com,password2,My realm", ]); let initialLoginCount = Services.logins.getAllLogins().length; let results = await LoginCSVImport.importFromCSV(csvFilePath); let afterImportLoginCount = Services.logins.getAllLogins().length; equal(results.length, 2, `Check that we got a result for each imported row`); equal(results[0].result, "added", `Check that the first login was added`); equal( results[1].result, "no_change", `Check that the second login was skipped` ); equal(initialLoginCount, 0, `Check that initially we had no logins`); equal(afterImportLoginCount, 1, `Check that we imported only one login`); }); /** * Imports login with duplicated guid values throws error. */ add_task(async function test_import_summary_with_duplicated_guid_values() { let csvFilePath = await setupCsv([ "url,username,password,httpRealm,formActionOrigin,guid,timeCreated,timeLastUsed,timePasswordChanged", "https://example1.com,jane1@example.com,password1,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb0004},1589617814635,1589710449871,1589617846802", "https://example2.com,jane2@example.com,password2,My realm,,{5ec0d12f-e194-4279-ae1b-d7d281bb0004},1589617814635,1589710449871,1589617846802", ]); let initialLoginCount = Services.logins.getAllLogins().length; let results = await LoginCSVImport.importFromCSV(csvFilePath); let afterImportLoginCount = Services.logins.getAllLogins().length; equal(results.length, 2, `Check that we got a result for each imported row`); equal(results[0].result, "added", `Check that the first login was added`); equal(results[1].result, "error", `Check that the second login was an error`); equal(initialLoginCount, 0, `Check that initially we had no logins`); equal(afterImportLoginCount, 1, `Check that we imported only one login`); }); /** * Imports login with different passwords will pick up the newest one and ignore the oldest one. */ add_task(async function test_import_summary_with_different_time_changed() { let csvFilePath = await setupCsv([ "url,username,password,timeCreated,timeLastUsed,timePasswordChanged", "https://example.com,eve@example.com,old password,1589617814635,1589710449800,1589617846800", "https://example.com,eve@example.com,new password,1589617814635,1589710449801,1589617846801", ]); let initialLoginCount = Services.logins.getAllLogins().length; let results = await LoginCSVImport.importFromCSV(csvFilePath); let afterImportLoginCount = Services.logins.getAllLogins().length; equal(results.length, 2, `Check that we got a result for each imported row`); equal( results[0].result, "no_change", `Check that the oldest password is skipped` ); equal( results[1].login.password, "new password", `Check that the newest password is imported` ); equal( results[1].result, "added", `Check that the newest password result is correct` ); equal(initialLoginCount, 0, `Check that initially we had no logins`); equal(afterImportLoginCount, 1, `Check that we imported only one login`); }); /** * Imports duplicate logins as one without an error. */ add_task(async function test_import_duplicate_logins_as_one() { let csvFilePath = await setupCsv([ "name,url,username,password", "somesite,https://example.com/,user@example.com,asdasd123123", "somesite,https://example.com/,user@example.com,asdasd123123", ]); let initialLoginCount = Services.logins.getAllLogins().length; let results = await LoginCSVImport.importFromCSV(csvFilePath); let afterImportLoginCount = Services.logins.getAllLogins().length; equal(results.length, 2, `Check that we got a result for each imported row`); equal( results[0].result, "added", `Check that the first login login was added` ); equal( results[1].result, "no_change", `Check that the second login was not changed` ); equal(initialLoginCount, 0, `Check that initially we had no logins`); equal(afterImportLoginCount, 1, `Check that we imported only one login`); });