summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/test/unit/test_storage_syncfields.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/extensions/formautofill/test/unit/test_storage_syncfields.js')
-rw-r--r--browser/extensions/formautofill/test/unit/test_storage_syncfields.js498
1 files changed, 498 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/unit/test_storage_syncfields.js b/browser/extensions/formautofill/test/unit/test_storage_syncfields.js
new file mode 100644
index 0000000000..e304aa4df0
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_storage_syncfields.js
@@ -0,0 +1,498 @@
+/**
+ * Tests FormAutofillStorage 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_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",
+ email: "timbl@w3.org",
+ "unknown-1": "an unknown field we roundtrip",
+};
+
+const TEST_ADDRESS_2 = {
+ "street-address": "Some Address",
+ country: "US",
+};
+
+const TEST_ADDRESS_3 = {
+ "street-address": "Other Address",
+ "postal-code": "12345",
+};
+
+// 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.
+async function findGUID(storage, guid, options) {
+ let all = await 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, [
+ TEST_ADDRESS_1,
+ ]);
+
+ let [address] = await 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, [
+ TEST_ADDRESS_1,
+ TEST_ADDRESS_2,
+ ]);
+
+ profileStorage.addresses.pullSyncChanges(); // force sync metadata for all items
+
+ let [, address] = await profileStorage.addresses.getAll();
+ let guid = address.guid;
+ let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+
+ // Pretend we're doing a sync now, and an update occured mid-sync.
+ let changes = {
+ [guid]: {
+ profile: address,
+ counter: changeCounter,
+ modified: address.timeLastModified,
+ synced: true,
+ },
+ };
+
+ let onChanged = TestUtils.topicObserved(
+ "formautofill-storage-changed",
+ (subject, data) => data == "update"
+ );
+ await profileStorage.addresses.update(guid, TEST_ADDRESS_3);
+ await onChanged;
+
+ changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+ Assert.equal(changeCounter, 2);
+
+ profileStorage.addresses.pushSyncChanges(changes);
+ address = await profileStorage.addresses.get(guid);
+ changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+
+ // Counter should still be 1, since our sync didn't record the mid-sync change
+ Assert.equal(
+ changeCounter,
+ 1,
+ "Counter shouldn't be zero because it didn't record update"
+ );
+
+ // now, push a new set of changes, which should make the changeCounter 0
+ profileStorage.addresses.pushSyncChanges({
+ [guid]: {
+ profile: address,
+ counter: changeCounter,
+ modified: address.timeLastModified,
+ synced: true,
+ },
+ });
+
+ changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+ Assert.equal(changeCounter, 0);
+});
+
+async function checkingSyncChange(action, callback) {
+ 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, []);
+
+ // 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", async () =>
+ profileStorage.addresses.add(testAddr, { sourceSync: true })
+ );
+
+ let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+ equal(changeCounter, 0);
+
+ await Assert.rejects(
+ 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 guid = profileStorage.addresses._generateGUID();
+ let testAddr = { guid, deleted: true };
+ await checkingSyncChange("add", async () =>
+ profileStorage.addresses.add(testAddr, { sourceSync: true })
+ );
+
+ let added = await findGUID(profileStorage.addresses, guid, {
+ includeDeleted: true,
+ });
+ ok(added);
+ equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
+ ok(added.deleted);
+
+ // Adding same record again shouldn't throw (or change anything)
+ await checkingSyncChange("add", async () =>
+ profileStorage.addresses.add(testAddr, { sourceSync: true })
+ );
+
+ added = await 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 guid = profileStorage.addresses._generateGUID();
+
+ // Add a tombstone.
+ await 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 });
+ await Assert.rejects(
+ profileStorage.addresses.add(resurrected),
+ /"(guid|version)" is not a valid field/
+ );
+
+ // But Sync can!
+ let guid3 = await profileStorage.addresses.add(resurrected, {
+ sourceSync: true,
+ });
+ equal(guid, guid3);
+
+ let got = await 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,
+ ]);
+ profileStorage.addresses.pullSyncChanges(); // force sync metadata
+
+ let [{ guid }] = await profileStorage.addresses.getAll();
+
+ equal(getSyncChangeCounter(profileStorage.addresses, guid), 1);
+ // try and remove a record stored locally with local changes
+ await checkingSyncChange("remove", async () =>
+ profileStorage.addresses.remove(guid, { sourceSync: true })
+ );
+
+ let record = await 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 guid = profileStorage.addresses._generateGUID();
+ await checkingSyncChange("remove", async () =>
+ profileStorage.addresses.remove(guid, { sourceSync: true })
+ );
+
+ let tombstone = await 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 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", async () =>
+ profileStorage.addresses.add(addr, { sourceSync: true })
+ );
+
+ equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
+
+ await checkingSyncChange("remove", async () =>
+ profileStorage.addresses.remove(guid, { sourceSync: true })
+ );
+
+ let tombstone = await 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, [
+ TEST_ADDRESS_1,
+ TEST_ADDRESS_2,
+ ]);
+
+ let startAddresses = await 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);
+ }
+ profileStorage.addresses.pullSyncChanges(); // force sync metadata
+
+ let addedDirectGUID = profileStorage.addresses._generateGUID();
+ let testAddr = Object.assign(
+ { guid: addedDirectGUID, version: 1 },
+ TEST_ADDRESS_1,
+ TEST_ADDRESS_3
+ );
+
+ await checkingSyncChange("add", async () =>
+ profileStorage.addresses.add(testAddr, { sourceSync: true })
+ );
+
+ let tombstoneGUID = profileStorage.addresses._generateGUID();
+ await checkingSyncChange("add", async () =>
+ profileStorage.addresses.add(
+ { guid: tombstoneGUID, deleted: true },
+ { sourceSync: true }
+ )
+ );
+
+ let onChanged = TestUtils.topicObserved(
+ "formautofill-storage-changed",
+ (subject, data) => data == "remove"
+ );
+
+ profileStorage.addresses.remove(startAddresses[0].guid);
+ await onChanged;
+
+ let addresses = await profileStorage.addresses.getAll({
+ includeDeleted: true,
+ });
+
+ // Should contain changes with a change counter
+ let changes = profileStorage.addresses.pullSyncChanges();
+ equal(Object.keys(changes).length, 2);
+
+ ok(changes[startAddresses[0].guid].profile.deleted);
+ equal(changes[startAddresses[0].guid].counter, 2);
+
+ ok(!changes[startAddresses[1].guid].profile.deleted);
+ equal(changes[startAddresses[1].guid].counter, 1);
+
+ ok(
+ !changes[tombstoneGUID],
+ "Missing because it's a tombstone from sourceSync"
+ );
+ ok(!changes[addedDirectGUID], "Missing because it was added with sourceSync");
+
+ for (let address of addresses) {
+ let change = changes[address.guid];
+ if (!change) {
+ continue;
+ }
+ equal(change.profile.guid, address.guid);
+ let changeCounter = getSyncChangeCounter(
+ profileStorage.addresses,
+ 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 psa = profileStorage.addresses;
+
+ let guid1 = await psa.add(TEST_ADDRESS_1);
+ let guid2 = await psa.add(TEST_ADDRESS_2);
+ let guid3 = await psa.add(TEST_ADDRESS_3);
+
+ let changes = psa.pullSyncChanges();
+
+ equal(getSyncChangeCounter(psa, guid1), 1);
+ equal(getSyncChangeCounter(psa, guid2), 1);
+ equal(getSyncChangeCounter(psa, guid3), 1);
+
+ // between the pull and the push we change the second.
+ await psa.update(guid2, Object.assign({}, TEST_ADDRESS_2, { country: "AU" }));
+ equal(getSyncChangeCounter(psa, guid2), 2);
+ // and update the changeset to indicated we did update the first 2, but failed
+ // to update the 3rd for some reason.
+ changes[guid1].synced = true;
+ changes[guid2].synced = true;
+
+ psa.pushSyncChanges(changes);
+
+ // first was synced correctly.
+ 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 newguid = () => profileStorage.addresses._generateGUID();
+
+ let guid_synced = await profileStorage.addresses.add(TEST_ADDRESS_1);
+
+ // pullSyncChanges so guid_synced is flagged as syncing.
+ profileStorage.addresses.pullSyncChanges();
+
+ // and 2 items that haven't been synced.
+ let guid_u1 = await profileStorage.addresses.add(TEST_ADDRESS_2);
+ let guid_u2 = await profileStorage.addresses.add(TEST_ADDRESS_3);
+
+ // Change a non-existing guid
+ Assert.throws(
+ () => profileStorage.addresses.changeGUID(newguid(), newguid()),
+ /changeGUID: no source record/
+ );
+ // Change to a guid that already exists.
+ Assert.throws(
+ () => profileStorage.addresses.changeGUID(guid_u1, guid_u2),
+ /changeGUID: record with destination id exists already/
+ );
+ // Try and change a guid that's already been synced.
+ Assert.throws(
+ () => profileStorage.addresses.changeGUID(guid_synced, newguid()),
+ /changeGUID: existing record has already been synced/
+ );
+
+ // Change an item to itself makes no sense.
+ Assert.throws(
+ () => profileStorage.addresses.changeGUID(guid_u1, guid_u1),
+ /changeGUID: old and new IDs are the same/
+ );
+
+ // and one that works.
+ equal(
+ (await profileStorage.addresses.getAll({ includeDeleted: true })).length,
+ 3
+ );
+ let targetguid = newguid();
+ profileStorage.addresses.changeGUID(guid_u1, targetguid);
+ equal(
+ (await profileStorage.addresses.getAll({ includeDeleted: true })).length,
+ 3
+ );
+
+ ok(
+ await profileStorage.addresses.get(guid_synced),
+ "synced item still exists."
+ );
+ ok(
+ await profileStorage.addresses.get(guid_u2),
+ "guid we didn't touch still exists."
+ );
+ ok(await profileStorage.addresses.get(targetguid), "target guid exists.");
+ ok(
+ !(await 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,
+ ]);
+
+ let [record] = await profileStorage.addresses.getAll({ rawData: true });
+ await Assert.rejects(
+ profileStorage.addresses.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;
+
+ await profileStorage.addresses.add(
+ {
+ guid: profileStorage.addresses._generateGUID(),
+ version: 1,
+ timeCreated,
+ timeLastModified,
+ },
+ { sourceSync: true }
+ );
+
+ strictEqual(
+ await profileStorage.addresses.findDuplicateGUID({
+ guid: profileStorage.addresses._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, [
+ TEST_ADDRESS_1,
+ TEST_ADDRESS_2,
+ ]);
+
+ let addresses = await profileStorage.addresses.getAll();
+ // All should start without sync metadata
+ for (let { guid } of addresses) {
+ let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+ equal(changeCounter, -1);
+ }
+ // pullSyncChanges should create the metadata.
+ profileStorage.addresses.pullSyncChanges();
+ addresses = await profileStorage.addresses.getAll();
+ for (let { guid } of addresses) {
+ let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+ equal(changeCounter, 1);
+ }
+ // and resetSync should wipe it.
+ profileStorage.addresses.resetSync();
+ addresses = await profileStorage.addresses.getAll();
+ for (let { guid } of addresses) {
+ let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
+ equal(changeCounter, -1);
+ }
+});