mirror of
https://github.com/bitwarden/browser
synced 2025-12-14 07:13:32 +00:00
287 lines
9.8 KiB
TypeScript
287 lines
9.8 KiB
TypeScript
import {
|
||
normalizeExpiryYearFormat,
|
||
isCardExpired,
|
||
parseYearMonthExpiry,
|
||
} from "@bitwarden/common/autofill/utils";
|
||
import { CardView } from "@bitwarden/common/vault/models/view/card.view";
|
||
|
||
function getExpiryYearValueFormats(currentCentury: string) {
|
||
return [
|
||
[-12, `${currentCentury}12`],
|
||
[0, `${currentCentury}00`],
|
||
[2043, "2043"], // valid year with a length of four should be taken directly
|
||
[24, `${currentCentury}24`],
|
||
[3054, "3054"], // valid year with a length of four should be taken directly
|
||
[31423524543, `${currentCentury}43`],
|
||
[4, `${currentCentury}04`],
|
||
[null, null],
|
||
[undefined, null],
|
||
["-12", `${currentCentury}12`],
|
||
["", null],
|
||
["0", `${currentCentury}00`],
|
||
["00", `${currentCentury}00`],
|
||
["000", `${currentCentury}00`],
|
||
["0000", `${currentCentury}00`],
|
||
["00000", `${currentCentury}00`],
|
||
["0234234", `${currentCentury}34`],
|
||
["04", `${currentCentury}04`],
|
||
["2043", "2043"], // valid year with a length of four should be taken directly
|
||
["24", `${currentCentury}24`],
|
||
["3054", "3054"], // valid year with a length of four should be taken directly
|
||
["31423524543", `${currentCentury}43`],
|
||
["4", `${currentCentury}04`],
|
||
["aaaa", null],
|
||
["adgshsfhjsdrtyhsrth", null],
|
||
["agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", `${currentCentury}45`],
|
||
];
|
||
}
|
||
|
||
describe("normalizeExpiryYearFormat", () => {
|
||
const currentCentury = `${new Date().getFullYear()}`.slice(0, 2);
|
||
|
||
const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury);
|
||
|
||
expiryYearValueFormats.forEach(([inputValue, expectedValue]) => {
|
||
it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => {
|
||
const formattedValue = normalizeExpiryYearFormat(inputValue);
|
||
|
||
expect(formattedValue).toEqual(expectedValue);
|
||
});
|
||
});
|
||
|
||
describe("in the year 3107", () => {
|
||
const theDistantFuture = new Date(Date.UTC(3107, 1, 1));
|
||
jest.spyOn(Date, "now").mockReturnValue(theDistantFuture.valueOf());
|
||
|
||
beforeAll(() => {
|
||
jest.useFakeTimers({ advanceTimers: true });
|
||
jest.setSystemTime(theDistantFuture);
|
||
});
|
||
|
||
afterAll(() => {
|
||
jest.useRealTimers();
|
||
});
|
||
|
||
const currentCentury = `${new Date(Date.now()).getFullYear()}`.slice(0, 2);
|
||
expect(currentCentury).toBe("31");
|
||
|
||
const expiryYearValueFormats = getExpiryYearValueFormats(currentCentury);
|
||
|
||
expiryYearValueFormats.forEach(([inputValue, expectedValue]) => {
|
||
it(`should return '${expectedValue}' when '${inputValue}' is passed`, () => {
|
||
const formattedValue = normalizeExpiryYearFormat(inputValue);
|
||
|
||
expect(formattedValue).toEqual(expectedValue);
|
||
});
|
||
});
|
||
jest.clearAllTimers();
|
||
});
|
||
});
|
||
|
||
function getCardExpiryDateValues() {
|
||
const currentDate = new Date();
|
||
|
||
const currentYear = currentDate.getFullYear();
|
||
|
||
// `Date` months are zero-indexed, our expiry date month inputs are one-indexed
|
||
const currentMonth = currentDate.getMonth() + 1;
|
||
|
||
const currentDateLastMonth = new Date(currentDate.setMonth(-1));
|
||
|
||
return [
|
||
[null, null, false], // no month, no year
|
||
[undefined, undefined, false], // no month, no year, invalid values
|
||
["", "", false], // no month, no year, invalid values
|
||
["12", "agdredg42grg35grrr. ea3534@#^145345ag$%^ -_#$rdg ", false], // invalid values
|
||
["0", `${currentYear}`, true], // invalid month
|
||
["0", `${currentYear - 1}`, true], // invalid 0 month
|
||
["00", `${currentYear + 1}`, false], // invalid 0 month
|
||
[`${currentMonth}`, "0000", true], // current month, in the year 2000
|
||
[null, `${currentYear}`.slice(-2), false], // no month, this year
|
||
[null, `${currentYear - 1}`.slice(-2), true], // no month, last year
|
||
["1", null, false], // no year, January
|
||
["1", `${currentYear - 1}`, true], // January last year
|
||
["13", `${currentYear}`, false], // 12 + 1 is Feb. in the next year (Date is zero-indexed)
|
||
[`${currentMonth + 36}`, `${currentYear - 1}`, true], // even though the month value would put the date 3 years into the future when calculated with `Date`, an explicit year in the past indicates the card is expired
|
||
[`${currentMonth}`, `${currentYear}`, false], // this year, this month (not expired until the month is over)
|
||
[`${currentMonth}`, `${currentYear}`.slice(-2), false], // This month, this year (not expired until the month is over)
|
||
[`${currentDateLastMonth.getMonth() + 1}`, `${currentDateLastMonth.getFullYear()}`, true], // last month
|
||
[`${currentMonth - 1}`, `${currentYear + 1}`, false], // 11 months from now
|
||
];
|
||
}
|
||
|
||
describe("isCardExpired", () => {
|
||
const expiryYearValueFormats = getCardExpiryDateValues();
|
||
|
||
expiryYearValueFormats.forEach(
|
||
([inputMonth, inputYear, expectedValue]: [string | null, string | null, boolean]) => {
|
||
it(`should return ${expectedValue} when the card expiry month is ${inputMonth} and the card expiry year is ${inputYear}`, () => {
|
||
const testCardView = new CardView();
|
||
testCardView.expMonth = inputMonth;
|
||
testCardView.expYear = inputYear;
|
||
|
||
const cardIsExpired = isCardExpired(testCardView);
|
||
|
||
expect(cardIsExpired).toBe(expectedValue);
|
||
});
|
||
},
|
||
);
|
||
});
|
||
|
||
const combinedDateTestValues = [
|
||
" 2024 / 05 ",
|
||
"05 2024",
|
||
"05 2024", // Tab whitespace character
|
||
"05 2024", // Em Quad
|
||
"05 2024", // Em Space
|
||
"05 2024", // En Quad
|
||
"05 2024", // En Space
|
||
"05 2024", // Figure Space
|
||
"05 2024", // Four-Per-Em Space
|
||
"05 2024", // Hair Space
|
||
"05 2024", // Ideographic Space
|
||
"05 2024", // Medium Mathematical Space
|
||
"05 2024", // No-Break Space
|
||
"05 2024", // ogham space mark
|
||
"05 2024", // Punctuation Space
|
||
"05 2024", // Six-Per-Em Space
|
||
"05 2024", // Thin Space
|
||
"05 2024", // Three-Per-Em Space
|
||
"05 24",
|
||
"05-2024",
|
||
"05-24",
|
||
"05.2024",
|
||
"05.24",
|
||
"05/2024",
|
||
"05/24",
|
||
"052024",
|
||
"0524",
|
||
"2024 05",
|
||
"2024 5",
|
||
"2024-05",
|
||
"2024-5",
|
||
"2024.05",
|
||
"2024.5",
|
||
"2024/05",
|
||
"2024/5",
|
||
"202405",
|
||
"20245",
|
||
"24 05",
|
||
"24 5",
|
||
"24-05",
|
||
"24-5",
|
||
"24.05",
|
||
"24.5",
|
||
"24/05",
|
||
"24/5",
|
||
"2405",
|
||
"5 2024",
|
||
"5 24",
|
||
"5-2024",
|
||
"5-24",
|
||
"5.2024",
|
||
"5.24",
|
||
"5/2024",
|
||
"5/24",
|
||
"52024",
|
||
];
|
||
const expectedParsedValue = ["2024", "5"];
|
||
describe("parseYearMonthExpiry", () => {
|
||
it('returns "null" expiration year and month values when a value of "" is passed', () => {
|
||
expect(parseYearMonthExpiry("")).toStrictEqual([null, null]);
|
||
});
|
||
|
||
it('returns "null" expiration year and month values when a value of "/" is passed', () => {
|
||
expect(parseYearMonthExpiry("/")).toStrictEqual([null, null]);
|
||
});
|
||
|
||
combinedDateTestValues.forEach((combinedDate) => {
|
||
it(`returns an expiration year value of "${expectedParsedValue[0]}" and month value of "${expectedParsedValue[1]}" when a value of "${combinedDate}" is passed`, () => {
|
||
expect(parseYearMonthExpiry(combinedDate)).toStrictEqual(expectedParsedValue);
|
||
});
|
||
});
|
||
|
||
it('returns an expiration year value of "2002" and month value of "2" when a value of "022" is passed', () => {
|
||
expect(parseYearMonthExpiry("022")).toStrictEqual(["2002", "2"]);
|
||
});
|
||
|
||
it('returns an expiration year value of "2002" and month value of "2" when a value of "202" is passed', () => {
|
||
expect(parseYearMonthExpiry("202")).toStrictEqual(["2002", "2"]);
|
||
});
|
||
|
||
it('returns an expiration year value of "2002" and month value of "1" when a value of "1/2/3/4" is passed', () => {
|
||
expect(parseYearMonthExpiry("1/2/3/4")).toStrictEqual(["2002", "1"]);
|
||
});
|
||
|
||
it('returns valid expiration year and month values when a value of "198" is passed', () => {
|
||
// This static value will cause the test to fail in 2098
|
||
const testValue = "198";
|
||
const parsedValue = parseYearMonthExpiry(testValue);
|
||
|
||
expect(parsedValue[0]).toHaveLength(4);
|
||
expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/);
|
||
|
||
expect(parsedValue).toStrictEqual(["2098", "1"]);
|
||
});
|
||
|
||
// Ambiguous input cases: we use try/catch for these cases as a workaround to accept either
|
||
// outcome (both are valid interpretations) in the event of any future code changes.
|
||
describe("ambiguous input cases", () => {
|
||
it('returns valid expiration year and month values when a value of "111" is passed', () => {
|
||
const testValue = "111";
|
||
const parsedValue = parseYearMonthExpiry(testValue);
|
||
|
||
expect(parsedValue[0]).toHaveLength(4);
|
||
expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/);
|
||
|
||
try {
|
||
expect(parsedValue).toStrictEqual(["2011", "1"]);
|
||
} catch {
|
||
expect(parsedValue).toStrictEqual(["2001", "11"]);
|
||
}
|
||
});
|
||
|
||
it('returns valid expiration year and month values when a value of "212" is passed', () => {
|
||
const testValue = "212";
|
||
const parsedValue = parseYearMonthExpiry(testValue);
|
||
|
||
expect(parsedValue[0]).toHaveLength(4);
|
||
expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/);
|
||
|
||
try {
|
||
expect(parsedValue).toStrictEqual(["2012", "2"]);
|
||
} catch {
|
||
expect(parsedValue).toStrictEqual(["2021", "2"]);
|
||
}
|
||
});
|
||
|
||
it('returns valid expiration year and month values when a value of "245" is passed', () => {
|
||
const testValue = "245";
|
||
const parsedValue = parseYearMonthExpiry(testValue);
|
||
|
||
expect(parsedValue[0]).toHaveLength(4);
|
||
expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/);
|
||
|
||
try {
|
||
expect(parsedValue).toStrictEqual(["2045", "2"]);
|
||
} catch {
|
||
expect(parsedValue).toStrictEqual(["2024", "5"]);
|
||
}
|
||
});
|
||
|
||
it('returns valid expiration year and month values when a value of "524" is passed', () => {
|
||
const testValue = "524";
|
||
const parsedValue = parseYearMonthExpiry(testValue);
|
||
|
||
expect(parsedValue[0]).toHaveLength(4);
|
||
expect(parsedValue[1]).toMatch(/^[\d]{1,2}$/);
|
||
|
||
try {
|
||
expect(parsedValue).toStrictEqual(["2024", "5"]);
|
||
} catch {
|
||
expect(parsedValue).toStrictEqual(["2052", "4"]);
|
||
}
|
||
});
|
||
});
|
||
});
|