|
| 1 | +import fs from "node:fs"; |
| 2 | +import { describe, it, expect, beforeAll, afterAll } from "vitest"; |
| 3 | +import { toAbsoluteIri } from "@hyperjump/uri"; |
| 4 | +import { registerSchema, unregisterSchema, validate } from "./index.js"; |
| 5 | + |
| 6 | +import type { Json } from "@hyperjump/json-pointer"; |
| 7 | +import type { OasSchema32, Validator } from "./index.js"; |
| 8 | + |
| 9 | + |
| 10 | +type Suite = { |
| 11 | + description: string; |
| 12 | + schema: OasSchema32; |
| 13 | + tests: Test[]; |
| 14 | +}; |
| 15 | + |
| 16 | +type Test = { |
| 17 | + description: string; |
| 18 | + data: Json; |
| 19 | + valid: boolean; |
| 20 | +}; |
| 21 | + |
| 22 | +// This package is indended to be a compatibility mode from stable JSON Schema. |
| 23 | +// Some edge cases might not work exactly as specified, but it should work for |
| 24 | +// any real-life schema. |
| 25 | +const skip = new Set<string>([ |
| 26 | + // Self-identifying with a `file:` URI is not allowed for security reasons. |
| 27 | + "|draft2020-12|ref.json|$id with file URI still resolves pointers - *nix", |
| 28 | + "|draft2020-12|ref.json|$id with file URI still resolves pointers - windows" |
| 29 | +]); |
| 30 | + |
| 31 | +const shouldSkip = (path: string[]): boolean => { |
| 32 | + let key = ""; |
| 33 | + for (const segment of path) { |
| 34 | + key = `${key}|${segment}`; |
| 35 | + if (skip.has(key)) { |
| 36 | + return true; |
| 37 | + } |
| 38 | + } |
| 39 | + return false; |
| 40 | +}; |
| 41 | + |
| 42 | +const testSuitePath = "./node_modules/json-schema-test-suite"; |
| 43 | + |
| 44 | +const addRemotes = (dialectId: string, filePath = `${testSuitePath}/remotes`, url = "") => { |
| 45 | + fs.readdirSync(filePath, { withFileTypes: true }) |
| 46 | + .forEach((entry) => { |
| 47 | + if (entry.isFile() && entry.name.endsWith(".json")) { |
| 48 | + const remote = JSON.parse(fs.readFileSync(`${filePath}/${entry.name}`, "utf8")) as Exclude<OasSchema32, boolean>; |
| 49 | + if (!remote.$schema || toAbsoluteIri(remote.$schema as string) === "https://json-schema.org/draft/2020-12/schema") { |
| 50 | + registerSchema(remote, `http://localhost:1234${url}/${entry.name}`, dialectId); |
| 51 | + } |
| 52 | + } else if (entry.isDirectory()) { |
| 53 | + addRemotes(dialectId, `${filePath}/${entry.name}`, `${url}/${entry.name}`); |
| 54 | + } |
| 55 | + }); |
| 56 | +}; |
| 57 | + |
| 58 | +const runTestSuite = (draft: string, dialectId: string) => { |
| 59 | + const testSuiteFilePath = `${testSuitePath}/tests/${draft}`; |
| 60 | + |
| 61 | + describe(`${draft} ${dialectId}`, () => { |
| 62 | + beforeAll(() => { |
| 63 | + addRemotes(dialectId); |
| 64 | + }); |
| 65 | + |
| 66 | + fs.readdirSync(testSuiteFilePath, { withFileTypes: true }) |
| 67 | + .filter((entry) => entry.isFile() && entry.name.endsWith(".json")) |
| 68 | + .forEach((entry) => { |
| 69 | + const file = `${testSuiteFilePath}/${entry.name}`; |
| 70 | + |
| 71 | + describe(entry.name, () => { |
| 72 | + const suites = JSON.parse(fs.readFileSync(file, "utf8")) as Suite[]; |
| 73 | + |
| 74 | + suites.forEach((suite) => { |
| 75 | + describe(suite.description, () => { |
| 76 | + let _validate: Validator; |
| 77 | + let url: string; |
| 78 | + |
| 79 | + beforeAll(async () => { |
| 80 | + if (shouldSkip([draft, entry.name, suite.description])) { |
| 81 | + return; |
| 82 | + } |
| 83 | + url = `http://${draft}-test-suite.json-schema.org/${entry.name}/${encodeURIComponent(suite.description)}`; |
| 84 | + if (typeof suite.schema === "object" && suite.schema.$schema === "https://json-schema.org/draft/2020-12/schema") { |
| 85 | + delete suite.schema.$schema; |
| 86 | + } |
| 87 | + registerSchema(suite.schema, url, dialectId); |
| 88 | + |
| 89 | + _validate = await validate(url); |
| 90 | + }); |
| 91 | + |
| 92 | + afterAll(() => { |
| 93 | + unregisterSchema(url); |
| 94 | + }); |
| 95 | + |
| 96 | + suite.tests.forEach((test) => { |
| 97 | + if (shouldSkip([draft, entry.name, suite.description, test.description])) { |
| 98 | + it.skip(test.description, () => { /* empty */ }); |
| 99 | + } else { |
| 100 | + it(test.description, () => { |
| 101 | + const output = _validate(test.data); |
| 102 | + expect(output.valid).to.equal(test.valid); |
| 103 | + }); |
| 104 | + } |
| 105 | + }); |
| 106 | + }); |
| 107 | + }); |
| 108 | + }); |
| 109 | + }); |
| 110 | + }); |
| 111 | +}; |
| 112 | + |
| 113 | +runTestSuite("draft2020-12", "https://spec.openapis.org/oas/3.2/dialect/base"); |
0 commit comments