Test case for Bug 439819 LDIF import does not include mozillahomestreet. r=me
authorJosh Geenen <joshgeenen+bugzilla@gmail.com>
Mon, 28 Jul 2008 08:53:17 -0700
changeset 37 68765c9e5a95bd9c220b7becc32b4523486b6e6e
parent 36 1b3d7cd8932925318ba1daefe90b2f942f83b65f
child 38 014093b714605ae1b6ba31da104d013c480ad9ae
push idunknown
push userunknown
push dateunknown
reviewersme
bugs439819
Test case for Bug 439819 LDIF import does not include mozillahomestreet. r=me
mailnews/import/test/resources/AB_README
mailnews/import/test/resources/addressbook.json
mailnews/import/test/resources/basic_ldif_addressbook.ldif
mailnews/import/test/resources/import_helper.js
mailnews/import/test/unit/test_bug_437556.js
mailnews/import/test/unit/test_ldif_import.js
new file mode 100644
--- /dev/null
+++ b/mailnews/import/test/resources/AB_README
@@ -0,0 +1,39 @@
+To test importing an address book, make a new file in the
+/import/test/unit directory with the prefix test_ in the filename
+(ex. test_ldif_import.js).
+
+It should have a function named run_test with no parameters.  If you are using
+import_helper.js, which is already imported, you must at least get the file
+to import and make a new AbImportHelper object with at least the file and type
+of import.  Call the beginImport method on the object when you are ready to
+start the import.
+
+If you would like the results of the import checked, make sure to update
+addressbook.json.  This file is read by import_helper.js to compare the address
+book cards imported to an array of "cards" in in this file. When making a new
+import, first chose a name for the array (like basic_addressbook) to store the
+cards that should be in the newly-imported address book.  The properties and
+values of each object in the array should identical to the properties and values
+of the newly-imported card(s) and the cards themselves need to be in the
+expected order.  If a card to be imported does not have a property, do not
+include it in the JSON card.  Multiple types of imports can be tested with one
+array, as only the supported attributes are checked.
+
+You will also need to give the AbImportHelper constructor two additional
+parameters: the name the imported address book will have (the filename without
+the extension) and the name you chose for the JSON object.
+
+Here is a sample unit test that doesn't check the results:
+function run_test()
+{
+  var file = do_get_file("../mailnews/import/test/resources/basic_ldif_addressbook.ldif");
+  new AbImportHelper(file, "ldif").beginImport();
+}
+
+Here is a sample unit test that checks the results:
+function run_test()
+{
+  var file = do_get_file("../mailnews/import/test/resources/basic_ldif_addressbook.ldif");
+  new AbImportHelper(file, "ldif", "basic_ldif_addressbook",
+                     "basic_addressbook").beginImport();
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/import/test/resources/addressbook.json
@@ -0,0 +1,46 @@
+{
+  "basic_addressbook" :
+  [
+    {
+      "DisplayName"      : "Display Name",
+      "PrimaryEmail"     : "primaryemail@host.invalid",
+      "FirstName"        : "First",
+      "LastName"         : "Last",
+      "NickName"         : "Nickname",
+      "SecondEmail"      : "secondemail@host.invalid",
+      "_AimScreenName"    : "screenname",
+      "PreferMailFormat" : "html",
+      "LastModifiedDate" : 1213818826,
+      "WorkPhone"        : "123-456-7890",
+      "HomePhone"        : "234-567-8901",
+      "FaxNumber"        : "345-678-9012",
+      "PagerNumber"      : "456-789-0123",
+      "CellularNumber"   : "567-890-1234",
+      "HomeAddress"      : "Home Address Line 1",
+      "HomeAddress2"     : "Home Address Line 2",
+      "HomeCity"         : "Home City",
+      "HomeState"        : "Home State",
+      "HomeZipCode"      : "Home Zip",
+      "HomeCountry"      : "Home Country",
+      "WorkAddress"      : "Work Address Line 1",
+      "WorkAddress2"     : "Work Address Line 2",
+      "WorkCity"         : "Work City",
+      "WorkState"        : "Work State",
+      "WorkZipCode"      : "Work Zip",
+      "WorkCountry"      : "Work Country",
+      "JobTitle"         : "Job Title",
+      "Department"       : "Department",
+      "Company"          : "Organization Name",
+      "WebPage1"         : "http://127.0.0.1",
+      "WebPage2"         : "http://localhost",
+      "BirthYear"        : "1900",
+      "BirthMonth"       : "1",
+      "BirthDay"         : "2",
+      "Custom1"          : "Custom Field 1",
+      "Custom2"          : "Custom Field 2",
+      "Custom3"          : "Custom Field 3",
+      "Custom4"          : "Custom Field 4",
+      "Notes"            : "Notes line 1\nNotes line 2\nNotes line 3\nNotes line 4"
+    }
+  ]
+}
new file mode 100644
--- /dev/null
+++ b/mailnews/import/test/resources/basic_ldif_addressbook.ldif
@@ -0,0 +1,43 @@
+dn: cn=Display Name,mail=primaryemail@host.invalid
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: mozillaAbPersonAlpha
+givenName: First
+sn: Last
+cn: Display Name
+mozillaNickname: Nickname
+mail: primaryemail@host.invalid
+mozillaSecondEmail: secondemail@host.invalid
+nsAIMid: screenname
+mozillaUseHtmlMail: true
+modifytimestamp: 1213818826
+telephoneNumber: 123-456-7890
+homePhone: 234-567-8901
+fax: 345-678-9012
+pager: 456-789-0123
+mobile: 567-890-1234
+mozillaHomeStreet: Home Address Line 1
+mozillaHomeStreet2: Home Address Line 2
+mozillaHomeLocalityName: Home City
+mozillaHomeState: Home State
+mozillaHomePostalCode: Home Zip
+mozillaHomeCountryName: Home Country
+street: Work Address Line 1
+mozillaWorkStreet2: Work Address Line 2
+l: Work City
+st: Work State
+postalCode: Work Zip
+c: Work Country
+title: Job Title
+ou: Department
+o: Organization Name
+mozillaWorkUrl: http://127.0.0.1
+mozillaHomeUrl: http://localhost
+birthyear: 1900
+mozillaCustom1: Custom Field 1
+mozillaCustom2: Custom Field 2
+mozillaCustom3: Custom Field 3
+mozillaCustom4: Custom Field 4
+description:: Tm90ZXMgbGluZSAxCk5vdGVzIGxpbmUgMgpOb3RlcyBsaW5lIDMKTm90ZXMgbGluZSA0
--- a/mailnews/import/test/resources/import_helper.js
+++ b/mailnews/import/test/resources/import_helper.js
@@ -1,37 +1,278 @@
+// used by checkProgress to periodically check the progress of the import
+var gAbImportHelper;
 /**
- * Returns an import interface based on the name of the module and a string
- * to search for.  Throws an error if it cannot find the module.
+ * 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 aType     The type of import.  Should be LDIF, CSV, or TAB.
  *
- * @param moduleName The name of the module, such as "addressbook"
- * @param searchStr  The string to search the module names for, such as ".csv"
- *                   to find the import module for comma-separated value files
- * @return           An nsIImportGeneric import interface.
+ * 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.
+ * @constructor
+ * @class
  */
-function getImportInterface(moduleName, searchStr)
+function AbImportHelper(aFile, aType, aAbName, aJsonName)
 {
-  do_check_true(moduleName && moduleName.length > 0);
-  do_check_true(searchStr && searchStr.length > 0);
+  helper = null;
+  this.mFile = aFile; // checked in the beginImport method
+  this.mAbName = aAbName;
+
+  /* Attribute notes:  The attributes listed in the declaration below are
+   * supported by all three text export/import types.  AimScreenName &
+   * PreferMailFormat are only supported by LDIF, and BirthMonth and BirthDay
+   * are only supported by CSV and tab-delimited exports/imports.
+   * The following are not supported: anniversaryYear, anniversaryMonth,
+   * anniversaryDay, popularityIndex, isMailList, mailListURI, lastModifiedDate,
+   * and allowRemoteContent
+   */
+  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", "WebPage1", "WebPage2", "Custom1", "Custom2",
+     "Custom3", "Custom4", "Notes"];
+  // get the extra attributes supported for the given type of import
+  if (aType == "LDIF")
+  {
+    // LDIF: add AimScreenName and PreferMailFormat
+    this.mSupportedAttributes = supportedAttributes.concat(["_AimScreenName",
+                                                            "PreferMailFormat"]);
+    this.mLdif = true;
+  }
+  else if (aType == "CSV" || aType == "TAB")
+  {
+    // CSV or TAB: add BirthMonth and BirthDay
+    this.mSupportedAttributes = supportedAttributes.concat(["BirthMonth",
+                                                            "BirthDay"]);
+    this.mLdif = false;
+  }
+  else
+    do_throw("Unexpected type passed to the AbImportHelper constructor");
+  // get the "cards" from the JSON file, if necessary
+  if (aJsonName)
+    this.mJsonCards = this.getJsonCards(aJsonName);
+}
+AbImportHelper.prototype =
+{
+  /**
+   * AbImportHelper.beginImport
+   * Imports the given address book export and checks the imported address book
+   * with the array in addressbook.json if aAbName and aJsonName were supplied
+   * to the constructor.
+   */
+  beginImport: function()
+  {
+    do_check_true(this.mFile instanceof Ci.nsIFile && this.mFile.exists() &&
+                  this.mFile.isFile());
 
-  var importService = Cc["@mozilla.org/import/import-service;1"]
-                        .getService(Ci.nsIImportService);
-  var module;
-  var count = importService.GetModuleCount(moduleName);
+    // get the import interface used for all text imports
+    this.mAbInterface = this.getInterface("addressbook", ".csv");
+    this.mAbInterface.SetData("addressLocation", this.mFile);
+    // skip setting the field map if this is an LDIF import
+    if (!this.mLdif)
+      this.mAbInterface.SetData("fieldMap", this.getDefaultFieldMap(true));
+
+    do_check_true(this.mAbInterface.WantsProgress());
+    do_check_true(this.mAbInterface.BeginImport(null, null, false));
+    do_test_pending();
+    this.checkProgress();
+  },
+  /**
+   * AbImportHelper.getInterface
+   * Returns an import interface based on the name of the module and a string
+   * to search for.
+   *
+   * @param aModuleName The name of the module, such as "addressbook".
+   * @param aSearchStr  The string to search the module names for, such as
+   *                    ".csv" to find the import module for comma-separated
+   *                    value, LDIF, and tab-delimited files.
+   * @return An nsIImportGeneric import interface.
+   */
+  getInterface: function(aModuleName, aSearchStr)
+  {
+    do_check_true(aModuleName && aModuleName.length > 0);
+    do_check_true(aSearchStr && aSearchStr.length > 0);
+
+    var importService = Cc["@mozilla.org/import/import-service;1"]
+                         .getService(Ci.nsIImportService);
+    var module;
+    var count = importService.GetModuleCount(aModuleName);
+
+    // 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(aModuleName, i).indexOf(aSearchStr) != -1)
+        return importService.GetModule(aModuleName, i)
+                            .GetImportInterface(aModuleName)
+                            .QueryInterface(Ci.nsIImportGeneric);
+    return null; // it wasn't found
+  },
+  /**
+   * AbImportHelper.getDefaultFieldMap
+   * Returns the default field map.
+   *
+   * @param aSkipFirstRecord True if the first record of the text file should
+   *                         be skipped.
+   * @return A default field map.
+   */
+  getDefaultFieldMap: function(aSkipFirstRecord)
+  {
+    var importService = Cc["@mozilla.org/import/import-service;1"]
+                         .getService(Ci.nsIImportService);
+    var fieldMap = importService.CreateNewFieldMap();
+
+    fieldMap.DefaultFieldMap(fieldMap.numMozFields);
+    fieldMap.skipFirstRecord = aSkipFirstRecord;
 
-  // Iterate through each import module until the one being searched for is found
-  for (var i = 0; i < count; i++)
+    return fieldMap;
+  },
+  /**
+   * AbImportHelper.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: function()
   {
-    // Check if the current module fits the search string gets the interface
-    if (importService.GetModuleName(moduleName, i).indexOf(searchStr) != -1)
+    do_check_true(this.mAbInterface && 
+                  this.mAbInterface instanceof Ci.nsIImportGeneric);
+    do_check_true(this.mAbInterface.ContinueImport());
+    // if the import isn't done, check again in 200 milliseconds.
+    if (this.mAbInterface.GetProgress() != 100) {
+      // use the helper object to check the progress of the import after 200 ms
+      gAbImportHelper = this;
+      do_timeout(200, "gAbImportHelper.checkProgress();");
+    }
+    // if it is done, check the results or finish the test.
+    else
     {
-      module = importService.GetModule(moduleName, i);
-      break;
+      if (this.mAbName)
+        this.checkResults(this.mAbName);
+      else
+        do_test_finished();
     }
-  }
+  },
+  /**
+   * 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: function()
+  {
+    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);
+      do_check_neq(newAb, null);
+      do_check_true(newAb instanceof Ci.nsIAbDirectory &&
+                    newAb.childCards instanceof Ci.nsISimpleEnumerator);
+      // get the imported card(s) and check each one
+      var iter = newAb.childCards;
+      var count = 0;
+      for (; iter.hasMoreElements(); count++) {
+        var importedCard = iter.getNext().QueryInterface(Ci.nsIAbCard);
+        this.compareCards(this.mJsonCards[count], importedCard);
+      }
+      // make sure there are the same number of cards in the address book and
+      // the JSON array
+      do_check_eq(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.
+   * @return An nsIAbDirectory, if found.
+   *         null if the requested Address Book could not be found.
+   */
+  getAbByName: function(aName)
+  {
+    do_check_true(aName && aName.length > 0);
 
-  // Make sure the module was found.  If not, return false
-  if (!module)
+    var iter = Cc["@mozilla.org/abmanager;1"].getService(Ci.nsIAbManager)
+                                             .directories;
+    var data = null;
+    while (iter.hasMoreElements())
+    {
+      data = iter.getNext();
+      if (data instanceof Ci.nsIAbDirectory)
+        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: function(aJsonCard, aCard)
+  {
+    for (var i in aJsonCard)
+      if (this.mSupportedAttributes.indexOf(i) >= 0)
+          do_check_eq(aJsonCard[i], aCard.getCardValue(i));
+  },
+  /**
+   * 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.
+   * @return An array of "cards".
+   */
+  getJsonCards: function(aName)
+  {
+    if (!aName)
+      do_throw("Error - getJSONAb requires an address book name");
+    var file = do_get_file("../mailnews/import/test/resources/addressbook.json");
+    if (!file || !file.exists() || !file.isFile())
+      do_throw("Unable to get JSON file");
 
-  return module.GetImportInterface(moduleName)
-               .QueryInterface(Ci.nsIImportGeneric);
+    var fis = Cc["@mozilla.org/network/file-input-stream;1"]
+               .createInstance(Ci.nsIFileInputStream);
+    fis.init(file, 0x01, 0444, 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 nsIJSON = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);  
+    var arr = nsIJSON.decode(json)[aName];
+    do_check_true(arr && arr.length > 0);
+    return arr;
+  }
 }
--- a/mailnews/import/test/unit/test_bug_437556.js
+++ b/mailnews/import/test/unit/test_bug_437556.js
@@ -1,19 +1,21 @@
 /**
- * Test for a regression of bug 437556: mailnews crashes while importing an 
- * address book if a field map is required but not set
+ * Test for a regression of Bug 437556: mailnews crashes while importing an 
+ * address book if a field map is required but not set.
  */
 function run_test()
 {
   var file = do_get_file("../mailnews/import/test/resources/basic_addressbook.csv");
   var errorStr = Cc["@mozilla.org/supports-string;1"]
-                     .createInstance(Ci.nsISupportsString);
-  // get the text Address Book import interface and make sure it succeeded
-  var abInterface = getImportInterface("addressbook", ".csv");
+                  .createInstance(Ci.nsISupportsString);
+  // get the Address Book text import interface and make sure it succeeded
+  var helper = new AbImportHelper(file, "CSV");
+  var abInterface = helper.getInterface("addressbook", ".csv");
   do_check_neq(abInterface, null);
+  // prepare to start the import
   abInterface.SetData("addressLocation", file);
   do_check_true(abInterface.WantsProgress());
-
+  // start the import
   // BeginImport should return false and log an error if the fieldMap isn't set
   do_check_false(abInterface.BeginImport(null, errorStr, false));
-  do_check_neq(errorStr, null);
+  do_check_neq(errorStr, "");
 }
new file mode 100644
--- /dev/null
+++ b/mailnews/import/test/unit/test_ldif_import.js
@@ -0,0 +1,16 @@
+/**
+ * Tests importing an address book export in the LDAP data interchange (LDIF)
+ * format and checks the accuracy of the imported address book's cards.
+ * The current export contains only one card with most fields full.
+ *
+ * This test also checks for the following bugs:
+ *   -Bug 439819: LDIF import does not include mozillahomestreet.
+ *   -Bug 182128: Edit Card, Notes on several lines appear on one after
+ *                export/import in text format *(only tests the import).
+ */
+function run_test()
+{
+  var file = do_get_file("../mailnews/import/test/resources/basic_ldif_addressbook.ldif");
+  new AbImportHelper(file, "LDIF", "basic_ldif_addressbook",
+                     "basic_addressbook").beginImport();
+}