tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_storage_syncfields.js (15627B)


      1 /**
      2 * Tests FormAutofillStorage objects support for sync related fields.
      3 */
      4 
      5 "use strict";
      6 
      7 // The duplication of some of these fixtures between tests is unfortunate.
      8 const TEST_STORE_FILE_NAME = "test-profile.json";
      9 
     10 const TEST_ADDRESS_1 = {
     11  name: "Timothy John Berners-Lee",
     12  organization: "World Wide Web Consortium",
     13  "street-address": "32 Vassar Street\nMIT Room 32-G524",
     14  "address-level2": "Cambridge",
     15  "address-level1": "MA",
     16  "postal-code": "02139",
     17  country: "US",
     18  tel: "+1 617 253 5702",
     19  email: "timbl@w3.org",
     20  "unknown-1": "an unknown field we roundtrip",
     21 };
     22 
     23 const TEST_ADDRESS_2 = {
     24  "street-address": "Some Address",
     25  country: "US",
     26 };
     27 
     28 const TEST_ADDRESS_3 = {
     29  "street-address": "Other Address",
     30  "postal-code": "12345",
     31 };
     32 
     33 // storage.get() doesn't support getting deleted items. However, this test
     34 // wants to do that, so rather than making .get() support that just for this
     35 // test, we use this helper.
     36 async function findGUID(storage, guid, options) {
     37  let all = await storage.getAll(options);
     38  let records = all.filter(r => r.guid == guid);
     39  equal(records.length, 1);
     40  return records[0];
     41 }
     42 
     43 add_task(async function test_changeCounter() {
     44  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [
     45    TEST_ADDRESS_1,
     46  ]);
     47 
     48  let [address] = await profileStorage.addresses.getAll();
     49  // new records don't get the sync metadata.
     50  equal(getSyncChangeCounter(profileStorage.addresses, address.guid), -1);
     51  // But we can force one.
     52  profileStorage.addresses.pullSyncChanges();
     53  equal(getSyncChangeCounter(profileStorage.addresses, address.guid), 1);
     54 });
     55 
     56 add_task(async function test_pushChanges() {
     57  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [
     58    TEST_ADDRESS_1,
     59    TEST_ADDRESS_2,
     60  ]);
     61 
     62  profileStorage.addresses.pullSyncChanges(); // force sync metadata for all items
     63 
     64  let [, address] = await profileStorage.addresses.getAll();
     65  let guid = address.guid;
     66  let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
     67 
     68  // Pretend we're doing a sync now, and an update occured mid-sync.
     69  let changes = {
     70    [guid]: {
     71      profile: address,
     72      counter: changeCounter,
     73      modified: address.timeLastModified,
     74      synced: true,
     75    },
     76  };
     77 
     78  let onChanged = TestUtils.topicObserved(
     79    "formautofill-storage-changed",
     80    (subject, data) => data == "update"
     81  );
     82  await profileStorage.addresses.update(guid, TEST_ADDRESS_3);
     83  await onChanged;
     84 
     85  changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
     86  Assert.equal(changeCounter, 2);
     87 
     88  profileStorage.addresses.pushSyncChanges(changes);
     89  address = await profileStorage.addresses.get(guid);
     90  changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
     91 
     92  // Counter should still be 1, since our sync didn't record the mid-sync change
     93  Assert.equal(
     94    changeCounter,
     95    1,
     96    "Counter shouldn't be zero because it didn't record update"
     97  );
     98 
     99  // now, push a new set of changes, which should make the changeCounter 0
    100  profileStorage.addresses.pushSyncChanges({
    101    [guid]: {
    102      profile: address,
    103      counter: changeCounter,
    104      modified: address.timeLastModified,
    105      synced: true,
    106    },
    107  });
    108 
    109  changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
    110  Assert.equal(changeCounter, 0);
    111 });
    112 
    113 async function checkingSyncChange(action, callback) {
    114  let onChanged = TestUtils.topicObserved(
    115    "formautofill-storage-changed",
    116    (subject, data) => data == action
    117  );
    118  await callback();
    119  let [subject] = await onChanged;
    120  ok(
    121    subject.wrappedJSObject.sourceSync,
    122    "change notification should have source sync"
    123  );
    124 }
    125 
    126 add_task(async function test_add_sourceSync() {
    127  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
    128 
    129  // Hardcode a guid so that we don't need to generate a dynamic regex
    130  let guid = "aaaaaaaaaaaa";
    131  let testAddr = Object.assign({ guid, version: 1 }, TEST_ADDRESS_1);
    132 
    133  await checkingSyncChange("add", async () =>
    134    profileStorage.addresses.add(testAddr, { sourceSync: true })
    135  );
    136 
    137  let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
    138  equal(changeCounter, 0);
    139 
    140  await Assert.rejects(
    141    profileStorage.addresses.add({ guid, deleted: true }, { sourceSync: true }),
    142    /Record aaaaaaaaaaaa already exists/
    143  );
    144 });
    145 
    146 add_task(async function test_add_tombstone_sourceSync() {
    147  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
    148 
    149  let guid = profileStorage.addresses._generateGUID();
    150  let testAddr = { guid, deleted: true };
    151  await checkingSyncChange("add", async () =>
    152    profileStorage.addresses.add(testAddr, { sourceSync: true })
    153  );
    154 
    155  let added = await findGUID(profileStorage.addresses, guid, {
    156    includeDeleted: true,
    157  });
    158  ok(added);
    159  equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
    160  ok(added.deleted);
    161 
    162  // Adding same record again shouldn't throw (or change anything)
    163  await checkingSyncChange("add", async () =>
    164    profileStorage.addresses.add(testAddr, { sourceSync: true })
    165  );
    166 
    167  added = await findGUID(profileStorage.addresses, guid, {
    168    includeDeleted: true,
    169  });
    170  equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
    171  ok(added.deleted);
    172 });
    173 
    174 add_task(async function test_add_resurrects_tombstones() {
    175  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
    176 
    177  let guid = profileStorage.addresses._generateGUID();
    178 
    179  // Add a tombstone.
    180  await profileStorage.addresses.add({ guid, deleted: true });
    181 
    182  // You can't re-add an item with an explicit GUID.
    183  let resurrected = Object.assign({}, TEST_ADDRESS_1, { guid, version: 1 });
    184  await Assert.rejects(
    185    profileStorage.addresses.add(resurrected),
    186    /"(guid|version)" is not a valid field/
    187  );
    188 
    189  // But Sync can!
    190  let guid3 = await profileStorage.addresses.add(resurrected, {
    191    sourceSync: true,
    192  });
    193  equal(guid, guid3);
    194 
    195  let got = await profileStorage.addresses.get(guid);
    196  equal(got.name, TEST_ADDRESS_1.name);
    197 });
    198 
    199 add_task(async function test_remove_sourceSync_localChanges() {
    200  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [
    201    TEST_ADDRESS_1,
    202  ]);
    203  profileStorage.addresses.pullSyncChanges(); // force sync metadata
    204 
    205  let [{ guid }] = await profileStorage.addresses.getAll();
    206 
    207  equal(getSyncChangeCounter(profileStorage.addresses, guid), 1);
    208  // try and remove a record stored locally with local changes
    209  await checkingSyncChange("remove", async () =>
    210    profileStorage.addresses.remove(guid, { sourceSync: true })
    211  );
    212 
    213  let record = await profileStorage.addresses.get(guid);
    214  ok(record);
    215  equal(getSyncChangeCounter(profileStorage.addresses, guid), 1);
    216 });
    217 
    218 add_task(async function test_remove_sourceSync_unknown() {
    219  // remove a record not stored locally
    220  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
    221 
    222  let guid = profileStorage.addresses._generateGUID();
    223  await checkingSyncChange("remove", async () =>
    224    profileStorage.addresses.remove(guid, { sourceSync: true })
    225  );
    226 
    227  let tombstone = await findGUID(profileStorage.addresses, guid, {
    228    includeDeleted: true,
    229  });
    230  ok(tombstone.deleted);
    231  equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
    232 });
    233 
    234 add_task(async function test_remove_sourceSync_unchanged() {
    235  // Remove a local record without a change counter.
    236  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
    237 
    238  let guid = profileStorage.addresses._generateGUID();
    239  let addr = Object.assign({ guid, version: 1 }, TEST_ADDRESS_1);
    240  // add a record with sourceSync to guarantee changeCounter == 0
    241  await checkingSyncChange("add", async () =>
    242    profileStorage.addresses.add(addr, { sourceSync: true })
    243  );
    244 
    245  equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
    246 
    247  await checkingSyncChange("remove", async () =>
    248    profileStorage.addresses.remove(guid, { sourceSync: true })
    249  );
    250 
    251  let tombstone = await findGUID(profileStorage.addresses, guid, {
    252    includeDeleted: true,
    253  });
    254  ok(tombstone.deleted);
    255  equal(getSyncChangeCounter(profileStorage.addresses, guid), 0);
    256 });
    257 
    258 add_task(async function test_pullSyncChanges() {
    259  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [
    260    TEST_ADDRESS_1,
    261    TEST_ADDRESS_2,
    262  ]);
    263 
    264  let startAddresses = await profileStorage.addresses.getAll();
    265  equal(startAddresses.length, 2);
    266  // All should start without sync metadata
    267  for (let { guid } of profileStorage.addresses._store.data.addresses) {
    268    let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
    269    equal(changeCounter, -1);
    270  }
    271  profileStorage.addresses.pullSyncChanges(); // force sync metadata
    272 
    273  let addedDirectGUID = profileStorage.addresses._generateGUID();
    274  let testAddr = Object.assign(
    275    { guid: addedDirectGUID, version: 1 },
    276    TEST_ADDRESS_1,
    277    TEST_ADDRESS_3
    278  );
    279 
    280  await checkingSyncChange("add", async () =>
    281    profileStorage.addresses.add(testAddr, { sourceSync: true })
    282  );
    283 
    284  let tombstoneGUID = profileStorage.addresses._generateGUID();
    285  await checkingSyncChange("add", async () =>
    286    profileStorage.addresses.add(
    287      { guid: tombstoneGUID, deleted: true },
    288      { sourceSync: true }
    289    )
    290  );
    291 
    292  let onChanged = TestUtils.topicObserved(
    293    "formautofill-storage-changed",
    294    (subject, data) => data == "remove"
    295  );
    296 
    297  profileStorage.addresses.remove(startAddresses[0].guid);
    298  await onChanged;
    299 
    300  let addresses = await profileStorage.addresses.getAll({
    301    includeDeleted: true,
    302  });
    303 
    304  // Should contain changes with a change counter
    305  let changes = profileStorage.addresses.pullSyncChanges();
    306  equal(Object.keys(changes).length, 2);
    307 
    308  ok(changes[startAddresses[0].guid].profile.deleted);
    309  equal(changes[startAddresses[0].guid].counter, 2);
    310 
    311  ok(!changes[startAddresses[1].guid].profile.deleted);
    312  equal(changes[startAddresses[1].guid].counter, 1);
    313 
    314  ok(
    315    !changes[tombstoneGUID],
    316    "Missing because it's a tombstone from sourceSync"
    317  );
    318  ok(!changes[addedDirectGUID], "Missing because it was added with sourceSync");
    319 
    320  for (let address of addresses) {
    321    let change = changes[address.guid];
    322    if (!change) {
    323      continue;
    324    }
    325    equal(change.profile.guid, address.guid);
    326    let changeCounter = getSyncChangeCounter(
    327      profileStorage.addresses,
    328      change.profile.guid
    329    );
    330    equal(change.counter, changeCounter);
    331    ok(!change.synced);
    332  }
    333 });
    334 
    335 add_task(async function test_pullPushChanges() {
    336  // round-trip changes between pull and push
    337  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
    338  let psa = profileStorage.addresses;
    339 
    340  let guid1 = await psa.add(TEST_ADDRESS_1);
    341  let guid2 = await psa.add(TEST_ADDRESS_2);
    342  let guid3 = await psa.add(TEST_ADDRESS_3);
    343 
    344  let changes = psa.pullSyncChanges();
    345 
    346  equal(getSyncChangeCounter(psa, guid1), 1);
    347  equal(getSyncChangeCounter(psa, guid2), 1);
    348  equal(getSyncChangeCounter(psa, guid3), 1);
    349 
    350  // between the pull and the push we change the second.
    351  await psa.update(guid2, Object.assign({}, TEST_ADDRESS_2, { country: "AU" }));
    352  equal(getSyncChangeCounter(psa, guid2), 2);
    353  // and update the changeset to indicated we did update the first 2, but failed
    354  // to update the 3rd for some reason.
    355  changes[guid1].synced = true;
    356  changes[guid2].synced = true;
    357 
    358  psa.pushSyncChanges(changes);
    359 
    360  // first was synced correctly.
    361  equal(getSyncChangeCounter(psa, guid1), 0);
    362  // second was synced correctly, but it had a change while syncing.
    363  equal(getSyncChangeCounter(psa, guid2), 1);
    364  // 3rd wasn't marked as having synced.
    365  equal(getSyncChangeCounter(psa, guid3), 1);
    366 });
    367 
    368 add_task(async function test_changeGUID() {
    369  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, []);
    370 
    371  let newguid = () => profileStorage.addresses._generateGUID();
    372 
    373  let guid_synced = await profileStorage.addresses.add(TEST_ADDRESS_1);
    374 
    375  // pullSyncChanges so guid_synced is flagged as syncing.
    376  profileStorage.addresses.pullSyncChanges();
    377 
    378  // and 2 items that haven't been synced.
    379  let guid_u1 = await profileStorage.addresses.add(TEST_ADDRESS_2);
    380  let guid_u2 = await profileStorage.addresses.add(TEST_ADDRESS_3);
    381 
    382  // Change a non-existing guid
    383  Assert.throws(
    384    () => profileStorage.addresses.changeGUID(newguid(), newguid()),
    385    /changeGUID: no source record/
    386  );
    387  // Change to a guid that already exists.
    388  Assert.throws(
    389    () => profileStorage.addresses.changeGUID(guid_u1, guid_u2),
    390    /changeGUID: record with destination id exists already/
    391  );
    392  // Try and change a guid that's already been synced.
    393  Assert.throws(
    394    () => profileStorage.addresses.changeGUID(guid_synced, newguid()),
    395    /changeGUID: existing record has already been synced/
    396  );
    397 
    398  // Change an item to itself makes no sense.
    399  Assert.throws(
    400    () => profileStorage.addresses.changeGUID(guid_u1, guid_u1),
    401    /changeGUID: old and new IDs are the same/
    402  );
    403 
    404  // and one that works.
    405  equal(
    406    (await profileStorage.addresses.getAll({ includeDeleted: true })).length,
    407    3
    408  );
    409  let targetguid = newguid();
    410  profileStorage.addresses.changeGUID(guid_u1, targetguid);
    411  equal(
    412    (await profileStorage.addresses.getAll({ includeDeleted: true })).length,
    413    3
    414  );
    415 
    416  ok(
    417    await profileStorage.addresses.get(guid_synced),
    418    "synced item still exists."
    419  );
    420  ok(
    421    await profileStorage.addresses.get(guid_u2),
    422    "guid we didn't touch still exists."
    423  );
    424  ok(await profileStorage.addresses.get(targetguid), "target guid exists.");
    425  ok(
    426    !(await profileStorage.addresses.get(guid_u1)),
    427    "old guid no longer exists."
    428  );
    429 });
    430 
    431 add_task(async function test_findDuplicateGUID() {
    432  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [
    433    TEST_ADDRESS_1,
    434  ]);
    435 
    436  let [record] = await profileStorage.addresses.getAll({ rawData: true });
    437  await Assert.rejects(
    438    profileStorage.addresses.findDuplicateGUID(record),
    439    /Record \w+ already exists/,
    440    "Should throw if the GUID already exists"
    441  );
    442 
    443  // Add a malformed record, passing `sourceSync` to work around the record
    444  // normalization logic that would prevent this.
    445  let timeLastModified = Date.now();
    446  let timeCreated = timeLastModified - 60 * 1000;
    447 
    448  await profileStorage.addresses.add(
    449    {
    450      guid: profileStorage.addresses._generateGUID(),
    451      version: 1,
    452      timeCreated,
    453      timeLastModified,
    454    },
    455    { sourceSync: true }
    456  );
    457 
    458  strictEqual(
    459    await profileStorage.addresses.findDuplicateGUID({
    460      guid: profileStorage.addresses._generateGUID(),
    461      version: 1,
    462      timeCreated,
    463      timeLastModified,
    464    }),
    465    null,
    466    "Should ignore internal fields and malformed records"
    467  );
    468 });
    469 
    470 add_task(async function test_reset() {
    471  let profileStorage = await initProfileStorage(TEST_STORE_FILE_NAME, [
    472    TEST_ADDRESS_1,
    473    TEST_ADDRESS_2,
    474  ]);
    475 
    476  let addresses = await profileStorage.addresses.getAll();
    477  // All should start without sync metadata
    478  for (let { guid } of addresses) {
    479    let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
    480    equal(changeCounter, -1);
    481  }
    482  // pullSyncChanges should create the metadata.
    483  profileStorage.addresses.pullSyncChanges();
    484  addresses = await profileStorage.addresses.getAll();
    485  for (let { guid } of addresses) {
    486    let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
    487    equal(changeCounter, 1);
    488  }
    489  // and resetSync should wipe it.
    490  profileStorage.addresses.resetSync();
    491  addresses = await profileStorage.addresses.getAll();
    492  for (let { guid } of addresses) {
    493    let changeCounter = getSyncChangeCounter(profileStorage.addresses, guid);
    494    equal(changeCounter, -1);
    495  }
    496 });