/** * Tests the migration algorithm in profileStorage. */ "use strict"; let FormAutofillStorage; add_setup(async () => { ({ FormAutofillStorage } = ChromeUtils.importESModule( "resource://autofill/FormAutofillStorage.sys.mjs" )); }); const TEST_STORE_FILE_NAME = "test-profile.json"; const { ADDRESS_SCHEMA_VERSION } = ChromeUtils.importESModule( "resource://autofill/FormAutofillStorageBase.sys.mjs" ); const { CREDIT_CARD_SCHEMA_VERSION } = ChromeUtils.importESModule( "resource://autofill/FormAutofillStorageBase.sys.mjs" ); const ADDRESS_TESTCASES = [ { description: "The record version is equal to the current version. The migration shouldn't be invoked.", record: { guid: "test-guid", version: ADDRESS_SCHEMA_VERSION, "given-name": "Timothy", name: "John", // The cached name field doesn't align "given-name" but it // won't be recomputed because the migration isn't invoked. }, expectedResult: { guid: "test-guid", version: ADDRESS_SCHEMA_VERSION, "given-name": "Timothy", name: "John", }, }, { description: "The record version is greater than the current version. The migration shouldn't be invoked.", record: { guid: "test-guid", version: 99, "given-name": "Timothy", name: "John", }, expectedResult: { guid: "test-guid", version: 99, "given-name": "Timothy", name: "John", }, }, { description: "The record version is less than the current version. The migration should be invoked.", record: { guid: "test-guid", version: 0, "given-name": "Timothy", name: "John", }, expectedResult: { guid: "test-guid", version: ADDRESS_SCHEMA_VERSION, "given-name": "Timothy", name: "Timothy", }, }, { description: "The record version is omitted. The migration should be invoked.", record: { guid: "test-guid", "given-name": "Timothy", name: "John", "unknown-1": "an unknown field from another client", }, expectedResult: { guid: "test-guid", version: ADDRESS_SCHEMA_VERSION, "given-name": "Timothy", name: "Timothy", "unknown-1": "an unknown field from another client", }, }, { description: "The record version is an invalid value. The migration should be invoked.", record: { guid: "test-guid", version: "ABCDE", "given-name": "Timothy", name: "John", "unknown-1": "an unknown field from another client", }, expectedResult: { guid: "test-guid", version: ADDRESS_SCHEMA_VERSION, "given-name": "Timothy", name: "Timothy", "unknown-1": "an unknown field from another client", }, }, { description: "The omitted computed fields should be always recomputed even the record version is up-to-date.", record: { guid: "test-guid", version: ADDRESS_SCHEMA_VERSION, "given-name": "Timothy", }, expectedResult: { guid: "test-guid", version: ADDRESS_SCHEMA_VERSION, "given-name": "Timothy", name: "Timothy", }, }, { description: "The migration shouldn't be invoked on tombstones.", record: { guid: "test-guid", timeLastModified: 12345, deleted: true, }, expectedResult: { guid: "test-guid", timeLastModified: 12345, deleted: true, // Make sure no new fields are appended. version: undefined, name: undefined, }, }, ]; const CREDIT_CARD_TESTCASES = [ { description: "The record version is equal to the current version. The migration shouldn't be invoked.", record: { guid: "test-guid", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Timothy", "cc-given-name": "John", // The cached "cc-given-name" field doesn't align // "cc-name" but it won't be recomputed because // the migration isn't invoked. }, expectedResult: { guid: "test-guid", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Timothy", "cc-given-name": "John", }, }, { description: "The record version is greater than the current version. The migration shouldn't be invoked.", record: { guid: "test-guid", version: 99, "cc-name": "Timothy", "cc-given-name": "John", }, expectedResult: { guid: "test-guid", version: 99, "cc-name": "Timothy", "cc-given-name": "John", }, }, { description: "The record version is less than the current version. The migration should be invoked.", record: { guid: "test-guid", version: 0, "cc-name": "Timothy", "cc-given-name": "John", }, expectedResult: { guid: "test-guid", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Timothy", "cc-given-name": "Timothy", }, }, { description: "The record version is omitted. The migration should be invoked.", record: { guid: "test-guid", "cc-name": "Timothy", "cc-given-name": "John", "unknown-1": "an unknown field from another client", }, expectedResult: { guid: "test-guid", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Timothy", "cc-given-name": "Timothy", "unknown-1": "an unknown field from another client", }, }, { description: "The record version is an invalid value. The migration should be invoked.", record: { guid: "test-guid", version: "ABCDE", "cc-name": "Timothy", "cc-given-name": "John", "unknown-1": "an unknown field from another client", }, expectedResult: { guid: "test-guid", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Timothy", "cc-given-name": "Timothy", "unknown-1": "an unknown field from another client", }, }, { description: "The omitted computed fields should be always recomputed even the record version is up-to-date.", record: { guid: "test-guid", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Timothy", }, expectedResult: { guid: "test-guid", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Timothy", "cc-given-name": "Timothy", }, }, { description: "The migration shouldn't be invoked on tombstones.", record: { guid: "test-guid", timeLastModified: 12345, deleted: true, }, expectedResult: { guid: "test-guid", timeLastModified: 12345, deleted: true, // Make sure no new fields are appended. version: undefined, "cc-given-name": undefined, }, }, ]; let do_check_record_matches = (expectedRecord, record) => { for (let key in expectedRecord) { Assert.equal(expectedRecord[key], record[key]); } }; add_task(async function test_migrateAddressRecords() { let path = getTempFile(TEST_STORE_FILE_NAME).path; let profileStorage = new FormAutofillStorage(path); await profileStorage.initialize(); for (let testcase of ADDRESS_TESTCASES) { info(testcase.description); profileStorage._store.data.addresses = [testcase.record]; await profileStorage.addresses._migrateRecord(testcase.record, 0); do_check_record_matches( testcase.expectedResult, profileStorage.addresses._data[0] ); } }); add_task(async function test_migrateCreditCardRecords() { let path = getTempFile(TEST_STORE_FILE_NAME).path; let profileStorage = new FormAutofillStorage(path); await profileStorage.initialize(); for (let testcase of CREDIT_CARD_TESTCASES) { info(testcase.description); profileStorage._store.data.creditCards = [testcase.record]; await profileStorage.creditCards._migrateRecord(testcase.record, 0); do_check_record_matches( testcase.expectedResult, profileStorage.creditCards._data[0] ); } }); add_task(async function test_migrateEncryptedCreditCardNumber() { let path = getTempFile(TEST_STORE_FILE_NAME).path; let profileStorage = new FormAutofillStorage(path); await profileStorage.initialize(); info("v1 and v2 schema cards should be abandoned."); let v1record = { guid: "test-guid1", version: 1, "cc-name": "Timothy", "cc-number-encrypted": "aaaa", }; let v2record = { guid: "test-guid2", version: 2, "cc-name": "Bob", "cc-number-encrypted": "bbbb", }; profileStorage._store.data.creditCards = [v1record, v2record]; await profileStorage.creditCards._migrateRecord(v1record, 0); await profileStorage.creditCards._migrateRecord(v2record, 1); v1record = profileStorage.creditCards._data[0]; v2record = profileStorage.creditCards._data[1]; Assert.ok(v1record.deleted); Assert.ok(v2record.deleted); }); add_task(async function test_migrateDeprecatedCreditCardV4() { let path = getTempFile(TEST_STORE_FILE_NAME).path; let profileStorage = new FormAutofillStorage(path); await profileStorage.initialize(); let records = [ { guid: "test-guid1", version: CREDIT_CARD_SCHEMA_VERSION, "cc-name": "Alice", _sync: { changeCounter: 0, lastSyncedFields: {}, }, }, { guid: "test-guid2", version: 4, "cc-name": "Timothy", _sync: { changeCounter: 0, lastSyncedFields: {}, }, }, { guid: "test-guid3", version: 4, "cc-name": "Bob", }, ]; profileStorage._store.data.creditCards = records; for (let idx = 0; idx < records.length; idx++) { await profileStorage.creditCards._migrateRecord(records[idx], idx); } profileStorage.creditCards.pullSyncChanges(); // Record that has already synced before, do not sync again equal(getSyncChangeCounter(profileStorage.creditCards, records[0].guid), 0); // alaways force sync v4 record equal(records[1].version, CREDIT_CARD_SCHEMA_VERSION); equal(getSyncChangeCounter(profileStorage.creditCards, records[1].guid), 1); equal(records[2].version, CREDIT_CARD_SCHEMA_VERSION); equal(getSyncChangeCounter(profileStorage.creditCards, records[2].guid), 1); });