/** * Tests tombstones in address/creditcard records. */ "use strict"; let FormAutofillStorage; add_setup(async () => { ({ FormAutofillStorage } = ChromeUtils.importESModule( "resource://autofill/FormAutofillStorage.sys.mjs" )); }); const TEST_STORE_FILE_NAME = "test-tombstones.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", }; const TEST_CC_1 = { "cc-name": "John Doe", "cc-number": "4111111111111111", "cc-exp-month": 4, "cc-exp-year": 2017, }; let do_check_tombstone_record = profile => { Assert.ok(profile.deleted); Assert.deepEqual( Object.keys(profile).sort(), ["guid", "timeLastModified", "deleted"].sort() ); }; // Like add_task, but actually adds 2 - one for addresses and one for cards. function add_storage_task(test_function) { add_task(async function () { let path = getTempFile(TEST_STORE_FILE_NAME).path; let profileStorage = new FormAutofillStorage(path); let testCC1 = Object.assign({}, TEST_CC_1); await profileStorage.initialize(); for (let [storage, record] of [ [profileStorage.addresses, TEST_ADDRESS_1], [profileStorage.creditCards, testCC1], ]) { await test_function(storage, record); } }); } add_storage_task(async function test_simple_tombstone(storage, record) { info("check simple tombstone semantics"); let guid = await storage.add(record); Assert.equal((await storage.getAll()).length, 1); storage.remove(guid); // should be unable to get it normally. Assert.equal(await storage.get(guid), null); // and getAll should also not return it. Assert.equal((await storage.getAll()).length, 0); // but getAll allows us to access deleted items - but we didn't create // a tombstone here, so even that will not get it. let all = await storage.getAll({ includeDeleted: true }); Assert.equal(all.length, 0); }); add_storage_task(async function test_simple_synctombstone(storage, record) { info("check simple tombstone semantics for synced records"); let guid = await storage.add(record); Assert.equal((await storage.getAll()).length, 1); storage.pullSyncChanges(); // force sync metadata, which triggers tombstone behaviour. storage.remove(guid); // should be unable to get it normally. Assert.equal(await storage.get(guid), null); // and getAll should also not return it. Assert.equal((await storage.getAll()).length, 0); // but getAll allows us to access deleted items. let all = await storage.getAll({ includeDeleted: true }); Assert.equal(all.length, 1); do_check_tombstone_record(all[0]); // a tombstone got from API should look exactly the same as it got from the // disk (besides "_sync"). let tombstoneInDisk = Object.assign( {}, storage._store.data[storage._collectionName][0] ); delete tombstoneInDisk._sync; do_check_tombstone_record(tombstoneInDisk); }); add_storage_task(async function test_add_tombstone(storage, record) { info("Should be able to add a new tombstone"); let guid = await storage.add({ guid: "test-guid-1", deleted: true }); // should be unable to get it normally. Assert.equal(await storage.get(guid), null); // and getAll should also not return it. Assert.equal((await storage.getAll()).length, 0); // but getAll allows us to access deleted items. let all = await storage.getAll({ rawData: true, includeDeleted: true }); Assert.equal(all.length, 1); do_check_tombstone_record(all[0]); // a tombstone got from API should look exactly the same as it got from the // disk (besides "_sync"). let tombstoneInDisk = Object.assign( {}, storage._store.data[storage._collectionName][0] ); delete tombstoneInDisk._sync; do_check_tombstone_record(tombstoneInDisk); }); add_storage_task(async function test_add_tombstone_without_guid( storage, record ) { info("Should not be able to add a new tombstone without specifying the guid"); await Assert.rejects(storage.add({ deleted: true }), /Record missing GUID/); Assert.equal((await storage.getAll({ includeDeleted: true })).length, 0); }); add_storage_task(async function test_add_tombstone_existing_guid( storage, record ) { info( "Should not be able to add a new tombstone when a record with that ID exists" ); let guid = await storage.add(record); await Assert.rejects( storage.add({ guid, deleted: true }), /a record with this GUID already exists/ ); // same if the existing item is already a tombstone. await storage.add({ guid: "test-guid-1", deleted: true }); await Assert.rejects( storage.add({ guid: "test-guid-1", deleted: true }), /a record with this GUID already exists/ ); }); add_storage_task(async function test_update_tombstone(storage, record) { info("Updating a tombstone should fail"); let guid = await storage.add({ guid: "test-guid-1", deleted: true }); await Assert.rejects(storage.update(guid, {}), /No matching record./); }); add_storage_task(async function test_remove_existing_tombstone( storage, record ) { info("Removing a record that's already a tombstone should be a no-op"); let guid = await storage.add({ guid: "test-guid-1", deleted: true, timeLastModified: 1234, }); storage.remove(guid); let all = await storage.getAll({ rawData: true, includeDeleted: true }); Assert.equal(all.length, 1); do_check_tombstone_record(all[0]); equal(all[0].timeLastModified, 1234); // should not be updated to now(). });