|
| 1 | +import { readFile } from "node:fs/promises"; |
| 2 | +import { describe, test, expect } from "vitest"; |
| 3 | +import { isDate, isDateTime, isDuration, isTime } from "./rfc3339.js"; |
| 4 | +import { isRegex } from "./ecma262.js"; |
| 5 | +import { isEmail } from "./rfc5321.js"; |
| 6 | +import { isHostname } from "./rfc1123.js"; |
| 7 | +import { isAsciiIdn, isIdn } from "./idna2008.js"; |
| 8 | +import { isIdnEmail } from "./rfc6531.js"; |
| 9 | +import { isIPv4 } from "./rfc2673.js"; |
| 10 | +import { isIPv6 } from "./rfc4291.js"; |
| 11 | +import { isIriReference } from "./rfc3987.js"; |
| 12 | +import { isJsonPointer } from "./rfc6901.js"; |
| 13 | +import { isRelativeJsonPointer } from "./draft-bhutton-relative-json-pointer-00.js"; |
| 14 | +import { isUri, isUriReference } from "./rfc3986.js"; |
| 15 | +import { isUriTemplate } from "./rfc6570.js"; |
| 16 | +import { isUuid } from "./rfc4122.js"; |
| 17 | + |
| 18 | +/** |
| 19 | + * @typedef {{ |
| 20 | + * description: string; |
| 21 | + * data: unknown; |
| 22 | + * valid: boolean; |
| 23 | + * }} Test |
| 24 | + */ |
| 25 | + |
| 26 | +/** |
| 27 | + * @typedef {{ |
| 28 | + * description: string; |
| 29 | + * tests: Test[]; |
| 30 | + * }} TestCase |
| 31 | + */ |
| 32 | + |
| 33 | +/** |
| 34 | + * @typedef {TestCase[]} TestSuite |
| 35 | + */ |
| 36 | + |
| 37 | +/** @type (format: string, fn: (value: any) => boolean, skip?: Record<string, Set<string>>) => Promise<void> */ |
| 38 | +export const testSuite = async (format, fn, skip = {}) => { |
| 39 | + const testSuiteJson = await readFile(`./node_modules/json-schema-test-suite/tests/${format}.json`, "utf8"); |
| 40 | + /** @type TestSuite */ |
| 41 | + const testSuite = JSON.parse(testSuiteJson); // eslint-disable-line @typescript-eslint/no-unsafe-assignment |
| 42 | + |
| 43 | + for (const testCase of testSuite) { |
| 44 | + describe(testCase.description, () => { |
| 45 | + for (const formatTest of testCase.tests) { |
| 46 | + if (skip[testCase.description]?.has(formatTest.description)) { |
| 47 | + continue; |
| 48 | + } |
| 49 | + |
| 50 | + test(formatTest.description, () => { |
| 51 | + expect(fn(formatTest.data)).to.equal(formatTest.valid); |
| 52 | + }); |
| 53 | + } |
| 54 | + }); |
| 55 | + } |
| 56 | +}; |
| 57 | + |
| 58 | +await testSuite("draft2020-12/optional/format/date-time", (dateTime) => typeof dateTime !== "string" || isDateTime(dateTime)); |
| 59 | +await testSuite("draft2020-12/optional/format/date", (date) => typeof date !== "string" || isDate(date)); |
| 60 | +await testSuite("draft2020-12/optional/format/duration", (duration) => typeof duration !== "string" || isDuration(duration)); |
| 61 | +await testSuite("draft2020-12/optional/format/ecmascript-regex", (pattern) => typeof pattern !== "string" || isRegex(pattern)); |
| 62 | +await testSuite("draft2020-12/optional/format/email", (email) => typeof email !== "string" || isEmail(email)); |
| 63 | +await testSuite("draft6/optional/format/hostname", (hostname) => typeof hostname !== "string" || isHostname(hostname)); |
| 64 | +await testSuite("draft2020-12/optional/format/hostname", (hostname) => typeof hostname !== "string" || isAsciiIdn(hostname)); |
| 65 | +await testSuite("draft2020-12/optional/format/idn-email", (email) => typeof email !== "string" || isIdnEmail(email)); |
| 66 | +await testSuite("draft2020-12/optional/format/idn-hostname", (hostname) => typeof hostname !== "string" || isIdn(hostname), { |
| 67 | + "validation of internationalized host names": new Set([ |
| 68 | + "contains illegal char U+302E Hangul single dot tone mark", |
| 69 | + "Exceptions that are DISALLOWED, right-to-left chars", |
| 70 | + "Exceptions that are DISALLOWED, left-to-right chars", |
| 71 | + "MIDDLE DOT with no preceding 'l'", |
| 72 | + "MIDDLE DOT with nothing preceding", |
| 73 | + "MIDDLE DOT with no following 'l'", |
| 74 | + "MIDDLE DOT with nothing following", |
| 75 | + "Greek KERAIA not followed by Greek", |
| 76 | + "Greek KERAIA not followed by anything", |
| 77 | + "Hebrew GERESH not preceded by anything", |
| 78 | + "Hebrew GERSHAYIM not preceded by anything", |
| 79 | + "KATAKANA MIDDLE DOT with no Hiragana, Katakana, or Han", |
| 80 | + "KATAKANA MIDDLE DOT with no other characters" |
| 81 | + ]) |
| 82 | +}); |
| 83 | +await testSuite("draft2020-12/optional/format/ipv4", (ip) => typeof ip !== "string" || isIPv4(ip)); |
| 84 | +await testSuite("draft2020-12/optional/format/ipv6", (ip) => typeof ip !== "string" || isIPv6(ip)); |
| 85 | +await testSuite("draft2020-12/optional/format/iri-reference", (iri) => typeof iri !== "string" || isIriReference(iri)); |
| 86 | +await testSuite("draft2020-12/optional/format/json-pointer", (pointer) => typeof pointer !== "string" || isJsonPointer(pointer)); |
| 87 | +await testSuite("draft2020-12/optional/format/regex", (pattern) => typeof pattern !== "string" || isRegex(pattern)); |
| 88 | +await testSuite("draft2020-12/optional/format/relative-json-pointer", (pointer) => typeof pointer !== "string" || isRelativeJsonPointer(pointer)); |
| 89 | +await testSuite("draft2020-12/optional/format/time", (time) => typeof time !== "string" || isTime(time), { |
| 90 | + "validation of time strings": new Set([ |
| 91 | + "a valid time string with leap second, Zulu", |
| 92 | + "valid leap second, zero time-offset", |
| 93 | + "valid leap second, positive time-offset", |
| 94 | + "valid leap second, large positive time-offset", |
| 95 | + "valid leap second, negative time-offset", |
| 96 | + "valid leap second, large negative time-offset" |
| 97 | + ]) |
| 98 | +}); |
| 99 | +await testSuite("draft2020-12/optional/format/uri-reference", (uri) => typeof uri !== "string" || isUriReference(uri)); |
| 100 | +await testSuite("draft2020-12/optional/format/uri-template", (uri) => typeof uri !== "string" || isUriTemplate(uri)); |
| 101 | +await testSuite("draft2020-12/optional/format/uri", (uri) => typeof uri !== "string" || isUri(uri)); |
| 102 | +await testSuite("draft2020-12/optional/format/uuid", (uuid) => typeof uuid !== "string" || isUuid(uuid)); |
0 commit comments