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 });