var { MailServices } = ChromeUtils.import( "resource:///modules/MailServices.jsm" ); // used by checkProgress to periodically check the progress of the import var gGenericImportHelper; /** * GenericImportHelper * The parent class of AbImportHelper, MailImportHelper, SettingsImportHelper * and FiltersImportHelper. * * @param aModuleType The type of import module. Should be addressbook or mail. * @param aModuleSearchString * The string to search the module names for, such as * "Text file" to find the import module for comma-separated * value, LDIF, and tab-delimited files. * @param aFile An instance of nsIFile to import. * * @class * @class */ function GenericImportHelper(aModuleType, aModuleSearchString, aFile) { gGenericImportHelper = null; if (!["addressbook", "mail", "settings", "filters"].includes(aModuleType)) { do_throw("Unexpected type passed to the GenericImportHelper constructor"); } this.mModuleType = aModuleType; this.mModuleSearchString = aModuleSearchString; this.mInterface = this._findInterface(); Assert.ok(this.mInterface !== null); this.mFile = aFile; // checked in the beginImport method } GenericImportHelper.prototype = { interfaceType: Ci.nsIImportGeneric, /** * GenericImportHelper.beginImport * Imports the given address book export or mail data and invoke * checkProgress of child class to check the data, */ beginImport() { Assert.ok(this.mFile instanceof Ci.nsIFile && this.mFile.exists()); if (this.mModuleType == "addressbook") { this.mInterface.SetData("addressLocation", this.mFile); } else if (this.mModuleType == "mail") { this.mInterface.SetData("mailLocation", this.mFile); } Assert.ok(this.mInterface.WantsProgress()); const error = Cc["@mozilla.org/supports-string;1"].createInstance( Ci.nsISupportsString ); Assert.ok(this.mInterface.BeginImport(null, error)); Assert.equal(error.data, ""); do_test_pending(); this.checkProgress(); }, /** * GenericImportHelper.getInterface * * @returns An nsIImportGeneric import interface. */ getInterface() { return this.mInterface; }, _findInterface() { var importService = Cc["@mozilla.org/import/import-service;1"].getService( Ci.nsIImportService ); var count = importService.GetModuleCount(this.mModuleType); // Iterate through each import module until the one being searched for is // found and then return the ImportInterface of that module for (var i = 0; i < count; i++) { // Check if the current module fits the search string gets the interface if ( importService .GetModuleName(this.mModuleType, i) .includes(this.mModuleSearchString) ) { return importService .GetModule(this.mModuleType, i) .GetImportInterface(this.mModuleType) .QueryInterface(this.interfaceType); } } return null; // it wasn't found }, /** * GenericImportHelper.checkProgress * Checks the progress of an import every 200 milliseconds until it is * complete. Checks the test results if there is an original address book, * otherwise evaluates the optional command, or calls do_test_finished(). */ checkProgress() { Assert.ok( this.mInterface && this.mInterface instanceof Ci.nsIImportGeneric ); Assert.ok(this.mInterface.ContinueImport()); // if the import isn't done, check again in 200 milliseconds. if (this.mInterface.GetProgress() != 100) { // use the helper object to check the progress of the import after 200 ms gGenericImportHelper = this; do_timeout(200, function () { gGenericImportHelper.checkProgress(); }); } else { // if it is done, check the results or finish the test. this.checkResults(); do_test_finished(); } }, /** * GenericImportHelper.checkResults * Checks the results of the import. * Child class should implement this method. */ checkResults() {}, }; /** * AbImportHelper * A helper for Address Book imports. To use, supply at least the file and type. * If you would like the results checked, add a new array in the addressbook * JSON file in the resources folder and supply aAbName and aJsonName. * See AB_README for more information. * * @param aFile An instance of nsIAbFile to import. * @param aModuleSearchString * The string to search the module names for, such as * "Text file" to find the import module for comma-separated * value, LDIF, and tab-delimited files. * Optional parameters: Include if you would like the import checked. * @param aAbName The name the address book will have (the filename without * the extension). * @param aJsonName The name of the array in addressbook.json with the cards * to compare with the imported cards. * @class * @class */ function AbImportHelper(aFile, aModuleSearchString, aAbName, aJsonName) { GenericImportHelper.call(this, "addressbook", aModuleSearchString, aFile); this.mAbName = aAbName; /* Attribute notes: The attributes listed in the declaration below are * supported by all three text export/import types. * The following are not supported: anniversaryYear, anniversaryMonth, * anniversaryDay, popularityIndex, isMailList, mailListURI, lastModifiedDate. */ var supportedAttributes = [ "FirstName", "LastName", "DisplayName", "NickName", "PrimaryEmail", "SecondEmail", "WorkPhone", "HomePhone", "FaxNumber", "PagerNumber", "CellularNumber", "HomeAddress", "HomeAddress2", "HomeCity", "HomeState", "HomeZipCode", "HomeCountry", "WorkAddress", "WorkAddress2", "WorkCity", "WorkState", "WorkZipCode", "WorkCountry", "JobTitle", "Department", "Company", "BirthYear", "BirthMonth", "BirthDay", "WebPage1", "WebPage2", "Custom1", "Custom2", "Custom3", "Custom4", "Notes", "_AimScreenName", "_vCard", ]; // get the extra attributes supported for the given type of import if (this.mFile.leafName.toLowerCase().endsWith(".ldif")) { this.mSupportedAttributes = supportedAttributes; } else if (this.mFile.leafName.toLowerCase().endsWith(".csv")) { this.mSupportedAttributes = supportedAttributes; this.setFieldMap(this.getDefaultFieldMap(true)); } else if (this.mFile.leafName.toLowerCase().endsWith(".vcf")) { this.mSupportedAttributes = supportedAttributes; } // get the "cards" from the JSON file, if necessary if (aJsonName) { this.mJsonCards = this.getJsonCards(aJsonName); } } AbImportHelper.prototype = { __proto__: GenericImportHelper.prototype, /** * AbImportHelper.getDefaultFieldMap * Returns the default field map. * * @param aSkipFirstRecord True if the first record of the text file should * be skipped. * @returns A default field map. */ getDefaultFieldMap(aSkipFirstRecord) { var importService = Cc["@mozilla.org/import/import-service;1"].getService( Ci.nsIImportService ); var fieldMap = importService.CreateNewFieldMap(); fieldMap.DefaultFieldMap(fieldMap.numMozFields); this.mInterface .GetData("addressInterface") .QueryInterface(Ci.nsIImportAddressBooks) .InitFieldMap(fieldMap); fieldMap.skipFirstRecord = aSkipFirstRecord; return fieldMap; }, /** * AbImportHelper.setFieldMap * Set the field map. * * @param aFieldMap The field map used for address book import. */ setFieldMap(aFieldMap) { this.mInterface.SetData("fieldMap", aFieldMap); }, /** * AbImportHelper.setAddressLocation * Set the the location of the address book. * * @param aLocation The location of the source address book. */ setAddressBookLocation(aLocation) { this.mInterface.SetData("addressLocation", aLocation); }, /** * AbImportHelper.setAddressDestination * Set the the destination of the address book. * * @param aDestination URI of destination address book or null if * new address books will be created. */ setAddressDestination(aDestination) { this.mInterface.SetData("addressDestination", aDestination); }, /** * AbImportHelper.checkResults * Checks the results of the import. * Ensures the an address book was created, then compares the supported * attributes of each card with the card(s) in the JSON array. * Calls do_test_finished() when done */ checkResults() { if (!this.mJsonCards) { do_throw("The address book must be setup before checking results"); } // When do_test_pending() was called and there is an error the test hangs. // This try/catch block will catch any errors and call do_throw() with the // error to throw the error and avoid the hang. try { // make sure an address book was created var newAb = this.getAbByName(this.mAbName); Assert.ok(newAb !== null); Assert.ok(newAb.QueryInterface(Ci.nsIAbDirectory)); // get the imported card(s) and check each one var count = 0; for (let importedCard of newAb.childCards) { this.compareCards(this.mJsonCards[count], importedCard); count++; } // make sure there are the same number of cards in the address book and // the JSON array Assert.equal(count, this.mJsonCards.length); do_test_finished(); } catch (e) { do_throw(e); } }, /** * AbImportHelper.getAbByName * Returns the Address Book (if any) with the given name. * * @param aName The name of the Address Book to find. * @returns An nsIAbDirectory, if found. * null if the requested Address Book could not be found. */ getAbByName(aName) { Assert.ok(aName && aName.length > 0); for (let data of MailServices.ab.directories) { if (data.dirName == aName) { return data; } } return null; }, /** * AbImportHelper.compareCards * Compares a JSON "card" with an imported card and throws an error if the * values of a supported attribute are different. * * @param aJsonCard The object decoded from addressbook.json. * @param aCard The imported card to compare with. */ compareCards(aJsonCard, aCard) { for (let [key, value] of Object.entries(aJsonCard)) { if (!this.mSupportedAttributes.includes(key)) { continue; } if (key == "_vCard") { equal( aCard .getProperty(key, "") .replace( /UID:[a-f0-9-]{36}/i, "UID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ), `BEGIN:VCARD\r\n${value.join("\r\n")}\r\nEND:VCARD\r\n`, "_vCard should be correct" ); } else { equal(aCard.getProperty(key, ""), value, `${key} should be correct`); } } }, /** * AbImportHelper.getJsonCards * Gets an array of "cards" from the JSON file addressbook.json located in the * mailnews/import/test/resources folder. The array should contain objects * with the expected properties and values of the cards in the imported * address book. * See addressbook.json for an example and AB_README for more details. * * @param aName The name of the array in addressbook.json. * @returns An array of "cards". */ getJsonCards(aName) { if (!aName) { do_throw("Error - getJSONAb requires an address book name"); } var file = do_get_file("resources/addressbook.json"); if (!file || !file.exists() || !file.isFile()) { do_throw("Unable to get JSON file"); } var fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( Ci.nsIFileInputStream ); fis.init(file, 0x01, 0o444, 0); var istream = Cc[ "@mozilla.org/intl/converter-input-stream;1" ].createInstance(Ci.nsIConverterInputStream); var replacementChar = Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER; istream.init(fis, "UTF-8", 1024, replacementChar); var json = ""; var str = {}; // get the entire file into the json string while (istream.readString(4096, str) != 0) { json += str.value; } // close the input streams istream.close(); fis.close(); // decode the JSON and get the array of cards var arr = JSON.parse(json)[aName]; Assert.ok(arr && arr.length > 0); return arr; }, setSupportedAttributes(attributes) { this.mSupportedAttributes = attributes; }, }; /** * MailImportHelper * A helper for mail imports. * * @param aFile An instance of nsIFile to import. * @param aModuleSearchString * The string to search the module names for, such as * "Outlook Express", etc. * @param aExpected An instance of nsIFile to compare with the imported * folders. * * @class * @class */ function MailImportHelper(aFile, aModuleSearchString, aExpected) { GenericImportHelper.call(this, "mail", aModuleSearchString, aFile); this.mExpected = aExpected; } MailImportHelper.prototype = { __proto__: GenericImportHelper.prototype, interfaceType: Ci.nsIImportGeneric, _checkEqualFolder(expectedFolder, actualFolder) { Assert.equal(expectedFolder.leafName, actualFolder.name); let expectedSubFolders = []; for (let entry of expectedFolder.directoryEntries) { if (entry.isDirectory()) { expectedSubFolders.push(entry); } } let actualSubFolders = actualFolder.subFolders; Assert.equal(expectedSubFolders.length, actualSubFolders.length); for (let i = 0; i < expectedSubFolders.length; i++) { this._checkEqualFolder(expectedSubFolders[i], actualSubFolders[i]); } }, checkResults() { let rootFolder = MailServices.accounts.localFoldersServer.rootFolder; Assert.ok(rootFolder.containsChildNamed(this.mFile.leafName)); let importedFolder = rootFolder.getChildNamed(this.mFile.leafName); Assert.notEqual(importedFolder, null); this._checkEqualFolder(this.mExpected, importedFolder); }, }; /** * SettingsImportHelper * A helper for settings imports. * * @param aFile An instance of nsIFile to import, can be null. * @param aModuleSearchString * The string to search the module names for, such as * "Outlook Express", etc. * @param aExpected An array of object which has incomingServer, identity * and smtpSever to compare with imported nsIMsgAccount. * * @class * @class */ function SettingsImportHelper(aFile, aModuleSearchString, aExpected) { GenericImportHelper.call(this, "settings", aModuleSearchString, aFile); this.mExpected = aExpected; } SettingsImportHelper.prototype = { __proto__: GenericImportHelper.prototype, interfaceType: Ci.nsIImportSettings, /** * SettingsImportHelper.beginImport * Imports settings from a specific file or auto-located if the file is null, * and compare the import results with the expected array. */ beginImport() { this._ensureNoAccounts(); if (this.mFile) { this.mInterface.SetLocation(this.mFile); } else { Assert.equal(true, this.mInterface.AutoLocate({}, {})); } Assert.equal(true, this.mInterface.Import({})); this.checkResults(); }, _ensureNoAccounts() { for (let account of MailServices.accounts.accounts) { MailServices.accounts.removeAccount(account); } }, _checkSmtpServer(expected, actual) { Assert.equal(expected.port, actual.port); Assert.equal(expected.username, actual.username); Assert.equal(expected.authMethod, actual.authMethod); Assert.equal(expected.socketType, actual.socketType); }, _checkIdentity(expected, actual) { Assert.equal(expected.fullName, actual.fullName); Assert.equal(expected.email, actual.email); Assert.equal(expected.replyTo, actual.replyTo); Assert.equal(expected.organization, actual.organization); }, _checkPop3IncomingServer(expected, actual) { Assert.equal(expected.leaveMessagesOnServer, actual.leaveMessagesOnServer); Assert.equal( expected.deleteMailLeftOnServer, actual.deleteMailLeftOnServer ); Assert.equal(expected.deleteByAgeFromServer, actual.deleteByAgeFromServer); Assert.equal( expected.numDaysToLeaveOnServer, actual.numDaysToLeaveOnServer ); }, _checkIncomingServer(expected, actual) { Assert.equal(expected.type, actual.type); Assert.equal(expected.port, actual.port); Assert.equal(expected.username, actual.username); Assert.equal(expected.isSecure, actual.isSecure); Assert.equal(expected.hostName, actual.hostName); Assert.equal(expected.prettyName, actual.prettyName); Assert.equal(expected.authMethod, actual.authMethod); Assert.equal(expected.socketType, actual.socketType); Assert.equal(expected.doBiff, actual.doBiff); Assert.equal(expected.biffMinutes, actual.biffMinutes); if (expected.type == "pop3") { this._checkPop3IncomingServer( expected, actual.QueryInterface(Ci.nsIPop3IncomingServer) ); } }, _checkAccount(expected, actual) { this._checkIncomingServer(expected.incomingServer, actual.incomingServer); Assert.equal(1, actual.identities.length); let actualIdentity = actual.identities[0]; this._checkIdentity(expected.identity, actualIdentity); if (expected.incomingServer.type != "nntp") { let actualSmtpServer = MailServices.smtp.getServerByKey( actualIdentity.smtpServerKey ); this._checkSmtpServer(expected.smtpServer, actualSmtpServer); } }, _isLocalMailAccount(account) { return ( account.incomingServer.type == "none" && account.incomingServer.username == "nobody" && account.incomingServer.hostName == "Local Folders" ); }, _findExpectedAccount(account) { return this.mExpected.filter(function (expectedAccount) { return ( expectedAccount.incomingServer.type == account.incomingServer.type && expectedAccount.incomingServer.username == account.incomingServer.username && expectedAccount.incomingServer.hostName == account.incomingServer.hostName ); }); }, checkResults() { for (let actualAccount of MailServices.accounts.accounts) { if (this._isLocalMailAccount(actualAccount)) { continue; } let expectedAccounts = this._findExpectedAccount(actualAccount); Assert.notEqual(null, expectedAccounts); Assert.equal(1, expectedAccounts.length); this._checkAccount(expectedAccounts[0], actualAccount); } }, }; /** * FiltersImportHelper * A helper for filter imports. * * @param aFile An instance of nsIFile to import. * @param aModuleSearchString * The string to search the module names for, such as * "Outlook Express", etc. * @param aExpected The number of filters that should exist after import. * * @class * @class */ function FiltersImportHelper(aFile, aModuleSearchString, aExpected) { GenericImportHelper.call(this, "filters", aModuleSearchString, aFile); this.mExpected = aExpected; } FiltersImportHelper.prototype = { __proto__: GenericImportHelper.prototype, interfaceType: Ci.nsIImportFilters, /** * FiltersImportHelper.beginImport * Imports filters from a specific file/folder or auto-located if the file is null, * and compare the import results with the expected array. */ beginImport() { if (this.mFile) { this.mInterface.SetLocation(this.mFile); } else { Assert.equal(true, this.mInterface.AutoLocate({}, {})); } Assert.equal(true, this.mInterface.Import({})); this.checkResults(); }, _loopOverFilters(aFilterList, aCondition) { let result = 0; for (let i = 0; i < aFilterList.filterCount; i++) { let filter = aFilterList.getFilterAt(i); if (aCondition(filter)) { result++; } } return result; }, checkResults() { let expected = this.mExpected; let server = MailServices.accounts.localFoldersServer; let filterList = server.getFilterList(null); if ("count" in expected) { Assert.equal(filterList.filterCount, expected.count); } if ("enabled" in expected) { Assert.equal( this._loopOverFilters(filterList, f => f.enabled), expected.enabled ); } if ("incoming" in expected) { Assert.equal( this._loopOverFilters( filterList, f => f.filterType & Ci.nsMsgFilterType.InboxRule ), expected.incoming ); } if ("outgoing" in expected) { Assert.equal( this._loopOverFilters( filterList, f => f.filterType & Ci.nsMsgFilterType.PostOutgoing ), expected.outgoing ); } }, };