1+ import * as fs from "node:fs" ;
2+ import * as path from "node:path" ;
3+ import * as crypto from "node:crypto" ;
4+ import jsonStringify from "json-stringify-deterministic" ;
5+ import { normalize } from "./normalize.js" ;
6+ import { loadRemotes } from "./load-remotes.js" ;
7+
8+ const DIALECT_MAP = {
9+ "https://json-schema.org/draft/2020-12/schema" : "https://json-schema.org/draft/2020-12/schema" ,
10+ "https://json-schema.org/draft/2019-09/schema" : "https://json-schema.org/draft/2019-09/schema" ,
11+ "http://json-schema.org/draft-07/schema#" : "http://json-schema.org/draft-07/schema#" ,
12+ "http://json-schema.org/draft-06/schema#" : "http://json-schema.org/draft-06/schema#" ,
13+ "http://json-schema.org/draft-04/schema#" : "http://json-schema.org/draft-04/schema#"
14+ } ;
15+
16+ function * jsonFiles ( dir ) {
17+ for ( const entry of fs . readdirSync ( dir , { withFileTypes : true } ) ) {
18+ const full = path . join ( dir , entry . name ) ;
19+ if ( entry . isDirectory ( ) ) {
20+ yield * jsonFiles ( full ) ;
21+ } else if ( entry . isFile ( ) && entry . name . endsWith ( ".json" ) ) {
22+ yield full ;
23+ }
24+ }
25+ }
26+
27+ function getDialectUri ( schema ) {
28+ if ( schema . $schema && DIALECT_MAP [ schema . $schema ] ) {
29+ return DIALECT_MAP [ schema . $schema ] ;
30+ }
31+ return "https://json-schema.org/draft/2020-12/schema" ;
32+ }
33+
34+ function generateTestId ( normalizedSchema , testData , testValid ) {
35+ return crypto
36+ . createHash ( "md5" )
37+ . update ( jsonStringify ( normalizedSchema ) + jsonStringify ( testData ) + testValid )
38+ . digest ( "hex" ) ;
39+ }
40+
41+ async function checkVersion ( dir ) {
42+ const missingIdFiles = new Set ( ) ;
43+ const duplicateIdFiles = new Set ( ) ;
44+ const mismatchedIdFiles = new Set ( ) ;
45+ const idMap = new Map ( ) ;
46+
47+ console . log ( `Checking tests in ${ dir } ...` ) ;
48+
49+ for ( const file of jsonFiles ( dir ) ) {
50+ const tests = JSON . parse ( fs . readFileSync ( file , "utf8" ) ) ;
51+
52+ for ( let i = 0 ; i < tests . length ; i ++ ) {
53+ const testCase = tests [ i ] ;
54+ if ( ! Array . isArray ( testCase . tests ) ) continue ;
55+
56+ const dialectUri = getDialectUri ( testCase . schema || { } ) ;
57+ const normalizedSchema = await normalize ( testCase . schema || true , dialectUri ) ;
58+
59+ for ( let j = 0 ; j < testCase . tests . length ; j ++ ) {
60+ const test = testCase . tests [ j ] ;
61+
62+ if ( ! test . id ) {
63+ missingIdFiles . add ( file ) ;
64+ console . log ( ` ✗ Missing ID: ${ file } | ${ testCase . description } | ${ test . description } ` ) ;
65+ continue ;
66+ }
67+
68+ const expectedId = generateTestId ( normalizedSchema , test . data , test . valid ) ;
69+
70+ if ( test . id !== expectedId ) {
71+ mismatchedIdFiles . add ( file ) ;
72+ console . log ( ` ✗ Mismatched ID: ${ file } ` ) ;
73+ console . log ( ` Test: ${ testCase . description } | ${ test . description } ` ) ;
74+ console . log ( ` Current ID: ${ test . id } ` ) ;
75+ console . log ( ` Expected ID: ${ expectedId } ` ) ;
76+ }
77+
78+ if ( idMap . has ( test . id ) ) {
79+ const existing = idMap . get ( test . id ) ;
80+ duplicateIdFiles . add ( file ) ;
81+ duplicateIdFiles . add ( existing . file ) ;
82+ console . log ( ` ✗ Duplicate ID: ${ test . id } ` ) ;
83+ console . log ( ` First: ${ existing . file } | ${ existing . testCase } | ${ existing . test } ` ) ;
84+ console . log ( ` Second: ${ file } | ${ testCase . description } | ${ test . description } ` ) ;
85+ } else {
86+ idMap . set ( test . id , {
87+ file,
88+ testCase : testCase . description ,
89+ test : test . description
90+ } ) ;
91+ }
92+ }
93+ }
94+ }
95+
96+ console . log ( "\n" + "=" . repeat ( 60 ) ) ;
97+ console . log ( "Summary:" ) ;
98+ console . log ( "=" . repeat ( 60 ) ) ;
99+
100+ console . log ( "\nFiles with missing IDs:" ) ;
101+ if ( missingIdFiles . size === 0 ) {
102+ console . log ( " ✓ None" ) ;
103+ } else {
104+ for ( const f of missingIdFiles ) console . log ( ` - ${ f } ` ) ;
105+ }
106+
107+ console . log ( "\nFiles with mismatched IDs:" ) ;
108+ if ( mismatchedIdFiles . size === 0 ) {
109+ console . log ( " ✓ None" ) ;
110+ } else {
111+ for ( const f of mismatchedIdFiles ) console . log ( ` - ${ f } ` ) ;
112+ }
113+
114+ console . log ( "\nFiles with duplicate IDs:" ) ;
115+ if ( duplicateIdFiles . size === 0 ) {
116+ console . log ( " ✓ None" ) ;
117+ } else {
118+ for ( const f of duplicateIdFiles ) console . log ( ` - ${ f } ` ) ;
119+ }
120+
121+ const hasErrors = missingIdFiles . size > 0 || mismatchedIdFiles . size > 0 || duplicateIdFiles . size > 0 ;
122+
123+ console . log ( "\n" + "=" . repeat ( 60 ) ) ;
124+ if ( hasErrors ) {
125+ console . log ( "❌ Check failed - issues found" ) ;
126+ process . exit ( 1 ) ;
127+ } else {
128+ console . log ( "✅ All checks passed!" ) ;
129+ }
130+ }
131+
132+ // Load remotes
133+ const remotesPaths = [ "./remotes" ] ;
134+ for ( const dialectUri of Object . values ( DIALECT_MAP ) ) {
135+ for ( const path of remotesPaths ) {
136+ if ( fs . existsSync ( path ) ) {
137+ loadRemotes ( dialectUri , path ) ;
138+ }
139+ }
140+ }
141+
142+ const dir = process . argv [ 2 ] || "tests/draft2020-12" ;
143+ checkVersion ( dir ) . catch ( console . error ) ;
0 commit comments