--- a/browser/extensions/formautofill/test/unit/test_addressRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -1,15 +1,16 @@
/**
* Tests ProfileStorage object with addresses records.
*/
"use strict";
const TEST_STORE_FILE_NAME = "test-profile.json";
+const TEST_COLLECTION_NAME = "addresses";
const TEST_ADDRESS_1 = {
"given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge",
@@ -208,37 +209,37 @@ const MERGE_TESTCASES = [
let do_check_record_matches = (recordWithMeta, record) => {
for (let key in record) {
do_check_eq(recordWithMeta[key], record[key]);
}
};
add_task(async function test_initialize() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME);
do_check_eq(profileStorage._store.data.version, 1);
do_check_eq(profileStorage._store.data.addresses.length, 0);
let data = profileStorage._store.data;
Assert.deepEqual(data.addresses, []);
await profileStorage._saveImmediately();
- profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
+ profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME);
Assert.deepEqual(profileStorage._store.data, data);
for (let {_sync} of profileStorage._store.data.addresses) {
Assert.ok(_sync);
Assert.equal(_sync.changeCounter, 1);
}
});
add_task(async function test_getAll() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
do_check_eq(addresses.length, 2);
do_check_record_matches(addresses[0], TEST_ADDRESS_1);
do_check_record_matches(addresses[1], TEST_ADDRESS_2);
@@ -254,17 +255,17 @@ add_task(async function test_getAll() {
do_check_eq(addresses[0]["address-line2"], undefined);
// Modifying output shouldn't affect the storage.
addresses[0].organization = "test";
do_check_record_matches(profileStorage.addresses.getAll()[0], TEST_ADDRESS_1);
});
add_task(async function test_get() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
let guid = addresses[0].guid;
let address = profileStorage.addresses.get(guid);
do_check_record_matches(address, TEST_ADDRESS_1);
@@ -277,17 +278,17 @@ add_task(async function test_get() {
// Modifying output shouldn't affect the storage.
address.organization = "test";
do_check_record_matches(profileStorage.addresses.get(guid), TEST_ADDRESS_1);
do_check_eq(profileStorage.addresses.get("INVALID_GUID"), null);
});
add_task(async function test_add() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
do_check_eq(addresses.length, 2);
do_check_record_matches(addresses[0], TEST_ADDRESS_1);
do_check_record_matches(addresses[1], TEST_ADDRESS_2);
@@ -299,17 +300,17 @@ add_task(async function test_add() {
do_check_eq(addresses[0].timeLastUsed, 0);
do_check_eq(addresses[0].timesUsed, 0);
Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_WITH_INVALID_FIELD),
/"invalidField" is not a valid field\./);
});
add_task(async function test_update() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
let guid = addresses[1].guid;
let timeLastModified = addresses[1].timeLastModified;
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
(subject, data) => data == "update");
@@ -352,17 +353,17 @@ add_task(async function test_update() {
Assert.throws(
() => profileStorage.addresses.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
/"invalidField" is not a valid field\./
);
});
add_task(async function test_notifyUsed() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
let guid = addresses[1].guid;
let timeLastUsed = addresses[1].timeLastUsed;
let timesUsed = addresses[1].timesUsed;
profileStorage.addresses.pullSyncChanges(); // force sync metadata, which we check below.
@@ -383,17 +384,17 @@ add_task(async function test_notifyUsed(
do_check_eq(getSyncChangeCounter(profileStorage.addresses, guid),
changeCounter);
Assert.throws(() => profileStorage.addresses.notifyUsed("INVALID_GUID"),
/No matching record\./);
});
add_task(async function test_remove() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
let guid = addresses[1].guid;
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
(subject, data) => data == "remove");
@@ -407,17 +408,17 @@ add_task(async function test_remove() {
do_check_eq(addresses.length, 1);
do_check_eq(profileStorage.addresses.get(guid), null);
});
MERGE_TESTCASES.forEach((testcase) => {
add_task(async function test_merge() {
do_print("Starting testcase: " + testcase.description);
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[testcase.addressInStorage]);
let addresses = profileStorage.addresses.getAll();
// Merge address and verify the guid in notifyObservers subject
let onMerged = TestUtils.topicObserved(
"formautofill-storage-changed",
(subject, data) =>
data == "merge" && subject.QueryInterface(Ci.nsISupportsString).data == addresses[0].guid
);
@@ -429,38 +430,39 @@ MERGE_TESTCASES.forEach((testcase) => {
addresses = profileStorage.addresses.getAll();
Assert.equal(addresses.length, 1);
Assert.notEqual(addresses[0].timeLastModified, timeLastModified);
do_check_record_matches(addresses[0], testcase.expectedAddress);
});
});
add_task(async function test_merge_same_address() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [TEST_ADDRESS_1]);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
+ [TEST_ADDRESS_1]);
let addresses = profileStorage.addresses.getAll();
let timeLastModified = addresses[0].timeLastModified;
// Merge same address will still return true but it won't update timeLastModified.
Assert.equal(profileStorage.addresses.mergeIfPossible(addresses[0].guid, TEST_ADDRESS_1), true);
Assert.equal(addresses[0].timeLastModified, timeLastModified);
});
add_task(async function test_merge_unable_merge() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
// Unable to merge because of conflict
do_check_eq(profileStorage.addresses.mergeIfPossible(addresses[1].guid, TEST_ADDRESS_3), false);
// Unable to merge because no overlap
do_check_eq(profileStorage.addresses.mergeIfPossible(addresses[1].guid, TEST_ADDRESS_4), false);
});
add_task(async function test_mergeToStorage() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
// Merge an address to storage
let anotherAddress = profileStorage.addresses._clone(TEST_ADDRESS_2);
profileStorage.addresses.add(anotherAddress);
anotherAddress.email = "timbl@w3.org";
do_check_eq(profileStorage.addresses.mergeToStorage(anotherAddress).length, 2);
do_check_eq(profileStorage.addresses.getAll()[1].email, anotherAddress.email);
do_check_eq(profileStorage.addresses.getAll()[2].email, anotherAddress.email);
--- a/browser/extensions/formautofill/test/unit/test_reconcile.js
+++ b/browser/extensions/formautofill/test/unit/test_reconcile.js
@@ -6,17 +6,17 @@ const TEST_STORE_FILE_NAME = "test-profi
// parent: What the local record looked like the last time we wrote the
// record to the Sync server.
// local: What the local record looks like now. IOW, the differences between
// 'parent' and 'local' are changes recently made which we wish to sync.
// remote: An incoming record we need to apply (ie, a record that was possibly
// changed on a remote device)
//
// To further help understanding this, a few of the testcases are annotated.
-const RECONCILE_TESTCASES = [
+const ADDRESS_RECONCILE_TESTCASES = [
{
description: "Local change",
parent: {
// So when we last wrote the record to the server, it had these values.
"guid": "2bbd2d8fbc6b",
"version": 1,
"given-name": "Mark",
"family-name": "Hammond",
@@ -459,30 +459,482 @@ const RECONCILE_TESTCASES = [
"family-name": "Hammond",
"timeCreated": 1234,
"timeLastUsed": 5678,
"timesUsed": 6,
},
},
];
+const CREDIT_CARD_RECONCILE_TESTCASES = [
+ {
+ description: "Local change",
+ parent: {
+ // So when we last wrote the record to the server, it had these values.
+ "guid": "2bbd2d8fbc6b",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ local: [{
+ // The current local record - by comparing against parent we can see that
+ // only the given-name has changed locally.
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ }],
+ remote: {
+ // This is the incoming record. It has the same values as "parent", so
+ // we can deduce the record hasn't actually been changed remotely so we
+ // can safely ignore the incoming record and write our local changes.
+ "guid": "2bbd2d8fbc6b",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ reconciled: {
+ "guid": "2bbd2d8fbc6b",
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ },
+ },
+ {
+ description: "Remote change",
+ parent: {
+ "guid": "e3680e9f890d",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ }],
+ remote: {
+ "guid": "e3680e9f890d",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ },
+ reconciled: {
+ "guid": "e3680e9f890d",
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ },
+ },
+
+ {
+ description: "New local field",
+ parent: {
+ "guid": "0cba738b1be0",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ }],
+ remote: {
+ "guid": "0cba738b1be0",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ reconciled: {
+ "guid": "0cba738b1be0",
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ },
+ {
+ description: "New remote field",
+ parent: {
+ "guid": "be3ef97f8285",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ }],
+ remote: {
+ "guid": "be3ef97f8285",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ reconciled: {
+ "guid": "be3ef97f8285",
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ },
+ {
+ description: "Deleted field locally",
+ parent: {
+ "guid": "9627322248ec",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ }],
+ remote: {
+ "guid": "9627322248ec",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ reconciled: {
+ "guid": "9627322248ec",
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ },
+ {
+ description: "Deleted field remotely",
+ parent: {
+ "guid": "7d7509f3eeb2",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ }],
+ remote: {
+ "guid": "7d7509f3eeb2",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ reconciled: {
+ "guid": "7d7509f3eeb2",
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ },
+ {
+ description: "Local and remote changes to unrelated fields",
+ parent: {
+ // The last time we wrote this to the server, "cc-exp-month" was 12.
+ "guid": "e087a06dfc57",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ local: [{
+ // The current local record - so locally we've changed "cc-number".
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ "cc-exp-month": 12,
+ }],
+ remote: {
+ // Remotely, we've changed "cc-exp-month" to 1.
+ "guid": "e087a06dfc57",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 1,
+ },
+ reconciled: {
+ "guid": "e087a06dfc57",
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ "cc-exp-month": 1,
+ },
+ },
+ {
+ description: "Multiple local changes",
+ parent: {
+ "guid": "340a078c596f",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ local: [{
+ "cc-name": "Skip",
+ "cc-number": "1111222233334444",
+ }, {
+ "cc-name": "Skip",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ }],
+ remote: {
+ "guid": "340a078c596f",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-year": 2000,
+ },
+ reconciled: {
+ "guid": "340a078c596f",
+ "cc-name": "Skip",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ "cc-exp-year": 2000,
+ },
+ },
+ {
+ // Local and remote diverged from the shared parent, but the values are the
+ // same, so we shouldn't fork.
+ description: "Same change to local and remote",
+ parent: {
+ "guid": "0b3a72a1bea2",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ }],
+ remote: {
+ "guid": "0b3a72a1bea2",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ },
+ reconciled: {
+ "guid": "0b3a72a1bea2",
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ },
+ },
+ {
+ description: "Conflicting changes to single field",
+ parent: {
+ // This is what we last wrote to the sync server.
+ "guid": "62068784d089",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ local: [{
+ // The current version of the local record - the cc-number has changed locally.
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111111111111111",
+ }],
+ remote: {
+ // An incoming record has a different cc-number than any of the above!
+ "guid": "62068784d089",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ },
+ forked: {
+ // So we've forked the local record to a new GUID (and the next sync is
+ // going to write this as a new record)
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111111111111111",
+ },
+ reconciled: {
+ // And we've updated the local version of the record to be the remote version.
+ guid: "62068784d089",
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ },
+ },
+ {
+ description: "Conflicting changes to multiple fields",
+ parent: {
+ "guid": "244dbb692e94",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111111111111111",
+ "cc-exp-month": 1,
+ }],
+ remote: {
+ "guid": "244dbb692e94",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ "cc-exp-month": 3,
+ },
+ forked: {
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111111111111111",
+ "cc-exp-month": 1,
+ },
+ reconciled: {
+ "guid": "244dbb692e94",
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ "cc-exp-month": 3,
+ },
+ },
+ {
+ description: "Field deleted locally, changed remotely",
+ parent: {
+ "guid": "6fc45e03d19a",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ }],
+ remote: {
+ "guid": "6fc45e03d19a",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 3,
+ },
+ forked: {
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ reconciled: {
+ "guid": "6fc45e03d19a",
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 3,
+ },
+ },
+ {
+ description: "Field changed locally, deleted remotely",
+ parent: {
+ "guid": "fff9fa27fa18",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 12,
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 3,
+ }],
+ remote: {
+ "guid": "fff9fa27fa18",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ forked: {
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "cc-exp-month": 3,
+ },
+ reconciled: {
+ "guid": "fff9fa27fa18",
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ },
+ },
+ {
+ // Created, last modified should be synced; last used and times used should
+ // be local. Remote created time older than local, remote modified time
+ // newer than local.
+ description: "Created, last modified time reconciliation without local changes",
+ parent: {
+ "guid": "5113f329c42f",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "timeCreated": 1234,
+ "timeLastModified": 5678,
+ "timeLastUsed": 5678,
+ "timesUsed": 6,
+ },
+ local: [],
+ remote: {
+ "guid": "5113f329c42f",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "timeCreated": 1200,
+ "timeLastModified": 5700,
+ "timeLastUsed": 5700,
+ "timesUsed": 3,
+ },
+ reconciled: {
+ "guid": "5113f329c42f",
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "timeCreated": 1200,
+ "timeLastModified": 5700,
+ "timeLastUsed": 5678,
+ "timesUsed": 6,
+ },
+ },
+ {
+ // Local changes, remote created time newer than local, remote modified time
+ // older than local.
+ description: "Created, last modified time reconciliation with local changes",
+ parent: {
+ "guid": "791e5608b80a",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "timeCreated": 1234,
+ "timeLastModified": 5678,
+ "timeLastUsed": 5678,
+ "timesUsed": 6,
+ },
+ local: [{
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ }],
+ remote: {
+ "guid": "791e5608b80a",
+ "version": 1,
+ "cc-name": "Mark Hammond",
+ "cc-number": "1111222233334444",
+ "timeCreated": 1300,
+ "timeLastModified": 5000,
+ "timeLastUsed": 5000,
+ "timesUsed": 3,
+ },
+ reconciled: {
+ "guid": "791e5608b80a",
+ "cc-name": "Mark Hammond",
+ "cc-number": "4444333322221111",
+ "timeCreated": 1234,
+ "timeLastUsed": 5678,
+ "timesUsed": 6,
+ },
+ },
+];
+
add_task(async function test_reconcile_unknown_version() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, "addresses");
// Cross-version reconciliation isn't supported yet. See bug 1377204.
await Assert.rejects(profileStorage.addresses.reconcile({
"guid": "31d83d2725ec",
"version": 2,
"given-name": "Mark",
"family-name": "Hammond",
}), /Got unknown record version/);
});
add_task(async function test_reconcile_idempotent() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, "addresses");
let guid = "de1ba7b094fe";
profileStorage.addresses.add({
guid,
version: 1,
"given-name": "Mark",
"family-name": "Hammond",
}, {sourceSync: true});
@@ -528,20 +980,20 @@ add_task(async function test_reconcile_i
"given-name": "Skip",
"family-name": "Hammond",
"organization": "Mozilla",
"tel": "123456",
}), "Second merge should not change record");
}
});
-add_task(async function test_reconcile_three_way_merge() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
+add_task(async function test_addresses_reconcile_three_way_merge() {
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, "addresses");
- for (let test of RECONCILE_TESTCASES) {
+ for (let test of ADDRESS_RECONCILE_TESTCASES) {
do_print(test.description);
profileStorage.addresses.add(test.parent, {sourceSync: true});
for (let updatedRecord of test.local) {
profileStorage.addresses.update(test.parent.guid, updatedRecord);
}
@@ -564,8 +1016,51 @@ add_task(async function test_reconcile_t
`${test.description} should fork record`);
} else {
ok(!test.forked, `${test.description} should not fork record`);
}
ok(objectMatches(reconciledRecord, test.reconciled));
}
});
+
+add_task(async function test_creditCards_reconcile_three_way_merge() {
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, "creditCards");
+
+ for (let test of CREDIT_CARD_RECONCILE_TESTCASES) {
+ do_print(test.description);
+
+ let clonedParent = Object.assign({}, test.parent);
+ await profileStorage.creditCards.encryptCCNumberFields(clonedParent);
+ profileStorage.creditCards.add(clonedParent, {sourceSync: true});
+
+ for (let updatedRecord of test.local) {
+ let clonedUpdatedRecord = Object.assign({}, updatedRecord);
+ await profileStorage.creditCards.encryptCCNumberFields(clonedUpdatedRecord);
+ profileStorage.creditCards.update(test.parent.guid, clonedUpdatedRecord);
+ }
+
+ let localRecord = profileStorage.creditCards.get(test.parent.guid, {
+ rawData: true,
+ });
+
+ let {forkedGUID} = await profileStorage.creditCards.reconcile(test.remote);
+ let reconciledRecord = profileStorage.creditCards.get(test.parent.guid, {
+ rawData: true,
+ });
+ await profileStorage.creditCards.decryptCCNumberFields(reconciledRecord);
+
+ if (forkedGUID) {
+ let forkedRecord = profileStorage.creditCards.get(forkedGUID, {
+ rawData: true,
+ });
+
+ notEqual(forkedRecord.guid, reconciledRecord.guid);
+ equal(forkedRecord.timeLastModified, localRecord.timeLastModified);
+ ok(objectMatches(forkedRecord, test.forked),
+ `${test.description} should fork record`);
+ } else {
+ ok(!test.forked, `${test.description} should not fork record`);
+ }
+
+ ok(objectMatches(reconciledRecord, test.reconciled));
+ }
+});
--- a/browser/extensions/formautofill/test/unit/test_storage_syncfields.js
+++ b/browser/extensions/formautofill/test/unit/test_storage_syncfields.js
@@ -1,65 +1,73 @@
/**
* Tests ProfileStorage objects support for sync related fields.
*/
"use strict";
// The duplication of some of these fixtures between tests is unfortunate.
const TEST_STORE_FILE_NAME = "test-profile.json";
+const TEST_COLLECTION_NAME = "addresses";
const TEST_ADDRESS_1 = {
"given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge",
"address-level1": "MA",
"postal-code": "02139",
country: "US",
- tel: "+1 617 253 5702",
+ tel: "+16172535702",
email: "timbl@w3.org",
};
const TEST_ADDRESS_2 = {
"street-address": "Some Address",
country: "US",
};
const TEST_ADDRESS_3 = {
"street-address": "Other Address",
"postal-code": "12345",
};
+const TEST_CREDIT_CARD_1 = {
+ "cc-name": "John Doe",
+ "cc-number": "1234567812345678",
+ "cc-exp-month": 4,
+ "cc-exp-year": 2017,
+};
+
// storage.get() doesn't support getting deleted items. However, this test
// wants to do that, so rather than making .get() support that just for this
// test, we use this helper.
function findGUID(storage, guid, options) {
let all = storage.getAll(options);
let records = all.filter(r => r.guid == guid);
equal(records.length, 1);
return records[0];
}
add_task(async function test_changeCounter() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1]);
let [address] = profileStorage.addresses.getAll();
// new records don't get the sync metadata.
equal(getSyncChangeCounter(profileStorage.addresses, address.guid), -1);
// But we can force one.
profileStorage.addresses.pullSyncChanges();
equal(getSyncChangeCounter(profileStorage.addresses, address.guid), 1);
});
add_task(async function test_pushChanges() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
profileStorage.addresses.pullSyncChanges(); // force sync metadata for all items
let [, address] = profileStorage.addresses.getAll();
let guid = address.guid;
let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
@@ -106,17 +114,17 @@ async function checkingSyncChange(action
let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
(subject, data) => data == action);
await callback();
let [subject] = await onChanged;
ok(subject.wrappedJSObject.sourceSync, "change notification should have source sync");
}
add_task(async function test_add_sourceSync() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME, []);
// Hardcode a guid so that we don't need to generate a dynamic regex
let guid = "aaaaaaaaaaaa";
let testAddr = Object.assign({guid, version: 1}, TEST_ADDRESS_1);
await checkingSyncChange("add", () =>
profileStorage.addresses.add(testAddr, {sourceSync: true}));
@@ -125,17 +133,17 @@ add_task(async function test_add_sourceS
Assert.throws(() =>
profileStorage.addresses.add({guid, deleted: true}, {sourceSync: true}),
/Record aaaaaaaaaaaa already exists/
);
});
add_task(async function test_add_tombstone_sourceSync() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME, []);
let guid = profileStorage.addresses._generateGUID();
let testAddr = {guid, deleted: true};
await checkingSyncChange("add", () =>
profileStorage.addresses.add(testAddr, {sourceSync: true}));
let added = findGUID(profileStorage.addresses, guid,
{includeDeleted: true});
@@ -149,17 +157,17 @@ add_task(async function test_add_tombsto
added = findGUID(profileStorage.addresses, guid,
{includeDeleted: true});
equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
ok(added.deleted);
});
add_task(async function test_add_resurrects_tombstones() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME, []);
let guid = profileStorage.addresses._generateGUID();
// Add a tombstone.
profileStorage.addresses.add({guid, deleted: true});
// You can't re-add an item with an explicit GUID.
let resurrected = Object.assign({}, TEST_ADDRESS_1, {guid, version: 1});
@@ -170,49 +178,50 @@ add_task(async function test_add_resurre
let guid3 = profileStorage.addresses.add(resurrected, {sourceSync: true});
equal(guid, guid3);
let got = profileStorage.addresses.get(guid);
equal(got["given-name"], TEST_ADDRESS_1["given-name"]);
});
add_task(async function test_remove_sourceSync_localChanges() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [TEST_ADDRESS_1]);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
+ [TEST_ADDRESS_1]);
profileStorage.addresses.pullSyncChanges(); // force sync metadata
let [{guid}] = profileStorage.addresses.getAll();
equal(getSyncChangeCounter(profileStorage.addresses, guid), 1);
// try and remove a record stored locally with local changes
await checkingSyncChange("remove", () =>
profileStorage.addresses.remove(guid, {sourceSync: true}));
let record = profileStorage.addresses.get(guid);
ok(record);
equal(getSyncChangeCounter(profileStorage.addresses, guid), 1);
});
add_task(async function test_remove_sourceSync_unknown() {
// remove a record not stored locally
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME, []);
let guid = profileStorage.addresses._generateGUID();
await checkingSyncChange("remove", () =>
profileStorage.addresses.remove(guid, {sourceSync: true}));
let tombstone = findGUID(profileStorage.addresses, guid, {
includeDeleted: true,
});
ok(tombstone.deleted);
equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
});
add_task(async function test_remove_sourceSync_unchanged() {
// Remove a local record without a change counter.
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME, []);
let guid = profileStorage.addresses._generateGUID();
let addr = Object.assign({guid, version: 1}, TEST_ADDRESS_1);
// add a record with sourceSync to guarantee changeCounter == 0
await checkingSyncChange("add", () =>
profileStorage.addresses.add(addr, {sourceSync: true}));
equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
@@ -223,17 +232,17 @@ add_task(async function test_remove_sour
let tombstone = findGUID(profileStorage.addresses, guid, {
includeDeleted: true,
});
ok(tombstone.deleted);
equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
});
add_task(async function test_pullSyncChanges() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let startAddresses = profileStorage.addresses.getAll();
equal(startAddresses.length, 2);
// All should start without sync metadata
for (let {guid} of profileStorage.addresses._store.data.addresses) {
let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
equal(changeCounter, -1);
@@ -286,17 +295,17 @@ add_task(async function test_pullSyncCha
change.profile.guid);
equal(change.counter, changeCounter);
ok(!change.synced);
}
});
add_task(async function test_pullPushChanges() {
// round-trip changes between pull and push
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME, []);
let psa = profileStorage.addresses;
let guid1 = psa.add(TEST_ADDRESS_1);
let guid2 = psa.add(TEST_ADDRESS_2);
let guid3 = psa.add(TEST_ADDRESS_3);
let changes = psa.pullSyncChanges();
@@ -318,17 +327,17 @@ add_task(async function test_pullPushCha
equal(getSyncChangeCounter(psa, guid1), 0);
// second was synced correctly, but it had a change while syncing.
equal(getSyncChangeCounter(psa, guid2), 1);
// 3rd wasn't marked as having synced.
equal(getSyncChangeCounter(psa, guid3), 1);
});
add_task(async function test_changeGUID() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME, []);
let newguid = () => profileStorage.addresses._generateGUID();
let guid_synced = profileStorage.addresses.add(TEST_ADDRESS_1);
// pullSyncChanges so guid_synced is flagged as syncing.
profileStorage.addresses.pullSyncChanges();
@@ -358,46 +367,59 @@ add_task(async function test_changeGUID(
ok(profileStorage.addresses.get(guid_synced), "synced item still exists.");
ok(profileStorage.addresses.get(guid_u2), "guid we didn't touch still exists.");
ok(profileStorage.addresses.get(targetguid), "target guid exists.");
ok(!profileStorage.addresses.get(guid_u1), "old guid no longer exists.");
});
add_task(async function test_findDuplicateGUID() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
- [TEST_ADDRESS_1]);
+ const TEST_RECORDS = {
+ "addresses": [TEST_ADDRESS_1],
+ "creditCards": [TEST_CREDIT_CARD_1],
+ };
- let [record] = profileStorage.addresses.getAll({rawData: true});
- await Assert.rejects(profileStorage.addresses.findDuplicateGUID(record),
- /Record \w+ already exists/,
- "Should throw if the GUID already exists");
+ for (let collectionName of Object.keys(TEST_RECORDS)) {
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, collectionName,
+ TEST_RECORDS[collectionName]);
+ let [record] = profileStorage[collectionName].getAll({rawData: true});
+ await Assert.rejects(profileStorage[collectionName].findDuplicateGUID(record),
+ /Record \w+ already exists/,
+ "Should throw if the GUID already exists");
- // Add a malformed record, passing `sourceSync` to work around the record
- // normalization logic that would prevent this.
- let timeLastModified = Date.now();
- let timeCreated = timeLastModified - 60 * 1000;
+ let targetRecord = Object.assign({}, TEST_RECORDS[collectionName][0], {
+ guid: profileStorage[collectionName]._generateGUID(),
+ version: record.version,
+ });
+ let duplicateGUID = await profileStorage[collectionName].findDuplicateGUID(targetRecord);
+ equal(duplicateGUID, record.guid);
- profileStorage.addresses.add({
- guid: profileStorage.addresses._generateGUID(),
- version: 1,
- timeCreated,
- timeLastModified,
- }, {sourceSync: true});
+ // Add a malformed record, passing `sourceSync` to work around the record
+ // normalization logic that would prevent this.
+ let timeLastModified = Date.now();
+ let timeCreated = timeLastModified - 60 * 1000;
- strictEqual(await profileStorage.addresses.findDuplicateGUID({
- guid: profileStorage.addresses._generateGUID(),
- version: 1,
- timeCreated,
- timeLastModified,
- }), null, "Should ignore internal fields and malformed records");
+ profileStorage[collectionName].add({
+ guid: profileStorage[collectionName]._generateGUID(),
+ version: 1,
+ timeCreated,
+ timeLastModified,
+ }, {sourceSync: true});
+
+ strictEqual(await profileStorage[collectionName].findDuplicateGUID({
+ guid: profileStorage[collectionName]._generateGUID(),
+ version: 1,
+ timeCreated,
+ timeLastModified,
+ }), null, "Should ignore internal fields and malformed records");
+ }
});
add_task(async function test_reset() {
- let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME,
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, TEST_COLLECTION_NAME,
[TEST_ADDRESS_1, TEST_ADDRESS_2]);
let addresses = profileStorage.addresses.getAll();
// All should start without sync metadata
for (let {guid} of addresses) {
let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
equal(changeCounter, -1);
}