Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'linux' && ccov OR os == 'android' OR tsan OR os == 'mac' && os_version == '11.20' && arch == 'aarch64'
- Manifest: browser/extensions/formautofill/test/unit/xpcshell.toml
/**
* Tests FormAutofillStorage object with creditCards records.
*/
"use strict";
ChromeUtils.defineESModuleGetters(this, {
Preferences: "resource://gre/modules/Preferences.sys.mjs",
});
const { CreditCard } = ChromeUtils.importESModule(
"resource://gre/modules/CreditCard.sys.mjs"
);
let FormAutofillStorage;
let CREDIT_CARD_SCHEMA_VERSION;
add_setup(async () => {
({ FormAutofillStorage } = ChromeUtils.importESModule(
"resource://autofill/FormAutofillStorage.sys.mjs"
));
({ CREDIT_CARD_SCHEMA_VERSION } = ChromeUtils.importESModule(
"resource://autofill/FormAutofillStorageBase.sys.mjs"
));
});
const TEST_STORE_FILE_NAME = "test-credit-card.json";
const COLLECTION_NAME = "creditCards";
const TEST_CREDIT_CARD_1 = {
"cc-name": "John Doe",
"cc-number": "4929001587121045",
"cc-exp-month": 4,
"cc-exp-year": 2017,
};
const TEST_CREDIT_CARD_2 = {
"cc-name": "Timothy Berners-Lee",
"cc-number": "5103059495477870",
"cc-exp-month": 12,
"cc-exp-year": 2022,
};
const TEST_CREDIT_CARD_3 = {
"cc-number": "3589993783099582",
"cc-exp-month": 1,
"cc-exp-year": 2000,
};
const TEST_CREDIT_CARD_WITH_BILLING_ADDRESS = {
"cc-name": "J. Smith",
"cc-number": "4111111111111111",
billingAddressGUID: "9m6hf4gfr6ge",
};
const TEST_CREDIT_CARD_WITH_EMPTY_FIELD = {
billingAddressGUID: "",
"cc-name": "",
"cc-number": "344060747836806",
"cc-exp-month": 1,
};
const TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD = {
"cc-given-name": "",
"cc-additional-name": "",
"cc-family-name": "",
"cc-exp": "",
"cc-number": "5415425865751454",
};
const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
"cc-number": "344060747836806",
"cc-exp-month": 1,
"cc-exp-year": 12,
};
const TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE = {
"cc-name": "John Doe",
"cc-number": "5103059495477870",
"cc-exp-month": 13,
"cc-exp-year": -3,
};
const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
"cc-name": "John Doe",
"cc-number": "5103 0594 9547 7870",
};
const TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE = {
"cc-exp-month": 13,
};
const TEST_CREDIT_CARD_EMPTY_AFTER_UPDATE_CREDIT_CARD_1 = {
"cc-name": "",
"cc-number": "",
"cc-exp-month": 13,
"cc-exp-year": "",
};
let prepareTestCreditCards = async function (path) {
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let onChanged = TestUtils.topicObserved(
"formautofill-storage-changed",
(subject, data) =>
data == "add" &&
subject.wrappedJSObject.guid &&
subject.wrappedJSObject.collectionName == COLLECTION_NAME
);
Assert.ok(await profileStorage.creditCards.add(TEST_CREDIT_CARD_1));
await onChanged;
Assert.ok(await profileStorage.creditCards.add(TEST_CREDIT_CARD_2));
await onChanged;
await profileStorage._saveImmediately();
};
let reCCNumber = /^(\*+)(.{4})$/;
let do_check_credit_card_matches = (creditCardWithMeta, creditCard) => {
for (let key in creditCard) {
if (key == "cc-number") {
let matches = reCCNumber.exec(creditCardWithMeta["cc-number"]);
Assert.notEqual(matches, null);
Assert.equal(
creditCardWithMeta["cc-number"].length,
creditCard["cc-number"].length
);
Assert.equal(creditCard["cc-number"].endsWith(matches[2]), true);
Assert.notEqual(creditCard["cc-number-encrypted"], "");
} else {
Assert.equal(creditCardWithMeta[key], creditCard[key], "Testing " + key);
}
}
};
add_task(async function test_initialize() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
Assert.equal(profileStorage._store.data.version, 1);
Assert.equal(profileStorage._store.data.creditCards.length, 0);
let data = profileStorage._store.data;
Assert.deepEqual(data.creditCards, []);
await profileStorage._saveImmediately();
profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
Assert.deepEqual(profileStorage._store.data, data);
});
add_task(async function test_getAll() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
await prepareTestCreditCards(path);
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCards = await profileStorage.creditCards.getAll();
Assert.equal(creditCards.length, 2);
do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
// Check computed fields.
Assert.equal(creditCards[0]["cc-given-name"], "John");
Assert.equal(creditCards[0]["cc-family-name"], "Doe");
Assert.equal(creditCards[0]["cc-exp"], "2017-04");
// Test with rawData set.
creditCards = await profileStorage.creditCards.getAll({ rawData: true });
Assert.equal(creditCards[0]["cc-given-name"], undefined);
Assert.equal(creditCards[0]["cc-family-name"], undefined);
Assert.equal(creditCards[0]["cc-exp"], undefined);
// Modifying output shouldn't affect the storage.
creditCards[0]["cc-name"] = "test";
do_check_credit_card_matches(
(await profileStorage.creditCards.getAll())[0],
TEST_CREDIT_CARD_1
);
});
add_task(async function test_get() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
await prepareTestCreditCards(path);
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCards = await profileStorage.creditCards.getAll();
let guid = creditCards[0].guid;
let creditCard = await profileStorage.creditCards.get(guid);
do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_1);
// Modifying output shouldn't affect the storage.
creditCards[0]["cc-name"] = "test";
do_check_credit_card_matches(
await profileStorage.creditCards.get(guid),
TEST_CREDIT_CARD_1
);
Assert.equal(await profileStorage.creditCards.get("INVALID_GUID"), null);
});
add_task(async function test_add() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
await prepareTestCreditCards(path);
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCards = await profileStorage.creditCards.getAll();
Assert.equal(creditCards.length, 2);
do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
Assert.notEqual(creditCards[0].guid, undefined);
Assert.equal(creditCards[0].version, CREDIT_CARD_SCHEMA_VERSION);
Assert.notEqual(creditCards[0].timeCreated, undefined);
Assert.equal(creditCards[0].timeLastModified, creditCards[0].timeCreated);
Assert.equal(creditCards[0].timeLastUsed, 0);
Assert.equal(creditCards[0].timesUsed, 0);
// Empty string should be deleted before saving.
await profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_EMPTY_FIELD);
let creditCard = profileStorage.creditCards._data[2];
Assert.equal(
creditCard["cc-exp-month"],
TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]
);
Assert.equal(creditCard["cc-name"], undefined);
Assert.equal(creditCard.billingAddressGUID, undefined);
// Empty computed fields shouldn't cause any problem.
await profileStorage.creditCards.add(
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD
);
creditCard = profileStorage.creditCards._data[3];
Assert.equal(
creditCard["cc-number"],
CreditCard.getLongMaskedNumber(
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]
)
);
await Assert.rejects(
profileStorage.creditCards.add({}),
/Record contains no valid field\./
);
await Assert.rejects(
profileStorage.creditCards.add(TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE),
/Record contains no valid field\./
);
});
add_task(async function test_addWithBillingAddress() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCards = await profileStorage.creditCards.getAll();
Assert.equal(creditCards.length, 0);
await profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_BILLING_ADDRESS);
creditCards = await profileStorage.creditCards.getAll();
Assert.equal(creditCards.length, 1);
do_check_credit_card_matches(
creditCards[0],
TEST_CREDIT_CARD_WITH_BILLING_ADDRESS
);
});
add_task(async function test_update() {
// Test assumes that when an entry is saved a second time, it's last modified date will
// be different from the first. With high values of precision reduction, we execute too
// fast for that to be true.
let timerPrecision = Preferences.get("privacy.reduceTimerPrecision");
Preferences.set("privacy.reduceTimerPrecision", false);
registerCleanupFunction(function () {
Preferences.set("privacy.reduceTimerPrecision", timerPrecision);
});
let path = getTempFile(TEST_STORE_FILE_NAME).path;
await prepareTestCreditCards(path);
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCards = await profileStorage.creditCards.getAll();
let guid = creditCards[1].guid;
let timeLastModified = creditCards[1].timeLastModified;
let onChanged = TestUtils.topicObserved(
"formautofill-storage-changed",
(subject, data) =>
data == "update" &&
subject.wrappedJSObject.guid == guid &&
subject.wrappedJSObject.collectionName == COLLECTION_NAME
);
Assert.notEqual(creditCards[1]["cc-name"], undefined);
await profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_3);
await onChanged;
await profileStorage._saveImmediately();
profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCard = await profileStorage.creditCards.get(guid);
Assert.equal(creditCard["cc-name"], undefined);
Assert.notEqual(creditCard.timeLastModified, timeLastModified);
do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3);
// Empty string should be deleted while updating.
await profileStorage.creditCards.update(
profileStorage.creditCards._data[0].guid,
TEST_CREDIT_CARD_WITH_EMPTY_FIELD
);
creditCard = profileStorage.creditCards._data[0];
Assert.equal(
creditCard["cc-exp-month"],
TEST_CREDIT_CARD_WITH_EMPTY_FIELD["cc-exp-month"]
);
Assert.equal(creditCard["cc-name"], undefined);
Assert.equal(creditCard["cc-type"], "amex");
Assert.equal(creditCard.billingAddressGUID, undefined);
// Empty computed fields shouldn't cause any problem.
await profileStorage.creditCards.update(
profileStorage.creditCards._data[0].guid,
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD,
false
);
creditCard = profileStorage.creditCards._data[0];
Assert.equal(
creditCard["cc-number"],
CreditCard.getLongMaskedNumber(
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]
)
);
await profileStorage.creditCards.update(
profileStorage.creditCards._data[1].guid,
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD,
true
);
creditCard = profileStorage.creditCards._data[1];
Assert.equal(
creditCard["cc-number"],
CreditCard.getLongMaskedNumber(
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]
)
);
// Decryption failure of existing record should not prevent it from being updated.
creditCard = profileStorage.creditCards._data[0];
creditCard["cc-number-encrypted"] = "INVALID";
await profileStorage.creditCards.update(
profileStorage.creditCards._data[0].guid,
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD,
false
);
creditCard = profileStorage.creditCards._data[0];
Assert.equal(
creditCard["cc-number"],
CreditCard.getLongMaskedNumber(
TEST_CREDIT_CARD_WITH_EMPTY_COMPUTED_FIELD["cc-number"]
)
);
await Assert.rejects(
profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
/No matching record\./
);
await Assert.rejects(
profileStorage.creditCards.update(guid, {}),
/Record contains no valid field\./
);
await Assert.rejects(
profileStorage.creditCards.update(
guid,
TEST_CREDIT_CARD_EMPTY_AFTER_NORMALIZE
),
/Record contains no valid field\./
);
await profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_1);
await Assert.rejects(
profileStorage.creditCards.update(
guid,
TEST_CREDIT_CARD_EMPTY_AFTER_UPDATE_CREDIT_CARD_1
),
/Record contains no valid field\./
);
});
add_task(async function test_validate() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
await profileStorage.creditCards.add(
TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE
);
await profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
await profileStorage.creditCards.add(
TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS
);
let creditCards = await profileStorage.creditCards.getAll();
Assert.equal(creditCards[0]["cc-exp-month"], undefined);
Assert.equal(creditCards[0]["cc-exp-year"], undefined);
Assert.equal(creditCards[0]["cc-exp"], undefined);
let month = TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-month"];
let year =
parseInt(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-year"], 10) + 2000;
Assert.equal(creditCards[1]["cc-exp-month"], month);
Assert.equal(creditCards[1]["cc-exp-year"], year);
Assert.equal(
creditCards[1]["cc-exp"],
year + "-" + month.toString().padStart(2, "0")
);
Assert.equal(creditCards[2]["cc-number"].length, 16);
});
add_task(async function test_notifyUsed() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
await prepareTestCreditCards(path);
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCards = await profileStorage.creditCards.getAll();
let guid = creditCards[1].guid;
let timeLastUsed = creditCards[1].timeLastUsed;
let timesUsed = creditCards[1].timesUsed;
let onChanged = TestUtils.topicObserved(
"formautofill-storage-changed",
(subject, data) =>
data == "notifyUsed" &&
subject.wrappedJSObject.collectionName == COLLECTION_NAME &&
subject.wrappedJSObject.guid == guid
);
profileStorage.creditCards.notifyUsed(guid);
await onChanged;
await profileStorage._saveImmediately();
profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCard = await profileStorage.creditCards.get(guid);
Assert.equal(creditCard.timesUsed, timesUsed + 1);
Assert.notEqual(creditCard.timeLastUsed, timeLastUsed);
Assert.throws(
() => profileStorage.creditCards.notifyUsed("INVALID_GUID"),
/No matching record\./
);
});
add_task(async function test_remove() {
let path = getTempFile(TEST_STORE_FILE_NAME).path;
await prepareTestCreditCards(path);
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
let creditCards = await profileStorage.creditCards.getAll();
let guid = creditCards[1].guid;
let onChanged = TestUtils.topicObserved(
"formautofill-storage-changed",
(subject, data) =>
data == "remove" &&
subject.wrappedJSObject.guid == guid &&
subject.wrappedJSObject.collectionName == COLLECTION_NAME
);
Assert.equal(creditCards.length, 2);
profileStorage.creditCards.remove(guid);
await onChanged;
await profileStorage._saveImmediately();
profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
creditCards = await profileStorage.creditCards.getAll();
Assert.equal(creditCards.length, 1);
Assert.equal(await profileStorage.creditCards.get(guid), null);
});
add_task(async function test_getDuplicateRecords() {
let profileStorage = await initProfileStorage(
TEST_STORE_FILE_NAME,
[TEST_CREDIT_CARD_3],
"creditCards"
);
let guid = profileStorage.creditCards._data[0].guid;
// Absolutely a duplicate.
let getDuplicateRecords =
profileStorage.creditCards.getDuplicateRecords(TEST_CREDIT_CARD_3);
let dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
// Absolutely not a duplicate.
getDuplicateRecords =
profileStorage.creditCards.getDuplicateRecords(TEST_CREDIT_CARD_1);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe, null);
// Subset with the same number is a duplicate.
let record = Object.assign({}, TEST_CREDIT_CARD_3);
delete record["cc-exp-month"];
getDuplicateRecords = profileStorage.creditCards.getDuplicateRecords(record);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
// Superset with the same number is a duplicate.
record = Object.assign({}, TEST_CREDIT_CARD_3);
record["cc-name"] = "John Doe";
getDuplicateRecords = profileStorage.creditCards.getDuplicateRecords(record);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
// Numbers with the same last 4 digits shouldn't be treated as a duplicate.
record = Object.assign({}, TEST_CREDIT_CARD_3);
let last4Digits = record["cc-number"].substr(-4);
getDuplicateRecords = profileStorage.creditCards.getDuplicateRecords(record);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
// This number differs from TEST_CREDIT_CARD_3 by swapping the order of the
// 09 and 90 adjacent digits, which is still a valid credit card number.
record["cc-number"] = "358999378390" + last4Digits;
// We don't treat numbers with the same last 4 digits as a duplicate.
getDuplicateRecords = profileStorage.creditCards.getDuplicateRecords(record);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe, null);
});
add_task(async function test_getDuplicateRecordsMatch() {
let profileStorage = await initProfileStorage(
TEST_STORE_FILE_NAME,
[TEST_CREDIT_CARD_2],
"creditCards"
);
let guid = profileStorage.creditCards._data[0].guid;
// Absolutely a duplicate.
let getDuplicateRecords =
profileStorage.creditCards.getDuplicateRecords(TEST_CREDIT_CARD_2);
let dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
// Absolutely not a duplicate.
getDuplicateRecords =
profileStorage.creditCards.getDuplicateRecords(TEST_CREDIT_CARD_1);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe, null);
record = Object.assign({}, TEST_CREDIT_CARD_2);
// We change month from `1` to `2`
record["cc-exp-month"] = 2;
getDuplicateRecords = profileStorage.creditCards.getDuplicateRecords(record);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
// We change year from `2000` to `2001`
record["cc-exp-year"] = 2001;
getDuplicateRecords = profileStorage.creditCards.getDuplicateRecords(record);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
// New name, same card
record["cc-name"] = "John Doe";
getDuplicateRecords = profileStorage.creditCards.getDuplicateRecords(record);
dupe = (await getDuplicateRecords.next()).value;
Assert.equal(dupe.guid, guid);
});
add_task(async function test_getMatchRecord() {
let profileStorage = await initProfileStorage(
TEST_STORE_FILE_NAME,
[TEST_CREDIT_CARD_2],
"creditCards"
);
let guid = profileStorage.creditCards._data[0].guid;
const TEST_FIELDS = {
"cc-name": "John Doe",
"cc-exp-month": 10,
"cc-exp-year": 2001,
};
// Absolutely a match.
let getMatchRecords =
profileStorage.creditCards.getMatchRecords(TEST_CREDIT_CARD_2);
let match = (await getMatchRecords.next()).value;
Assert.equal(match.guid, guid);
// Subset with the same number is a match.
for (const field of Object.keys(TEST_FIELDS)) {
let record = Object.assign({}, TEST_CREDIT_CARD_2);
delete record[field];
getMatchRecords = profileStorage.creditCards.getMatchRecords(record);
match = (await getMatchRecords.next()).value;
Assert.equal(match.guid, guid);
}
// Subset with different number is not a match.
for (const field of Object.keys(TEST_FIELDS)) {
let record = Object.assign({}, TEST_CREDIT_CARD_2, {
"cc-number": TEST_CREDIT_CARD_1["cc-number"],
});
delete record[field];
getMatchRecords = profileStorage.creditCards.getMatchRecords(record);
match = (await getMatchRecords.next()).value;
Assert.equal(match, null);
}
// Superset with the same number is not a match.
for (const [field, value] of Object.entries(TEST_FIELDS)) {
let record = Object.assign({}, TEST_CREDIT_CARD_2);
record[field] = value;
getMatchRecords = profileStorage.creditCards.getMatchRecords(record);
match = (await getMatchRecords.next()).value;
Assert.equal(match, null);
}
// Superset with different number is not a match.
for (const [field, value] of Object.entries(TEST_FIELDS)) {
let record = Object.assign({}, TEST_CREDIT_CARD_2, {
"cc-number": TEST_CREDIT_CARD_1["cc-number"],
});
record[field] = value;
getMatchRecords = profileStorage.creditCards.getMatchRecords(record);
match = (await getMatchRecords.next()).value;
Assert.equal(match, null);
}
});
add_task(async function test_creditCardFillDisabled() {
Services.prefs.setBoolPref(
"extensions.formautofill.creditCards.enabled",
false
);
let path = getTempFile(TEST_STORE_FILE_NAME).path;
let profileStorage = new FormAutofillStorage(path);
await profileStorage.initialize();
Assert.equal(
!!profileStorage.creditCards,
true,
"credit card records initialized and available."
);
Services.prefs.setBoolPref(
"extensions.formautofill.creditCards.enabled",
true
);
});