@@ -2,20 +2,13 @@ import { readdir, readFile } from "node:fs/promises";
22import YAML from "yaml" ;
33import { join } from "node:path" ;
44import { argv } from "node:process" ;
5- import "@hyperjump/json-schema/draft-2020-12" ;
5+ import { validate } from "@hyperjump/json-schema/draft-2020-12" ;
66import "@hyperjump/json-schema/draft-04" ;
7- import {
8- compile ,
9- getSchema ,
10- interpret ,
11- Validation ,
12- BASIC ,
13- } from "@hyperjump/json-schema/experimental" ;
14- import * as Instance from "@hyperjump/json-schema/instance/experimental" ;
7+ import { BASIC } from "@hyperjump/json-schema/experimental" ;
158
169/**
17- * @import { AST } from "@hyperjump/json-schema/experimental"
18- * @import { Json } from "@hyperjump/json-schema "
10+ * @import { EvaluationPlugin } from "@hyperjump/json-schema/experimental"
11+ * @import { Json } from "@hyperjump/json-pointer "
1912 */
2013
2114import contentTypeParser from "content-type" ;
@@ -36,6 +29,41 @@ addMediaTypePlugin("application/schema+yaml", {
3629 fileMatcher : ( path ) => path . endsWith ( ".yaml" ) ,
3730} ) ;
3831
32+ /** @implements EvaluationPlugin */
33+ class TestCoveragePlugin {
34+ constructor ( ) {
35+ /** @type Set<string> */
36+ this . visitedLocations = new Set ( ) ;
37+ }
38+
39+ beforeSchema ( _schemaUri , _instance , context ) {
40+ if ( this . allLocations ) {
41+ return ;
42+ }
43+
44+ /** @type Set<string> */
45+ this . allLocations = [ ] ;
46+
47+ for ( const schemaLocation in context . ast ) {
48+ if ( schemaLocation === "metaData" ) {
49+ continue ;
50+ }
51+
52+ if ( Array . isArray ( context . ast [ schemaLocation ] ) ) {
53+ for ( const keyword of context . ast [ schemaLocation ] ) {
54+ if ( Array . isArray ( keyword ) ) {
55+ this . allLocations . push ( keyword [ 1 ] ) ;
56+ }
57+ }
58+ }
59+ }
60+ }
61+
62+ beforeKeyword ( [ , schemaUri ] ) {
63+ this . visitedLocations . add ( schemaUri ) ;
64+ }
65+ }
66+
3967/** @type (testDirectory: string) => AsyncGenerator<[string,Json]> */
4068const tests = async function * ( testDirectory ) {
4169 for ( const file of await readdir ( testDirectory , {
@@ -53,70 +81,43 @@ const tests = async function* (testDirectory) {
5381 }
5482} ;
5583
56- /** @type (testDirectory: string) => Promise<void> */
57- const runTests = async ( testDirectory ) => {
58- for await ( const [ name , test ] of tests ( testDirectory ) ) {
59- const instance = Instance . fromJs ( test ) ;
84+ /**
85+ * @typedef {{
86+ * allLocations: string[];
87+ * visitedLocations: Set<string>;
88+ * }} Coverage
89+ */
6090
61- const result = interpret ( compiled , instance , BASIC ) ;
91+ /** @type (schemaUri: string, testDirectory: string) => Promise<Coverage> */
92+ const runTests = async ( schemaUri , testDirectory ) => {
93+ const testCoveragePlugin = new TestCoveragePlugin ( ) ;
94+ const validateOpenApi = await validate ( schemaUri ) ;
95+
96+ for await ( const [ name , test ] of tests ( testDirectory ) ) {
97+ const result = validateOpenApi ( test , {
98+ outputFormat : BASIC ,
99+ plugins : [ testCoveragePlugin ] ,
100+ } ) ;
62101
63102 if ( ! result . valid ) {
64103 console . log ( "Failed:" , name , result . errors ) ;
65104 }
66105 }
67- } ;
68-
69- /** @type (ast: AST) => string[] */
70- const keywordLocations = ( ast ) => {
71- /** @type string[] */
72- const locations = [ ] ;
73- for ( const schemaLocation in ast ) {
74- if ( schemaLocation === "metaData" ) {
75- continue ;
76- }
77-
78- if ( Array . isArray ( ast [ schemaLocation ] ) ) {
79- for ( const keyword of ast [ schemaLocation ] ) {
80- if ( Array . isArray ( keyword ) ) {
81- locations . push ( keyword [ 1 ] ) ;
82- }
83- }
84- }
85- }
86106
87- return locations ;
107+ return {
108+ allLocations : testCoveragePlugin . allLocations ?? new Set ( ) ,
109+ visitedLocations : testCoveragePlugin . visitedLocations
110+ } ;
88111} ;
89112
90113///////////////////////////////////////////////////////////////////////////////
91114
92- const schema = await getSchema ( argv [ 2 ] ) ;
93- const compiled = await compile ( schema ) ;
94-
95- /** @type Set<string> */
96- const visitedLocations = new Set ( ) ;
97- const baseInterpret = Validation . interpret ;
98- Validation . interpret = ( url , instance , context ) => {
99- if ( Array . isArray ( context . ast [ url ] ) ) {
100- for ( const keywordNode of context . ast [ url ] ) {
101- if ( Array . isArray ( keywordNode ) ) {
102- visitedLocations . add ( keywordNode [ 1 ] ) ;
103- }
104- }
105- }
106- return baseInterpret ( url , instance , context ) ;
107- } ;
108-
109- await runTests ( argv [ 3 ] ) ;
110- Validation . interpret = baseInterpret ;
111-
112- // console.log("Covered:", visitedLocations);
113-
114- const allKeywords = keywordLocations ( compiled . ast ) ;
115- const notCovered = allKeywords . filter (
115+ const { allLocations, visitedLocations } = await runTests ( argv [ 2 ] , argv [ 3 ] ) ;
116+ const notCovered = allLocations . filter (
116117 ( location ) => ! visitedLocations . has ( location ) ,
117118) ;
118119if ( notCovered . length > 0 ) {
119- console . log ( "NOT Covered:" , notCovered . length , "of" , allKeywords . length ) ;
120+ console . log ( "NOT Covered:" , notCovered . length , "of" , allLocations . length ) ;
120121 const maxNotCovered = 20 ;
121122 const firstNotCovered = notCovered . slice ( 0 , maxNotCovered ) ;
122123 if ( notCovered . length > maxNotCovered ) firstNotCovered . push ( "..." ) ;
@@ -127,6 +128,6 @@ console.log(
127128 "Covered:" ,
128129 visitedLocations . size ,
129130 "of" ,
130- allKeywords . length ,
131- "(" + Math . floor ( ( visitedLocations . size / allKeywords . length ) * 100 ) + "%)" ,
131+ allLocations . length ,
132+ "(" + Math . floor ( ( visitedLocations . size / allLocations . length ) * 100 ) + "%)" ,
132133) ;
0 commit comments