zod
Version:
TypeScript-first schema declaration and validation library with static type inference
82 lines (70 loc) • 3.32 kB
text/typescript
import { describe, expect, test } from "vitest";
import * as z from "zod";
describe("z.iban", () => {
const valid: [string, string][] = [
["Norway (15, minimum)", "NO9386011117947"],
["Belgium (16)", "BE68539007547034"],
["Netherlands (18)", "NL91ABNA0417164300"],
["Germany (22)", "DE89370400440532013000"],
["Great Britain (22)", "GB29NWBK60161331926819"],
["Turkey (26)", "TR610000100000000000000001"],
["France (27)", "FR1420041010050500013M02606"],
["Cyprus (28)", "CY17002001280000001200527600"],
["Malta (31)", "MT84MALT011000012345MTLCAST001S"],
["Maximum length (34)", "AA73AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"],
["Maximum length mixed (34)", "ZZ560123456789ABCDEFGHIJKLMNOPQRST"],
];
test.each(valid)("accepts %s", (_label, n) => {
expect(z.iban().parse(n)).toBe(n);
});
test("rejects lowercase characters", () => {
expect(() => z.iban().parse("no9386011117947")).toThrow();
expect(() => z.iban().parse("de89370400440532013000")).toThrow();
expect(() => z.iban().parse("FR1420041010050500013m02606")).toThrow();
});
test("rejects spaces and hyphens", () => {
expect(() => z.iban().parse("NO93 8601 1117 947")).toThrow();
expect(() => z.iban().parse(" DE89370400440532013000")).toThrow();
expect(() => z.iban().parse("DE89370400440532013000 ")).toThrow();
expect(() => z.iban().parse("NO93-8601-1117-947")).toThrow();
expect(() => z.iban().parse("DE89-3704-0044-0532-0130-00")).toThrow();
});
test("rejects special characters", () => {
expect(() => z.iban().parse("NO938601111794!")).toThrow();
expect(() => z.iban().parse("DE8937040044053201300@")).toThrow();
expect(() => z.iban().parse("")).toThrow();
});
test("rejects inputs outside length bounds (15-34)", () => {
expect(() => z.iban().parse("NO938601111794")).toThrow();
expect(() => z.iban().parse("AA73AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")).toThrow();
});
test("rejects invalid check digits (checksum failure)", () => {
expect(() => z.iban().parse("NO9386011117948")).toThrow();
expect(() => z.iban().parse("DE89370400440532013001")).toThrow();
expect(() => z.iban().parse("FR1420041010050500013M02607")).toThrow();
});
test("rejects check digits outside 02-98", () => {
// mod 97 alone accepts all three; `98 - remainder` can only ever emit 02-98
expect(() => z.iban().parse("DE00000000000000000066")).toThrow();
expect(() => z.iban().parse("DE01000000000000000048")).toThrow();
expect(() => z.iban().parse("DE99000000000000000030")).toThrow();
});
test("carries the shape regex as its JSON Schema pattern", () => {
expect(z.toJSONSchema(z.iban())).toMatchObject({
format: "iban",
pattern: z.regexes.iban.source,
});
});
test("round-trips through JSON Schema with the checksum intact", () => {
const schema = z.fromJSONSchema(z.toJSONSchema(z.iban()));
expect(schema.safeParse("DE89370400440532013000").success).toBe(true);
expect(schema.safeParse("DE89370400440532013001").success).toBe(false);
});
test("error message references IBAN", () => {
const result = z.iban().safeParse("not-an-iban");
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0]?.message).toMatch(/IBAN/i);
}
});
});