summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/test/unit/test_sync_deprecate_address_x_name_fields.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /browser/extensions/formautofill/test/unit/test_sync_deprecate_address_x_name_fields.js
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/extensions/formautofill/test/unit/test_sync_deprecate_address_x_name_fields.js')
-rw-r--r--browser/extensions/formautofill/test/unit/test_sync_deprecate_address_x_name_fields.js1100
1 files changed, 1100 insertions, 0 deletions
diff --git a/browser/extensions/formautofill/test/unit/test_sync_deprecate_address_x_name_fields.js b/browser/extensions/formautofill/test/unit/test_sync_deprecate_address_x_name_fields.js
new file mode 100644
index 0000000000..f30a708709
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_sync_deprecate_address_x_name_fields.js
@@ -0,0 +1,1100 @@
+/**
+ * Tests sync functionality.
+ */
+
+/* import-globals-from ../../../../../services/sync/tests/unit/head_appinfo.js */
+/* import-globals-from ../../../../../services/common/tests/unit/head_helpers.js */
+/* import-globals-from ../../../../../services/sync/tests/unit/head_helpers.js */
+/* import-globals-from ../../../../../services/sync/tests/unit/head_http_server.js */
+
+"use strict";
+
+const { Service } = ChromeUtils.importESModule(
+ "resource://services-sync/service.sys.mjs"
+);
+
+const { AddressesEngine } = ChromeUtils.importESModule(
+ "resource://autofill/FormAutofillSync.sys.mjs"
+);
+
+Services.prefs.setCharPref("extensions.formautofill.loglevel", "Debug");
+initTestLogging("Trace");
+
+const TEST_STORE_FILE_NAME = "test-profile.json";
+
+function expectProfiles(profiles, expected) {
+ expected.sort((a, b) => a.guid.localeCompare(b.guid));
+ profiles.sort((a, b) => a.guid.localeCompare(b.guid));
+ try {
+ deepEqual(
+ profiles.map(p => p.guid),
+ expected.map(p => p.guid)
+ );
+ for (let i = 0; i < expected.length; i++) {
+ let thisExpected = expected[i];
+ let thisGot = profiles[i];
+ // always check "deleted".
+ equal(thisExpected.deleted, thisGot.deleted);
+ ok(objectMatches(thisGot, thisExpected));
+ }
+ } catch (ex) {
+ info("Comparing expected profiles:");
+ info(JSON.stringify(expected, undefined, 2));
+ info("against actual profiles:");
+ info(JSON.stringify(profiles, undefined, 2));
+ throw ex;
+ }
+}
+
+async function expectServerProfiles(collection, expected) {
+ const profiles = collection
+ .payloads()
+ .map(payload => Object.assign({ guid: payload.id }, payload.entry));
+ expectProfiles(profiles, expected);
+}
+
+async function expectLocalProfiles(profileStorage, expected) {
+ const profiles = await profileStorage.addresses.getAll({
+ rawData: true,
+ includeDeleted: true,
+ });
+ expectProfiles(profiles, expected);
+}
+
+async function setup() {
+ let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME);
+ // should always start with no profiles.
+ Assert.equal(
+ (await profileStorage.addresses.getAll({ includeDeleted: true })).length,
+ 0
+ );
+
+ Services.prefs.setCharPref(
+ "services.sync.log.logger.engine.addresses",
+ "Trace"
+ );
+ let engine = new AddressesEngine(Service);
+ await engine.initialize();
+ // Avoid accidental automatic sync due to our own changes
+ Service.scheduler.syncThreshold = 10000000;
+ let syncID = await engine.resetLocalSyncID();
+ let server = serverForUsers(
+ { foo: "password" },
+ {
+ meta: {
+ global: {
+ engines: { addresses: { version: engine.version, syncID } },
+ },
+ },
+ addresses: {},
+ }
+ );
+
+ Service.engineManager._engines.addresses = engine;
+ engine.enabled = true;
+ engine._store._storage = profileStorage.addresses;
+
+ generateNewKeys(Service.collectionKeys);
+
+ await SyncTestingInfrastructure(server);
+
+ let collection = server.user("foo").collection("addresses");
+
+ return { profileStorage, server, collection, engine };
+}
+
+async function cleanup(server) {
+ let promiseStartOver = promiseOneObserver("weave:service:start-over:finish");
+ await Service.startOver();
+ await promiseStartOver;
+ await promiseStopServer(server);
+}
+
+function setupServerRecords(server, records) {
+ for (const record of records) {
+ server.insertWBO(
+ "foo",
+ "addresses",
+ new ServerWBO(
+ record.guid,
+ encryptPayload({
+ id: record.guid,
+ entry: Object.assign({}, record),
+ }),
+ getDateForSync()
+ )
+ );
+ }
+}
+
+function assertNumberRecordUploadedBySync(engine, expectedNumber) {
+ const uploadOutgoing = engine._uploadOutgoing;
+ engine._uploadOutgoing = async function () {
+ engine._uploadOutgoing = uploadOutgoing;
+ try {
+ await uploadOutgoing.call(this);
+ } finally {
+ Assert.equal(this._modified.ids().length, expectedNumber);
+ }
+ };
+}
+
+/**
+ * The following tests test uploading. Ensure `given-name`, `additional-name`, and `family-name` fields are included
+ * in the sync payload.
+ */
+
+add_task(async function test_local_upload() {
+ const { collection, profileStorage, server, engine } = await setup();
+
+ try {
+ const localGuid = await profileStorage.addresses.add({
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ });
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectServerProfiles(collection, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: "Mr John William Doe",
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "Some Address",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+add_task(async function test_local_upload_no_addtional_name() {
+ const { collection, profileStorage, server, engine } = await setup();
+
+ try {
+ const localGuid = await profileStorage.addresses.add({
+ name: "Timothy Berners-Lee",
+ "street-address": "Some Address",
+ });
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectServerProfiles(collection, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: "Timothy Berners-Lee",
+ "given-name": "Timothy",
+ "additional-name": undefined,
+ "family-name": "Berners-Lee",
+ "street-address": "Some Address",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+add_task(async function test_local_upload_no_name() {
+ const { collection, profileStorage, server, engine } = await setup();
+
+ try {
+ const localGuid = await profileStorage.addresses.add({
+ "street-address": "Some Address",
+ });
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectServerProfiles(collection, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: undefined,
+ "given-name": undefined,
+ "additional-name": undefined,
+ "family-name": undefined,
+ "street-address": "Some Address",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+/**
+ * The following tasks test cases where no matching local record is found while applying an
+ * incoming record. We test:
+ * 1. An incoming record with `name` field and `*-name` fields, indicating this is a new record.
+ * 2. An incoming record with only `*-name` fields, indicating this is an old record.
+ * 3. An incoming record without any name fields, leaving it unknown whether this record comes
+ * from a new or an old device.
+ *
+ * The expected result for each task is:
+ * 1. The `name` field should NOT be rebuilt based on the deprecated `*-name` fields.
+ * 2. The `name` field should be rebuilt based on the deprecated `*-name` fields.
+ * 3. All the name related fields should remain empty.
+ */
+
+// 1. Remote record is a new record
+add_task(async function test_apply_incoming() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ setupServerRecords(server, [
+ {
+ guid: "86d961c7717a",
+ version: 1,
+ name: "Mr. John William Doe",
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: "86d961c7717a",
+ version: 1,
+ name: "Mr. John William Doe", // Prefix `Mr.` remains
+ organization: "Mozilla",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 2. Remote record is an old record
+add_task(async function test_apply_incoming_legacy() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ setupServerRecords(server, [
+ {
+ guid: "86d961c7717a",
+ version: 1,
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ organization: "Mozilla",
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: "86d961c7717a",
+ version: 1,
+ name: "John William Doe", // rebuild name field based on `*-name` fields
+ organization: "Mozilla",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 3. Remote record does not have `name`
+add_task(async function test_apply_incoming_without_name() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ setupServerRecords(server, [
+ {
+ guid: "86d961c7717a",
+ version: 1,
+ "street-address": "Some Address",
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: "86d961c7717a",
+ version: 1,
+ "street-address": "Some Address",
+ name: undefined,
+ "given-name": undefined,
+ "additional-name": undefined,
+ "family-name": undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+/**
+ * The following tasks test cases where a matching local record is found by matching GUID while applying an
+ * incoming record. We test:
+ * 1. An incoming record with `name` field and `*-name` fields, indicating this is a new record.
+ * 2. An incoming record with only `*-name` fields, indicating this is an old record, `*-name` is not updated
+ * 3. An incoming record with only `*-name` fields, indicating this is an old record, `*-name` is updated
+ * 4. An incoming record with only `*-name` fields, indicating this is an old record. Local record does not have `name`.
+ * 5. An incoming record without any name fields, leaving it unknown whether this record comes
+ * from a new or an old device.
+ *
+ * The expected result for each task is:
+ * 1. The `name` field of the local record should be replaced by the remote record.
+ * 2. The `name` field of the local record should NOT be replaced by the remote record, `name` remains the same.
+ * 3. The `name` field of the local record should be replaced by the remote record.
+ * 4. The `name` field of the local record should be replaced by the remote record.
+ * 5. The `name` field of the local record should be replaced by the remote record.
+ */
+
+// 1. Remote record is a new record
+add_task(async function test_apply_incoming_repalce() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ // Setup the local record and sync the record to sync
+ const localGuid = await profileStorage.addresses.add({
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ });
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // Remote record is updated by a new device
+ setupServerRecords(server, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: "Dr Timothy Berners Lee",
+ "given-name": "Timothy",
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "32 Vassar Street", // updated
+ organization: "Mozilla", // added
+ email: undefined, // removed
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: "Dr Timothy Berners Lee", // Should be replaced!
+ "street-address": "32 Vassar Street",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 2. Remote record is an old record, `*-name` is not updated
+add_task(async function test_apply_incoming_legacy_replace_name_is_updated() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ // Setup the local record and sync the record to sync
+ const localGuid = await profileStorage.addresses.add({
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ });
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // Remote record is updated by an old device
+ setupServerRecords(server, [
+ {
+ guid: localGuid,
+ version: 1,
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "32 Vassar Street", // updated
+ organization: "Mozilla", // added
+ email: undefined, // removed
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: "Mr John William Doe", // Shoult not be replaced!
+ "street-address": "32 Vassar Street",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 3. Remote record is an old record, `*-name` is updated
+add_task(
+ async function test_apply_incoming_legacy_replace_name_is_not_updated() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ // Setup the local record and sync the record to sync
+ const localGuid = await profileStorage.addresses.add({
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ });
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // Remote record is updated by an old device
+ setupServerRecords(server, [
+ {
+ guid: localGuid,
+ version: 1,
+ "given-name": "Timothy",
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "32 Vassar Street", // updated
+ organization: "Mozilla", // added
+ email: undefined, // removed
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: "Timothy Berners Lee", // Shoult be replaced!
+ "street-address": "32 Vassar Street",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+ }
+);
+
+// 4. Remote record is an old record. Local record does not have `name`
+add_task(
+ async function test_apply_incoming_legacy_repalce_local_without_name() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ // Setup the local record and sync the record to sync
+ const localGuid = await profileStorage.addresses.add({
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ });
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // Remote record is updated by an old device
+ setupServerRecords(server, [
+ {
+ guid: localGuid,
+ version: 1,
+ "given-name": "Timothy",
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "32 Vassar Street", // updated
+ organization: "Mozilla", // added
+ email: undefined, // removed
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: "Timothy Berners Lee", // Shoult be replaced!
+ "street-address": "32 Vassar Street",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+ }
+);
+
+// 5. Remote record doesn't have `name`
+add_task(async function test_apply_incoming_without_name_replace() {
+ const { profileStorage, server, engine } = await setup();
+
+ try {
+ // Setup the local record and sync the record to sync
+ const localGuid = await profileStorage.addresses.add({
+ name: "Mr. John William Doe",
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ });
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // Remote record is updated by a device
+ setupServerRecords(server, [
+ {
+ guid: localGuid,
+ version: 1,
+ "street-address": "32 Vassar Street", // updated
+ organization: "Mozilla", // added
+ email: undefined, // removed
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 0);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // `name` field should be removed
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: localGuid,
+ version: 1,
+ name: undefined, // Should be replaced!
+ "street-address": "32 Vassar Street",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+/**
+ * The following tasks test cases where a matching local record is found by matching GUID while applying an
+ * incoming record. And both the local and remote records are modified.
+ *
+ * We test:
+ * 1. An incoming record with `name` field and `*-name` fields, indicating this is a new record.
+ * 2. An incoming record with only `*-name` fields, indicating this is an old record, `*-name` is not updated
+ * 3. An incoming record with only `*-name` fields, indicating this is an old record, `*-name` is updated
+ * 4. An incoming record with only `*-name` fields, indicating this is an old record. Local record does not have `name`.
+ * 5. An incoming record without any name fields, leaving it unknown whether this record comes
+ * from a new or an old device.
+ *
+ * The expected result for each task is:
+ * 1. The `name` field of the local record should be replaced by the remote record
+ * 2. The `name` field of the local record should NOT be replaced by the remote record, `name` remains the same.
+ * 3. The `name` field of the local record should be replaced by the remote record.
+ * 4. The `name` field of the local record should be replaced by the remote record.
+ * 5. The `name` field of the local record should be replaced by the remote record
+ */
+
+// 1. Remote record is a new record
+add_task(async function test_apply_incoming_merge() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ };
+
+ try {
+ const guid = await profileStorage.addresses.add(LOCAL_ENTRY);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // local modifies "street-address"
+ let localCopy = Object.assign({}, LOCAL_ENTRY);
+ localCopy["street-address"] = "I moved!";
+ await profileStorage.addresses.update(guid, localCopy);
+
+ setupServerRecords(server, [
+ {
+ guid,
+ version: 1,
+ name: "Dr Timothy Berners Lee", // `name` is modified
+ "given-name": "Timothy",
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "Some Address",
+ organization: "Mozilla", // `organization` is added
+ email: undefined, // `email` is removed
+ },
+ ]);
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid,
+ version: 1,
+ name: "Dr Timothy Berners Lee", // Name should be replaced!
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid,
+ version: 1,
+ name: "Dr Timothy Berners Lee", // Name should be replaced!
+ "given-name": "Timothy",
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 2. Remote record is an old record, `*-name` is not updated
+add_task(async function test_apply_incoming_legacy_merge_name_is_not_updated() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ };
+
+ try {
+ const guid = await profileStorage.addresses.add(LOCAL_ENTRY);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // local modifies "street-address"
+ let localCopy = Object.assign({}, LOCAL_ENTRY);
+ localCopy["street-address"] = "I moved!";
+ await profileStorage.addresses.update(guid, localCopy);
+
+ setupServerRecords(server, [
+ {
+ guid,
+ version: 1,
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "Some Address",
+ organization: "Mozilla", // `organization` is added
+ email: undefined, // `email` is removed
+ },
+ ]);
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid,
+ version: 1,
+ name: "Mr John William Doe", // `name` should NOT be replaced!
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid,
+ version: 1,
+ name: "Mr John William Doe",
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 3. Remote record is an old record, `*-name` is updated
+add_task(async function test_apply_incoming_legacy_merge_name_is_updated() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ };
+
+ try {
+ const guid = await profileStorage.addresses.add(LOCAL_ENTRY);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // local modifies "street-address"
+ let localCopy = Object.assign({}, LOCAL_ENTRY);
+ localCopy["street-address"] = "I moved!";
+ await profileStorage.addresses.update(guid, localCopy);
+
+ setupServerRecords(server, [
+ {
+ guid,
+ version: 1,
+ "given-name": "Timothy", // `name` is modified
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "Some Address",
+ organization: "Mozilla", // `organization` is added
+ email: undefined, // `email` is removed
+ },
+ ]);
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid,
+ version: 1,
+ name: "Timothy Berners Lee", // `name` should be replaced!
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid,
+ version: 1,
+ name: "Timothy Berners Lee",
+ "given-name": "Timothy",
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 4. Remote record is an old record. Local record does not have `name`
+add_task(async function test_apply_incoming_legacy_merge_local_without_name() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ };
+
+ try {
+ const guid = await profileStorage.addresses.add(LOCAL_ENTRY);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // local modifies "street-address"
+ let localCopy = Object.assign({}, LOCAL_ENTRY);
+ localCopy["street-address"] = "I moved!";
+ await profileStorage.addresses.update(guid, localCopy);
+
+ setupServerRecords(server, [
+ {
+ guid,
+ version: 1,
+ "given-name": "Timothy", // `name` is modified
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "Some Address",
+ organization: "Mozilla", // `organization` is added
+ email: undefined, // `email` is removed
+ },
+ ]);
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid,
+ version: 1,
+ name: "Timothy Berners Lee", // `name` should be replaced!
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid,
+ version: 1,
+ name: "Timothy Berners Lee",
+ "given-name": "Timothy",
+ "additional-name": "Berners",
+ "family-name": "Lee",
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 5. Remote record does not have `name`
+add_task(async function test_apply_incoming_without_name_merge() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ "street-address": "Some Address",
+ email: "john.doe@mozilla.org",
+ };
+
+ try {
+ const guid = await profileStorage.addresses.add(LOCAL_ENTRY);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ // local modifies "street-address"
+ let localCopy = Object.assign({}, LOCAL_ENTRY);
+ localCopy["street-address"] = "I moved!";
+ await profileStorage.addresses.update(guid, localCopy);
+
+ setupServerRecords(server, [
+ {
+ guid,
+ version: 1,
+ "street-address": "Some Address",
+ organization: "Mozilla",
+ },
+ ]);
+
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid,
+ version: 1,
+ "street-address": "I moved!",
+ organization: "Mozilla", // `organization` is added
+ email: undefined, // `email` is removed
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid,
+ version: 1,
+ "street-address": "I moved!",
+ organization: "Mozilla",
+ email: undefined,
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+/**
+ * The following tasks test cases where a matching local record is found by running dedupe algorithm while applying an
+ * incoming record. We test:
+ * 1. An incoming record with `name` field and `*-name` fields, indicating this is a new record.
+ * 2. An incoming record with only `*-name` fields, indicating this is an old record.
+ * 3. An incoming record without any name fields, leaving it unknown whether this record comes
+ * from a new or an old device.
+ *
+ * The expected result is still one record in the local after merging the incoming record
+ */
+
+// 1. Remote record is a new record
+add_task(async function test_apply_incoming_dedupe() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ country: "US",
+ };
+
+ try {
+ const localGuid = await profileStorage.addresses.add(LOCAL_ENTRY);
+
+ const remoteGuid = Utils.makeGUID();
+ notEqual(localGuid, remoteGuid);
+
+ setupServerRecords(server, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ name: "Mr John William Doe",
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+
+ // Local duplicated record has not been synced before, so will trigger
+ // sync upload after merging.
+ assertNumberRecordUploadedBySync(engine, 1);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ name: "Mr John William Doe",
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ name: "Mr John William Doe",
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 2. Remote record is an old record
+add_task(async function test_apply_incoming_legacy_dedupe() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ name: "John William Doe",
+ "street-address": "Some Address",
+ country: "US",
+ };
+
+ try {
+ const localGuid = await profileStorage.addresses.add(LOCAL_ENTRY);
+
+ const remoteGuid = Utils.makeGUID();
+ notEqual(localGuid, remoteGuid);
+
+ setupServerRecords(server, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+
+ // Local duplicated record has not been synced before, so will trigger
+ // sync upload after merging.
+ assertNumberRecordUploadedBySync(engine, 1);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ name: "John William Doe",
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ name: "John William Doe",
+ "given-name": "John",
+ "additional-name": "William",
+ "family-name": "Doe",
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});
+
+// 3. Remote record does not have `name`
+add_task(async function test_apply_incoming_without_name_dedupe() {
+ const { collection, profileStorage, server, engine } = await setup();
+ const LOCAL_ENTRY = {
+ "street-address": "Some Address",
+ country: "US",
+ };
+
+ try {
+ const localGuid = await profileStorage.addresses.add(LOCAL_ENTRY);
+
+ const remoteGuid = Utils.makeGUID();
+ notEqual(localGuid, remoteGuid);
+
+ setupServerRecords(server, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+
+ assertNumberRecordUploadedBySync(engine, 1);
+ await engine.setLastSync(0);
+ await engine.sync();
+
+ await expectLocalProfiles(profileStorage, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+ await expectServerProfiles(collection, [
+ {
+ guid: remoteGuid,
+ version: 1,
+ "street-address": "Some Address",
+ country: "US",
+ },
+ ]);
+ } finally {
+ await cleanup(server);
+ }
+});