@@ -2,20 +2,13 @@ import { readdir, readFile } from "node:fs/promises";
2
2
import YAML from "yaml" ;
3
3
import { join } from "node:path" ;
4
4
import { argv } from "node:process" ;
5
- import "@hyperjump/json-schema/draft-2020-12" ;
5
+ import { validate } from "@hyperjump/json-schema/draft-2020-12" ;
6
6
import "@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" ;
15
8
16
9
/**
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 "
19
12
*/
20
13
21
14
import contentTypeParser from "content-type" ;
@@ -36,6 +29,41 @@ addMediaTypePlugin("application/schema+yaml", {
36
29
fileMatcher : ( path ) => path . endsWith ( ".yaml" ) ,
37
30
} ) ;
38
31
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
+
39
67
/** @type (testDirectory: string) => AsyncGenerator<[string,Json]> */
40
68
const tests = async function * ( testDirectory ) {
41
69
for ( const file of await readdir ( testDirectory , {
@@ -53,70 +81,43 @@ const tests = async function* (testDirectory) {
53
81
}
54
82
} ;
55
83
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
+ */
60
90
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
+ } ) ;
62
101
63
102
if ( ! result . valid ) {
64
103
console . log ( "Failed:" , name , result . errors ) ;
65
104
}
66
105
}
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
- }
86
106
87
- return locations ;
107
+ return {
108
+ allLocations : testCoveragePlugin . allLocations ?? new Set ( ) ,
109
+ visitedLocations : testCoveragePlugin . visitedLocations
110
+ } ;
88
111
} ;
89
112
90
113
///////////////////////////////////////////////////////////////////////////////
91
114
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 (
116
117
( location ) => ! visitedLocations . has ( location ) ,
117
118
) ;
118
119
if ( notCovered . length > 0 ) {
119
- console . log ( "NOT Covered:" , notCovered . length , "of" , allKeywords . length ) ;
120
+ console . log ( "NOT Covered:" , notCovered . length , "of" , allLocations . length ) ;
120
121
const maxNotCovered = 20 ;
121
122
const firstNotCovered = notCovered . slice ( 0 , maxNotCovered ) ;
122
123
if ( notCovered . length > maxNotCovered ) firstNotCovered . push ( "..." ) ;
@@ -127,6 +128,6 @@ console.log(
127
128
"Covered:" ,
128
129
visitedLocations . size ,
129
130
"of" ,
130
- allKeywords . length ,
131
- "(" + Math . floor ( ( visitedLocations . size / allKeywords . length ) * 100 ) + "%)" ,
131
+ allLocations . length ,
132
+ "(" + Math . floor ( ( visitedLocations . size / allLocations . length ) * 100 ) + "%)" ,
132
133
) ;
0 commit comments