77 lines
2.9 KiB
Plaintext
77 lines
2.9 KiB
Plaintext
import { describe, expect, test } from "vitest";
|
|
import * as z from "zod";
|
|
|
|
describe("z.creditCard", () => {
|
|
// Luhn-valid PANs. The last four are the schemes an issuer allowlist would turn away — validation is shape plus Luhn only, so the BIN is never consulted.
|
|
const valid: [string, string][] = [
|
|
["Visa", "4111111111111111"],
|
|
["Visa (13)", "4007000000027"],
|
|
["Mastercard", "5555555555554444"],
|
|
["AmEx", "378282246310005"],
|
|
["Discover", "6011111111111117"],
|
|
["JCB", "3530111333300000"],
|
|
["Diners Club", "30569309025904"],
|
|
["UnionPay", "6200000000000005"],
|
|
["Mir", "2200000000000004"],
|
|
["Elo", "6362000000000009"],
|
|
["Discover 644-649", "6450000000000002"],
|
|
["Troy", "9792000000000003"],
|
|
];
|
|
|
|
test.each(valid)("accepts %s", (_label, n) => {
|
|
expect(z.creditCard().parse(n)).toBe(n);
|
|
});
|
|
|
|
test("accepts single space or hyphen separators", () => {
|
|
expect(z.creditCard().parse("4111 1111 1111 1111")).toBe("4111 1111 1111 1111");
|
|
expect(z.creditCard().parse("4111-1111-1111-1111")).toBe("4111-1111-1111-1111");
|
|
});
|
|
|
|
test("rejects other separator shapes", () => {
|
|
for (const n of ["4111 1111 1111 1111", " 4111111111111111", "4111111111111111 ", "4111.1111.1111.1111"]) {
|
|
expect(() => z.creditCard().parse(n)).toThrow();
|
|
}
|
|
});
|
|
|
|
test("accepts the 12- and 19-digit length bounds", () => {
|
|
expect(z.creditCard().parse("400000000002")).toBe("400000000002");
|
|
expect(z.creditCard().parse("6250000000000000005")).toBe("6250000000000000005");
|
|
});
|
|
|
|
test("rejects Luhn-valid input outside 12-19 digits", () => {
|
|
expect(() => z.creditCard().parse("40000000006")).toThrow();
|
|
expect(() => z.creditCard().parse("40000000000000000002")).toThrow();
|
|
});
|
|
|
|
test("rejects when Luhn fails (right shape, wrong checksum)", () => {
|
|
expect(() => z.creditCard().parse("4111111111111112")).toThrow();
|
|
});
|
|
|
|
test("rejects non-digit input", () => {
|
|
expect(() => z.creditCard().parse("not-a-card")).toThrow();
|
|
expect(() => z.creditCard().parse("")).toThrow();
|
|
});
|
|
|
|
test("carries the shape regex as its JSON Schema pattern", () => {
|
|
// Luhn is not expressible as a pattern, so `pattern` covers length and separators only.
|
|
expect(z.toJSONSchema(z.creditCard())).toMatchObject({
|
|
format: "credit_card",
|
|
pattern: z.regexes.creditCard.source,
|
|
});
|
|
});
|
|
|
|
test("round-trips through JSON Schema with the checksum intact", () => {
|
|
const schema = z.fromJSONSchema(z.toJSONSchema(z.creditCard()));
|
|
expect(schema.safeParse("4111111111111111").success).toBe(true);
|
|
expect(schema.safeParse("4111111111111112").success).toBe(false);
|
|
});
|
|
|
|
test("error message references credit card", () => {
|
|
const result = z.creditCard().safeParse("not-a-card");
|
|
expect(result.success).toBe(false);
|
|
if (!result.success) {
|
|
expect(result.error.issues[0]?.message).toMatch(/credit card/i);
|
|
}
|
|
});
|
|
});
|