* refactor(schemas): share identifier and canonical base64 patterns
* test: hold the offset datetime schema to the validator it replaces
The module documents that its accepted set matches z.string().datetime({ offset: true }), and nothing in the suite checked that claim. Adds table-driven accept and reject cases (leap years including the 1900/2000 century pair, month, day, hour and offset ranges, fractional and omitted seconds, missing offset, basic format) plus a differential assertion that classifies every case exactly as the replaced validator does, and asserts the emitted pattern compiles under u and v while the replaced pattern does not compile under v.
---------
Co-authored-by: felipeatp <felipe.troncosopt@gmail.com>
76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
import { expect, it } from "vitest";
|
|
|
|
import {
|
|
CANONICAL_BASE64_PATTERN,
|
|
IDENTIFIER_PATTERN,
|
|
STABLE_IDENTIFIER_PATTERN,
|
|
} from "./stringPatterns.js";
|
|
|
|
it("preserves identifier grammars under Unicode and Unicode-set compilation", () => {
|
|
const variants = [
|
|
{ pattern: IDENTIFIER_PATTERN, previous: /^[A-Za-z][A-Za-z0-9._-]*$/u },
|
|
{
|
|
pattern: STABLE_IDENTIFIER_PATTERN,
|
|
previous: /^[A-Za-z][A-Za-z0-9._:/-]*$/u,
|
|
},
|
|
];
|
|
const samples = [
|
|
"",
|
|
"a",
|
|
"A",
|
|
"_id",
|
|
"1id",
|
|
"é",
|
|
"aé",
|
|
"a/b:c",
|
|
"a-b.c_1",
|
|
"a\n",
|
|
"a\\b",
|
|
];
|
|
for (let code = 0; code < 128; code += 1) {
|
|
const character = String.fromCharCode(code);
|
|
samples.push(character, `A${character}`, `A${character}B`);
|
|
}
|
|
for (const { pattern, previous } of variants) {
|
|
for (const flag of ["u", "v"]) {
|
|
const projected = new RegExp(pattern.source, flag);
|
|
for (const sample of samples)
|
|
expect(projected.test(sample), JSON.stringify({ flag, sample })).toBe(
|
|
previous.test(sample),
|
|
);
|
|
}
|
|
}
|
|
});
|
|
|
|
it("preserves canonical base64 padding and pad-bit validation", () => {
|
|
const previous =
|
|
/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/][AQgw]==|[A-Za-z0-9+/]{2}[AEIMQUYcgkosw048]=)?$/;
|
|
const samples = [
|
|
"",
|
|
"AA==",
|
|
"AB==",
|
|
"AAA=",
|
|
"AAB=",
|
|
"////",
|
|
"A",
|
|
"AA",
|
|
"AAA",
|
|
"====",
|
|
"AA==\n",
|
|
"AA-_",
|
|
"a b c",
|
|
];
|
|
const alphabet =
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
for (const first of alphabet)
|
|
for (const second of alphabet)
|
|
samples.push(`${first}${second}==`, `A${first}${second}=`);
|
|
for (const flag of ["u", "v"]) {
|
|
const projected = new RegExp(CANONICAL_BASE64_PATTERN.source, flag);
|
|
for (const sample of samples)
|
|
expect(projected.test(sample), JSON.stringify({ flag, sample })).toBe(
|
|
previous.test(sample),
|
|
);
|
|
}
|
|
});
|