Bug 1415451 - [Form Autofill] Storage should avoid saving an empty record. r=steveck
authorLuke Chang <lchang@mozilla.com>
Tue, 07 Nov 2017 20:07:32 +0800
changeset 436660 10594729070e1e9e4865864b5c73e5008f0ff3e7
parent 436659 865003783c5d8a0d9dad0eda65105c7d57669560
child 436661 37ecedbfdae92a8b7f3c334a33637ce035f1f804
push id117
push userfmarier@mozilla.com
push dateTue, 28 Nov 2017 20:17:16 +0000
reviewerssteveck
bugs1415451
milestone59.0a1
Bug 1415451 - [Form Autofill] Storage should avoid saving an empty record. r=steveck MozReview-Commit-ID: BFomE2mTG84
browser/extensions/formautofill/ProfileStorage.jsm
browser/extensions/formautofill/test/unit/test_addressRecords.js
browser/extensions/formautofill/test/unit/test_creditCardRecords.js
browser/extensions/formautofill/test/unit/test_transformFields.js
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -407,36 +407,42 @@ class AutofillRecords {
       throw new Error("No matching record.");
     }
 
     // Clone the record before modifying it to avoid exposing incomplete changes.
     let recordFound = this._clone(this.data[recordFoundIndex]);
     this._stripComputedFields(recordFound);
 
     let recordToUpdate = this._clone(record);
-    this._normalizeRecord(recordToUpdate);
+    this._normalizeRecord(recordToUpdate, true);
 
+    let hasValidField = false;
     for (let field of this.VALID_FIELDS) {
       let oldValue = recordFound[field];
       let newValue = recordToUpdate[field];
 
       // Resume the old field value in the perserve case
       if (preserveOldProperties && newValue === undefined) {
         newValue = oldValue;
       }
 
       if (newValue === undefined || newValue === "") {
         delete recordFound[field];
       } else {
+        hasValidField = true;
         recordFound[field] = newValue;
       }
 
       this._maybeStoreLastSyncedField(recordFound, field, oldValue);
     }
 
+    if (!hasValidField) {
+      throw new Error("Record contains no valid field.");
+    }
+
     recordFound.timeLastModified = Date.now();
     let syncMetadata = this._getSyncMetaData(recordFound);
     if (syncMetadata) {
       syncMetadata.changeCounter += 1;
     }
 
     this._computeFields(recordFound);
     this.data[recordFoundIndex] = recordFound;
@@ -1134,27 +1140,34 @@ class AutofillRecords {
       // Force to recompute fields if we upgrade the schema.
       this._stripComputedFields(record);
     }
 
     hasChanges |= this._computeFields(record);
     return hasChanges;
   }
 
-  _normalizeRecord(record) {
+  _normalizeRecord(record, preserveEmptyFields = false) {
     this._normalizeFields(record);
 
     for (let key in record) {
       if (!this.VALID_FIELDS.includes(key)) {
         throw new Error(`"${key}" is not a valid field.`);
       }
       if (typeof record[key] !== "string" &&
           typeof record[key] !== "number") {
         throw new Error(`"${key}" contains invalid data type.`);
       }
+      if (!preserveEmptyFields && record[key] === "") {
+        delete record[key];
+      }
+    }
+
+    if (!Object.keys(record).length) {
+      throw new Error("Record contains no valid field.");
     }
   }
 
   /**
    * Merge the record if storage has multiple mergeable records.
    * @param {Object} targetRecord
    *        The record for merge.
    * @param {boolean} [strict = false]
@@ -1209,17 +1222,20 @@ class Addresses extends AutofillRecords 
       delete address.country;
       delete address["country-name"];
     }
   }
 
   _computeFields(address) {
     // NOTE: Remember to bump the schema version number if any of the existing
     //       computing algorithm changes. (No need to bump when just adding new
-    //       computed fields)
+    //       computed fields.)
+
+    // NOTE: Computed fields should be always present in the storage no matter
+    //       it's empty or not.
 
     let hasNewComputedFields = false;
 
     if (address.deleted) {
       return hasNewComputedFields;
     }
 
     // Compute name
@@ -1398,18 +1414,18 @@ class Addresses extends AutofillRecords 
   mergeIfPossible(guid, address, strict) {
     this.log.debug("mergeIfPossible:", guid, address);
 
     let addressFound = this._findByGUID(guid);
     if (!addressFound) {
       throw new Error("No matching address.");
     }
 
-    let addressToMerge = strict ? this._clone(address) : this._cloneAndCleanUp(address);
-    this._normalizeRecord(addressToMerge);
+    let addressToMerge = this._clone(address);
+    this._normalizeRecord(addressToMerge, strict);
     let hasMatchingField = false;
 
     for (let field of this.VALID_FIELDS) {
       let existingField = addressFound[field];
       let incomingField = addressToMerge[field];
       if (incomingField !== undefined && existingField !== undefined) {
         if (incomingField != existingField) {
           // Treat "street-address" as mergeable if their single-line versions
@@ -1470,50 +1486,54 @@ class CreditCards extends AutofillRecord
       throw new Error(`Invalid credit card number`);
     }
     return "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
   }
 
   _computeFields(creditCard) {
     // NOTE: Remember to bump the schema version number if any of the existing
     //       computing algorithm changes. (No need to bump when just adding new
-    //       computed fields)
+    //       computed fields.)
+
+    // NOTE: Computed fields should be always present in the storage no matter
+    //       it's empty or not.
 
     let hasNewComputedFields = false;
 
     if (creditCard.deleted) {
       return hasNewComputedFields;
     }
 
     // Compute split names
     if (!("cc-given-name" in creditCard)) {
       let nameParts = FormAutofillNameUtils.splitName(creditCard["cc-name"]);
       creditCard["cc-given-name"] = nameParts.given;
       creditCard["cc-additional-name"] = nameParts.middle;
       creditCard["cc-family-name"] = nameParts.family;
       hasNewComputedFields = true;
     }
 
-    let year = creditCard["cc-exp-year"];
-    let month = creditCard["cc-exp-month"];
-    if (!creditCard["cc-exp"] && month && year) {
-      creditCard["cc-exp"] = String(year) + "-" + String(month).padStart(2, "0");
+    // Compute credit card expiration date
+    if (!("cc-exp" in creditCard)) {
+      if (creditCard["cc-exp-month"] && creditCard["cc-exp-year"]) {
+        creditCard["cc-exp"] = String(creditCard["cc-exp-year"]) + "-" + String(creditCard["cc-exp-month"]).padStart(2, "0");
+      } else {
+        creditCard["cc-exp"] = "";
+      }
       hasNewComputedFields = true;
     }
 
     // Encrypt credit card number
     if (!("cc-number-encrypted" in creditCard)) {
       let ccNumber = (creditCard["cc-number"] || "").replace(/\s/g, "");
       if (FormAutofillUtils.isCCNumber(ccNumber)) {
         creditCard["cc-number"] = this._getMaskedCCNumber(ccNumber);
         creditCard["cc-number-encrypted"] = MasterPassword.encryptSync(ccNumber);
       } else {
         delete creditCard["cc-number"];
-        // Computed fields are always present in the storage no matter it's
-        // empty or not.
         creditCard["cc-number-encrypted"] = "";
       }
     }
 
     return hasNewComputedFields;
   }
 
   _stripComputedFields(creditCard) {
@@ -1650,16 +1670,17 @@ class CreditCards extends AutofillRecord
         return creditCard.guid;
       }
     }
     return null;
   }
 
   /**
    * Merge new credit card into the specified record if cc-number is identical.
+   * (Note that credit card records always do non-strict merge.)
    *
    * @param  {string} guid
    *         Indicates which credit card to merge.
    * @param  {Object} creditCard
    *         The new credit card used to merge into the old one.
    * @returns {boolean}
    *          Return true if credit card is merged into target with specific guid or false if not.
    */
@@ -1667,17 +1688,17 @@ class CreditCards extends AutofillRecord
     this.log.debug("mergeIfPossible:", guid, creditCard);
 
     // Query raw data for comparing the decrypted credit card number
     let creditCardFound = this.get(guid, {rawData: true});
     if (!creditCardFound) {
       throw new Error("No matching credit card.");
     }
 
-    let creditCardToMerge = this._cloneAndCleanUp(creditCard);
+    let creditCardToMerge = this._clone(creditCard);
     this._normalizeRecord(creditCardToMerge);
 
     for (let field of this.VALID_FIELDS) {
       let existingField = creditCardFound[field];
 
       // Make sure credit card field is existed and have value
       if (field == "cc-number" && (!existingField || !creditCardToMerge[field])) {
         return false;
--- a/browser/extensions/formautofill/test/unit/test_addressRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -44,16 +44,25 @@ const TEST_ADDRESS_WITH_EMPTY_FIELD = {
   "street-address": "",
 };
 
 const TEST_ADDRESS_WITH_INVALID_FIELD = {
   "street-address": "Another Address",
   invalidField: "INVALID",
 };
 
+const TEST_ADDRESS_EMPTY_AFTER_NORMALIZE = {
+  country: "XXXXXX",
+};
+
+const TEST_ADDRESS_EMPTY_AFTER_UPDATE_ADDRESS_2 = {
+  "street-address": "",
+  country: "XXXXXX",
+};
+
 const MERGE_TESTCASES = [
   {
     description: "Merge a superset",
     addressInStorage: {
       "given-name": "Timothy",
       "street-address": "331 E. Evelyn Avenue",
       "tel": "+16509030800",
     },
@@ -325,16 +334,22 @@ add_task(async function test_add() {
   // Empty string should be deleted before saving.
   profileStorage.addresses.add(TEST_ADDRESS_WITH_EMPTY_FIELD);
   let address = profileStorage.addresses.data[2];
   do_check_eq(address.name, TEST_ADDRESS_WITH_EMPTY_FIELD.name);
   do_check_eq(address["street-address"], undefined);
 
   Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./);
+
+  Assert.throws(() => profileStorage.addresses.add({}),
+    /Record contains no valid field\./);
+
+  Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_EMPTY_AFTER_NORMALIZE),
+    /Record contains no valid field\./);
 });
 
 add_task(async function test_update() {
   let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
                                                 [TEST_ADDRESS_1, TEST_ADDRESS_2]);
 
   let addresses = profileStorage.addresses.getAll();
   let guid = addresses[1].guid;
@@ -387,16 +402,32 @@ add_task(async function test_update() {
     () => profileStorage.addresses.update("INVALID_GUID", TEST_ADDRESS_3),
     /No matching record\./
   );
 
   Assert.throws(
     () => profileStorage.addresses.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./
   );
+
+  Assert.throws(
+    () => profileStorage.addresses.update(guid, {}),
+    /Record contains no valid field\./
+  );
+
+  Assert.throws(
+    () => profileStorage.addresses.update(guid, TEST_ADDRESS_EMPTY_AFTER_NORMALIZE),
+    /Record contains no valid field\./
+  );
+
+  profileStorage.addresses.update(guid, TEST_ADDRESS_2);
+  Assert.throws(
+    () => profileStorage.addresses.update(guid, TEST_ADDRESS_EMPTY_AFTER_UPDATE_ADDRESS_2),
+    /Record contains no valid field\./
+  );
 });
 
 add_task(async function test_notifyUsed() {
   let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
                                                 [TEST_ADDRESS_1, TEST_ADDRESS_2]);
 
   let addresses = profileStorage.addresses.getAll();
   let guid = addresses[1].guid;
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -58,16 +58,27 @@ const TEST_CREDIT_CARD_WITH_INVALID_EXPI
   "cc-exp-year": -3,
 };
 
 const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
   "cc-name": "John Doe",
   "cc-number": "1111 2222 3333 4444",
 };
 
+const TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE = {
+  "cc-exp-month": 13,
+};
+
+const TEST_CREDIT_CARD_EMPTY_AFTER_UPDATE_CREDIT_CARD_1 = {
+  "cc-name": "",
+  "cc-number": "",
+  "cc-exp-month": 13,
+  "cc-exp-year": "",
+};
+
 const MERGE_TESTCASES = [
   {
     description: "Merge a superset",
     creditCardInStorage: {
       "cc-number": "1234567812345678",
       "cc-exp-month": 4,
       "cc-exp-year": 2017,
     },
@@ -246,16 +257,22 @@ add_task(async function test_add() {
   // Empty string should be deleted before saving.
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_EMPTY_FIELD);
   let creditCard = profileStorage.creditCards.data[2];
   do_check_eq(creditCard["cc-exp-month"], TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]);
   do_check_eq(creditCard["cc-name"], undefined);
 
   Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./);
+
+  Assert.throws(() => profileStorage.creditCards.add({}),
+    /Record contains no valid field\./);
+
+  Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE),
+    /Record contains no valid field\./);
 });
 
 add_task(async function test_update() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
   await prepareTestCreditCards(path);
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
@@ -291,16 +308,32 @@ add_task(async function test_update() {
     () => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
     /No matching record\./
   );
 
   Assert.throws(
     () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
     /"invalidField" is not a valid field\./
   );
+
+  Assert.throws(
+    () => profileStorage.creditCards.update(guid, {}),
+    /Record contains no valid field\./
+  );
+
+  Assert.throws(
+    () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE),
+    /Record contains no valid field\./
+  );
+
+  profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_1);
+  Assert.throws(
+    () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_EMPTY_AFTER_UPDATE_CREDIT_CARD_1),
+    /Record contains no valid field\./
+  );
 });
 
 add_task(async function test_validate() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -4,25 +4,16 @@
 
 "use strict";
 
 const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
 const ADDRESS_COMPUTE_TESTCASES = [
-  // Empty
-  {
-    description: "Empty address",
-    address: {
-    },
-    expectedResult: {
-    },
-  },
-
   // Name
   {
     description: "Has split names",
     address: {
       "given-name": "Timothy",
       "additional-name": "John",
       "family-name": "Berners-Lee",
     },
@@ -193,25 +184,16 @@ const ADDRESS_COMPUTE_TESTCASES = [
       "tel-local": undefined,
       "tel-local-prefix": undefined,
       "tel-local-suffix": undefined,
     },
   },
 ];
 
 const ADDRESS_NORMALIZE_TESTCASES = [
-  // Empty
-  {
-    description: "Empty address",
-    address: {
-    },
-    expectedResult: {
-    },
-  },
-
   // Name
   {
     description: "Has \"name\", and the split names are omitted",
     address: {
       "name": "Timothy John Berners-Lee",
     },
     expectedResult: {
       "given-name": "Timothy",
@@ -300,16 +282,17 @@ const ADDRESS_NORMALIZE_TESTCASES = [
     },
     expectedResult: {
       "country": "US",
     },
   },
   {
     description: "Has unknown \"country\"",
     address: {
+      "given-name": "John", // Make sure it won't be an empty record.
       "country": "AA",
     },
     expectedResult: {
       "country": undefined,
     },
   },
   {
     description: "Has \"country-name\"",
@@ -339,26 +322,28 @@ const ADDRESS_NORMALIZE_TESTCASES = [
     expectedResult: {
       "country": "US",
       "country-name": "United States",
     },
   },
   {
     description: "Has \"country-name\" as part of a word",
     address: {
+      "given-name": "John", // Make sure it won't be an empty record.
       "country-name": "TRUST",
     },
     expectedResult: {
       "country": undefined,
       "country-name": undefined,
     },
   },
   {
     description: "Has unknown \"country-name\"",
     address: {
+      "given-name": "John", // Make sure it won't be an empty record.
       "country-name": "unknown country name",
     },
     expectedResult: {
       "country": undefined,
       "country-name": undefined,
     },
   },
   {
@@ -370,27 +355,29 @@ const ADDRESS_NORMALIZE_TESTCASES = [
     expectedResult: {
       "country": "US",
       "country-name": "United States",
     },
   },
   {
     description: "Has \"country-name\" and unknown \"country\"",
     address: {
+      "given-name": "John", // Make sure it won't be an empty record.
       "country": "AA",
       "country-name": "united states",
     },
     expectedResult: {
       "country": undefined,
       "country-name": undefined,
     },
   },
   {
     description: "Has unsupported \"country\"",
     address: {
+      "given-name": "John", // Make sure it won't be an empty record.
       "country": "CA",
     },
     expectedResult: {
       "country": undefined,
       "country-name": undefined,
     },
   },
 
@@ -491,52 +478,77 @@ const ADDRESS_NORMALIZE_TESTCASES = [
     },
     expectedResult: {
       "tel": "+16172535702",
     },
   },
 ];
 
 const CREDIT_CARD_COMPUTE_TESTCASES = [
-  // Empty
-  {
-    description: "Empty credit card",
-    creditCard: {
-      "cc-number": "1234123412341234", // cc-number won't be verified
-    },
-    expectedResult: {
-    },
-  },
-
   // Name
   {
     description: "Has \"cc-name\"",
     creditCard: {
       "cc-name": "Timothy John Berners-Lee",
-      "cc-number": "1234123412341234", // cc-number won't be verified
     },
     expectedResult: {
       "cc-name": "Timothy John Berners-Lee",
       "cc-given-name": "Timothy",
       "cc-additional-name": "John",
       "cc-family-name": "Berners-Lee",
     },
   },
+
+  // Card Number
+  {
+    description: "Number should be encrypted and masked",
+    creditCard: {
+      "cc-number": "1234123412341234",
+    },
+    expectedResult: {
+      "cc-number": "************1234",
+    },
+  },
+
+  // Expiration Date
+  {
+    description: "Has \"cc-exp-year\" and \"cc-exp-month\"",
+    creditCard: {
+      "cc-exp-month": 12,
+      "cc-exp-year": 2022,
+    },
+    expectedResult: {
+      "cc-exp-month": 12,
+      "cc-exp-year": 2022,
+      "cc-exp": "2022-12",
+    },
+  },
+  {
+    description: "Has only \"cc-exp-month\"",
+    creditCard: {
+      "cc-exp-month": 12,
+    },
+    expectedResult: {
+      "cc-exp-month": 12,
+      "cc-exp": undefined,
+    },
+  },
+  {
+    description: "Has only \"cc-exp-year\"",
+    creditCard: {
+      "cc-exp-year": 2022,
+    },
+    expectedResult: {
+      "cc-exp-year": 2022,
+      "cc-exp": undefined,
+    },
+  },
 ];
 
 const CREDIT_CARD_NORMALIZE_TESTCASES = [
-  // Empty
-  {
-    description: "No normalizable field",
-    creditCard: {
-    },
-    expectedResult: {
-    },
-  },
-
   // Name
   {
     description: "Has both \"cc-name\" and the split name fields",
     creditCard: {
       "cc-name": "Timothy John Berners-Lee",
       "cc-given-name": "John",
       "cc-family-name": "Doe",
     },
@@ -704,16 +716,17 @@ const CREDIT_CARD_NORMALIZE_TESTCASES = 
     expectedResult: {
       "cc-exp-month": 11,
       "cc-exp-year": 2033,
     },
   },
   {
     description: "Has invalid \"cc-exp\"",
     creditCard: {
+      "cc-number": "1111222233334444", // Make sure it won't be an empty record.
       "cc-exp": "99-9999",
     },
     expectedResult: {
       "cc-exp-month": undefined,
       "cc-exp-year": undefined,
     },
   },
   {
@@ -745,110 +758,83 @@ const CREDIT_CARD_NORMALIZE_TESTCASES = 
       "cc-exp": "2022-12",
       "cc-exp-month": 3,
     },
     expectedResult: {
       "cc-exp-month": 12,
       "cc-exp-year": 2022,
     },
   },
-
-  // Card Number
-  {
-    description: "Number should be encrypted and masked",
-    creditCard: {
-      "cc-number": "1234123412341234",
-    },
-    expectedResult: {
-      "cc-number": "************1234",
-    },
-  },
 ];
 
 let do_check_record_matches = (expectedRecord, record) => {
   for (let key in expectedRecord) {
     do_check_eq(expectedRecord[key], record[key]);
   }
 };
 
 add_task(async function test_computeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  ADDRESS_COMPUTE_TESTCASES.forEach(testcase => profileStorage.addresses.add(testcase.address));
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
+  ADDRESS_COMPUTE_TESTCASES.forEach(testcase => {
+    do_print("Verify testcase: " + testcase.description);
 
-  let addresses = profileStorage.addresses.getAll();
+    let guid = profileStorage.addresses.add(testcase.address);
+    let address = profileStorage.addresses.get(guid);
+    do_check_record_matches(testcase.expectedResult, address);
 
-  for (let i in addresses) {
-    do_print("Verify testcase: " + ADDRESS_COMPUTE_TESTCASES[i].description);
-    do_check_record_matches(ADDRESS_COMPUTE_TESTCASES[i].expectedResult, addresses[i]);
-  }
+    profileStorage.addresses.remove(guid);
+  });
 });
 
 add_task(async function test_normalizeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  ADDRESS_NORMALIZE_TESTCASES.forEach(testcase => profileStorage.addresses.add(testcase.address));
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
+  ADDRESS_NORMALIZE_TESTCASES.forEach(testcase => {
+    do_print("Verify testcase: " + testcase.description);
 
-  let addresses = profileStorage.addresses.getAll();
+    let guid = profileStorage.addresses.add(testcase.address);
+    let address = profileStorage.addresses.get(guid);
+    do_check_record_matches(testcase.expectedResult, address);
 
-  for (let i in addresses) {
-    do_print("Verify testcase: " + ADDRESS_NORMALIZE_TESTCASES[i].description);
-    do_check_record_matches(ADDRESS_NORMALIZE_TESTCASES[i].expectedResult, addresses[i]);
-  }
+    profileStorage.addresses.remove(guid);
+  });
 });
 
 add_task(async function test_computeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  for (let testcase of CREDIT_CARD_COMPUTE_TESTCASES) {
-    profileStorage.creditCards.add(testcase.creditCard);
-  }
-  await profileStorage._saveImmediately();
+  CREDIT_CARD_COMPUTE_TESTCASES.forEach(testcase => {
+    do_print("Verify testcase: " + testcase.description);
 
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let creditCards = profileStorage.creditCards.getAll();
+    let guid = profileStorage.creditCards.add(testcase.creditCard);
+    let creditCard = profileStorage.creditCards.get(guid);
+    do_check_record_matches(testcase.expectedResult, creditCard);
 
-  for (let i in creditCards) {
-    do_print("Verify testcase: " + CREDIT_CARD_COMPUTE_TESTCASES[i].description);
-    do_check_record_matches(CREDIT_CARD_COMPUTE_TESTCASES[i].expectedResult, creditCards[i]);
-  }
+    profileStorage.creditCards.remove(guid);
+  });
 });
 
 add_task(async function test_normalizeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  for (let testcase of CREDIT_CARD_NORMALIZE_TESTCASES) {
-    profileStorage.creditCards.add(testcase.creditCard);
-  }
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
+  CREDIT_CARD_NORMALIZE_TESTCASES.forEach(testcase => {
+    do_print("Verify testcase: " + testcase.description);
 
-  let creditCards = profileStorage.creditCards.getAll();
+    let guid = profileStorage.creditCards.add(testcase.creditCard);
+    let creditCard = profileStorage.creditCards.get(guid, {rawData: true});
+    do_check_record_matches(testcase.expectedResult, creditCard);
 
-  for (let i in creditCards) {
-    do_print("Verify testcase: " + CREDIT_CARD_NORMALIZE_TESTCASES[i].description);
-    do_check_record_matches(CREDIT_CARD_NORMALIZE_TESTCASES[i].expectedResult, creditCards[i]);
-  }
+    profileStorage.creditCards.remove(guid);
+  });
 });