Skip to content

Commit ed50aef

Browse files
committed
Add alternate version of script and action
1 parent 61f1940 commit ed50aef

7 files changed

Lines changed: 187 additions & 78 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Test IDs
2+
3+
on: [pull_request]
4+
5+
jobs:
6+
generate:
7+
runs-on: ubuntu-latest
8+
9+
permissions:
10+
contents: write
11+
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
with:
16+
ref: ${{ github.head_ref }} # Check out the PR branch, not the merge commit
17+
18+
- name: Setup Node.js
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: latest
22+
23+
- name: Install dependencies
24+
run: npm ci
25+
26+
- name: Generate test IDs for v1
27+
run: node scripts/generate-ids-for.js v1
28+
29+
- name: Generate test IDs for draft2020-12
30+
run: node scripts/generate-ids-for.js draft2020-12
31+
32+
- name: Generate test IDs for draft2019-09
33+
run: node scripts/generate-ids-for.js draft2019-09
34+
35+
- name: Generate test IDs for draft7
36+
run: node scripts/generate-ids-for.js draft7
37+
38+
- name: Generate test IDs for draft6
39+
run: node scripts/generate-ids-for.js draft6
40+
41+
- name: Generate test IDs for draft4
42+
run: node scripts/generate-ids-for.js draft4
43+
44+
- name: Commit and push changes
45+
run: |
46+
git config user.name "test-id-action"
47+
git config user.email "test-id-action@users.noreply.github.com"
48+
git add "tests/**/*.json"
49+
git commit -m "Update test IDs based on the schema, test data, and expected result." || echo "No changes to commit"
50+
git push

scripts/add-test-ids.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import * as fs from "node:fs";
22

33
import { parse, modify, applyEdits } from "jsonc-parser";
4-
import { normalize } from "./normalize.js";
5-
import { loadRemotes } from "./load-remotes.js";
6-
import generateTestId from "./utils/generateTestIds.js";
4+
import { loadRemotes, generateTestId, normalizeSchema } from "./utils/test-ids.js";
75

86

97
const DIALECT_MAP = {
@@ -27,7 +25,7 @@ async function addIdsToFile(filePath, dialectUri) {
2725

2826
for (let i = 0; i < tests.length; i++) {
2927
const testCase = tests[i];
30-
const normalizedSchema = await normalize(testCase.schema, dialectUri);
28+
const normalizedSchema = await normalizeSchema(testCase.schema, dialectUri);
3129

3230
for (let j = 0; j < testCase.tests.length; j++) {
3331
const test = testCase.tests[j];

scripts/check-test-ids.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import * as fs from "node:fs";
22
import * as path from "node:path";
3-
import { normalize } from "./normalize.js";
4-
import { loadRemotes } from "./load-remotes.js";
5-
import generateTestId from "./utils/generateTestIds.js";
3+
import { loadRemotes, generateTestId, normalizeSchema } from "./utils/test-ids.js";
64
import jsonFiles from "./utils/jsonfiles.js";
75

86

@@ -47,7 +45,7 @@ async function checkVersion(dir) {
4745
const testCases = JSON.parse(fs.readFileSync(file, "utf8"));
4846

4947
for (const testCase of testCases) {
50-
const normalizedSchema = await normalize(testCase.schema, dialectUri);
48+
const normalizedSchema = await normalizeSchema(testCase.schema, dialectUri);
5149

5250
for (const test of testCase.tests) {
5351
if (!test.id) {

scripts/generate-ids-for.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { generateIdsFor } from "./utils/test-ids.js";
2+
3+
const version = process.argv[2];
4+
if (!version) {
5+
console.error("Usage: node scripts/generate-ids-for.js <draftXXXX>");
6+
process.exit(1);
7+
}
8+
9+
await generateIdsFor(version);

scripts/load-remotes.js

Lines changed: 0 additions & 35 deletions
This file was deleted.

scripts/utils/generateTestIds.js

Lines changed: 0 additions & 13 deletions
This file was deleted.
Lines changed: 124 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,108 @@
1-
import * as Schema from "@hyperjump/browser";
1+
import * as crypto from "node:crypto";
2+
import * as fs from "node:fs/promises";
3+
import * as path from "node:path";
4+
import jsonStringify from "json-stringify-deterministic";
5+
import { applyEdits, modify } from "jsonc-parser";
6+
27
import * as Pact from "@hyperjump/pact";
38
import * as JsonPointer from "@hyperjump/json-pointer";
4-
import { toAbsoluteIri } from "@hyperjump/uri";
5-
import { registerSchema, unregisterSchema } from "@hyperjump/json-schema/draft-2020-12";
6-
import { getSchema, getKeywordId } from "@hyperjump/json-schema/experimental";
9+
import * as Schema from "@hyperjump/browser";
10+
import { registerSchema, unregisterSchema } from "@hyperjump/json-schema";
11+
import { getSchema, getKeywordId, getKeywordName } from "@hyperjump/json-schema/experimental";
12+
import "@hyperjump/json-schema/draft-2020-12";
713
import "@hyperjump/json-schema/draft-2019-09";
814
import "@hyperjump/json-schema/draft-07";
915
import "@hyperjump/json-schema/draft-06";
1016
import "@hyperjump/json-schema/draft-04";
1117

18+
const DIALECT_MAP = {
19+
"v1": "https://json-schema.org/v1",
20+
"draft2020-12": "https://json-schema.org/draft/2020-12/schema",
21+
"draft2019-09": "https://json-schema.org/draft/2019-09/schema",
22+
"draft7": "http://json-schema.org/draft-07/schema",
23+
"draft6": "http://json-schema.org/draft-06/schema",
24+
"draft4": "http://json-schema.org/draft-04/schema",
25+
"draft3": "http://json-schema.org/draft-03/schema"
26+
};
27+
28+
export const generateIdsFor = async (version) => {
29+
const dialectUri = DIALECT_MAP[version];
30+
31+
const registeredSchemas = await loadRemotes(version);
32+
33+
for (const entry of await fs.readdir(`./tests/${version}`, { recursive: true, withFileTypes: true })) {
34+
if (!entry.isFile() || path.extname(entry.name) !== ".json") {
35+
continue;
36+
}
37+
38+
const edits = [];
39+
const filePath = path.resolve(entry.parentPath, entry.name);
40+
const json = await fs.readFile(filePath, "utf8")
41+
const suite = JSON.parse(json);
42+
43+
for (let testCaseIndex = 0; testCaseIndex < suite.length; testCaseIndex++) {
44+
const testCase = suite[testCaseIndex];
45+
46+
try {
47+
const normalizedSchema = await normalizeSchema(testCase.schema, dialectUri);
48+
49+
for (let testIndex = 0; testIndex < testCase.tests.length; testIndex++) {
50+
const test = testCase.tests[testIndex];
51+
const id = generateTestId(normalizedSchema, test.data, test.valid);
52+
53+
edits.push(...modify(json, [testCaseIndex, "tests", testIndex, "id"], id, {
54+
formattingOptions: {
55+
insertSpaces: true,
56+
tabSize: 4
57+
}
58+
}));
59+
}
60+
} catch (error) {
61+
console.log(`Failed to generate an ID for ${version} ${entry.name}: ${testCase.description}`);
62+
// console.log(error);
63+
}
64+
}
65+
66+
if (edits.length > 0) {
67+
const updatedJson = applyEdits(json, edits);
68+
await fs.writeFile(filePath, updatedJson);
69+
}
70+
}
71+
72+
for (const remoteUri of registeredSchemas) {
73+
unregisterSchema(remoteUri);
74+
}
75+
};
76+
77+
export const loadRemotes = async (version, filePath = `./remotes`, url = "") => {
78+
const registeredSchemas = [];
1279

13-
const sanitizeTopLevelId = (schema) => {
14-
if (typeof schema !== "object" || schema === null) return schema;
15-
const copy = { ...schema };
16-
if (typeof copy.$id === "string" && copy.$id.startsWith("file:")) {
17-
copy.$id = copy.$id.replace(/^file:/, "x-file:");
80+
for (const entry of await fs.readdir(filePath, { withFileTypes: true })) {
81+
if (entry.isFile() && path.extname(entry.name) === ".json") {
82+
const remote = JSON.parse(await fs.readFile(`${filePath}/${entry.name}`, "utf8"));
83+
const schemaUri = `http://localhost:1234${url}/${entry.name}`;
84+
registerSchema(remote, schemaUri, DIALECT_MAP[version]);
85+
registeredSchemas.push(schemaUri);
86+
} else if (entry.isDirectory() && entry.name === version || !(entry.name in DIALECT_MAP)) {
87+
registeredSchemas.push(...await loadRemotes(version, `${filePath}/${entry.name}`, `${url}/${entry.name}`));
88+
}
1889
}
19-
return copy;
90+
91+
return registeredSchemas;
2092
};
21-
// ===========================================
2293

94+
export const generateTestId = (normalizedSchema, testData, testValid) => {
95+
return crypto
96+
.createHash("md5")
97+
.update(jsonStringify(normalizedSchema) + jsonStringify(testData) + testValid)
98+
.digest("hex");
99+
};
23100

24-
export const normalize = async (rawSchema, dialectUri) => {
101+
export const normalizeSchema = async (rawSchema, dialectUri) => {
25102
const schemaUri = "https://test-suite.json-schema.org/main";
26103

27-
28-
const safeSchema = sanitizeTopLevelId(rawSchema);
29-
30104
try {
31-
105+
const safeSchema = sanitizeTopLevelId(rawSchema, dialectUri);
32106
registerSchema(safeSchema, schemaUri, dialectUri);
33107

34108
const schema = await getSchema(schemaUri);
@@ -40,6 +114,20 @@ export const normalize = async (rawSchema, dialectUri) => {
40114
}
41115
};
42116

117+
const sanitizeTopLevelId = (schema, dialectUri) => {
118+
if (typeof schema !== "object") {
119+
return schema;
120+
}
121+
122+
const idToken = getKeywordName(dialectUri, "https://json-schema.org/keyword/id")
123+
?? getKeywordName(dialectUri, "https://json-schema.org/keyword/draft-04/id");
124+
if (idToken in schema) {
125+
schema[idToken] = schema[idToken].replace(/^file:/, "x-file:");
126+
}
127+
128+
return schema;
129+
};
130+
43131
const compile = async (schema, ast) => {
44132
if (!(schema.document.baseUri in ast.metaData)) {
45133
ast.metaData[schema.document.baseUri] = {
@@ -124,16 +212,13 @@ const keywordHandlers = {
124212
"https://json-schema.org/keyword/dependentSchemas": objectApplicator,
125213
"https://json-schema.org/keyword/deprecated": simpleValue,
126214
"https://json-schema.org/keyword/description": simpleValue,
127-
"https://json-schema.org/keyword/dynamicRef": simpleValue, // base dynamicRef
128-
129-
"https://json-schema.org/keyword/draft-2020-12/dynamicRef": simpleValue,
130-
131-
215+
"https://json-schema.org/keyword/dynamicRef": simpleValue,
132216
"https://json-schema.org/keyword/else": simpleApplicator,
133217
"https://json-schema.org/keyword/enum": simpleValue,
134218
"https://json-schema.org/keyword/examples": simpleValue,
135219
"https://json-schema.org/keyword/exclusiveMaximum": simpleValue,
136220
"https://json-schema.org/keyword/exclusiveMinimum": simpleValue,
221+
"https://json-schema.org/keyword/format": simpleValue,
137222
"https://json-schema.org/keyword/if": simpleApplicator,
138223
"https://json-schema.org/keyword/items": simpleApplicator,
139224
"https://json-schema.org/keyword/maxContains": simpleValue,
@@ -153,6 +238,22 @@ const keywordHandlers = {
153238
"https://json-schema.org/keyword/patternProperties": objectApplicator,
154239
"https://json-schema.org/keyword/prefixItems": arrayApplicator,
155240
"https://json-schema.org/keyword/properties": objectApplicator,
241+
"https://json-schema.org/keyword/propertyDependencies": (keyword, ast) => {
242+
return Pact.pipe(
243+
Schema.entries(keyword),
244+
Pact.asyncMap(async ([propertyName, valueSchemaMap]) => {
245+
return [
246+
propertyName,
247+
await Pact.pipe(
248+
Schema.entries(valueSchemaMap),
249+
Pact.asyncMap(async ([propertyValue, schema]) => [propertyValue, await compile(schema, ast)]),
250+
Pact.asyncCollectObject
251+
)
252+
];
253+
}),
254+
Pact.asyncCollectObject
255+
);
256+
},
156257
"https://json-schema.org/keyword/propertyNames": simpleApplicator,
157258
"https://json-schema.org/keyword/readOnly": simpleValue,
158259
"https://json-schema.org/keyword/ref": compile,
@@ -166,6 +267,7 @@ const keywordHandlers = {
166267
"https://json-schema.org/keyword/unknown": simpleValue,
167268
"https://json-schema.org/keyword/writeOnly": simpleValue,
168269

270+
"https://json-schema.org/keyword/draft-2020-12/dynamicRef": simpleValue,
169271
"https://json-schema.org/keyword/draft-2020-12/format": simpleValue,
170272
"https://json-schema.org/keyword/draft-2020-12/format-assertion": simpleValue,
171273

@@ -200,4 +302,4 @@ const keywordHandlers = {
200302
},
201303
"https://json-schema.org/keyword/draft-04/maximum": simpleValue,
202304
"https://json-schema.org/keyword/draft-04/minimum": simpleValue
203-
};
305+
};

0 commit comments

Comments
 (0)