@@ -13,7 +13,9 @@ import {
1313import {
1414 listScenarios ,
1515 listClientScenarios ,
16- listActiveClientScenarios
16+ listActiveClientScenarios ,
17+ listAuthScenarios ,
18+ listMetadataScenarios
1719} from './scenarios' ;
1820import { ConformanceCheck } from './types' ;
1921import { ClientOptionsSchema , ServerOptionsSchema } from './schemas' ;
@@ -33,34 +35,141 @@ program
3335 'Run conformance tests against a client implementation or start interactive mode'
3436 )
3537 . option ( '--command <command>' , 'Command to run the client' )
36- . requiredOption ( '--scenario <scenario>' , 'Scenario to test' )
38+ . option ( '--scenario <scenario>' , 'Scenario to test' )
39+ . option ( '--suite <suite>' , 'Run a suite of tests in parallel (e.g., "auth")' )
3740 . option ( '--timeout <ms>' , 'Timeout in milliseconds' , '30000' )
3841 . option ( '--verbose' , 'Show verbose output' )
3942 . action ( async ( options ) => {
4043 try {
41- // Validate options with Zod
44+ const timeout = parseInt ( options . timeout , 10 ) ;
45+ const verbose = options . verbose ?? false ;
46+
47+ // Handle suite mode
48+ if ( options . suite ) {
49+ if ( ! options . command ) {
50+ console . error ( '--command is required when using --suite' ) ;
51+ process . exit ( 1 ) ;
52+ }
53+
54+ const suites : Record < string , ( ) => string [ ] > = {
55+ auth : listAuthScenarios ,
56+ metadata : listMetadataScenarios
57+ } ;
58+
59+ const suiteName = options . suite . toLowerCase ( ) ;
60+ if ( ! suites [ suiteName ] ) {
61+ console . error ( `Unknown suite: ${ suiteName } ` ) ;
62+ console . error ( `Available suites: ${ Object . keys ( suites ) . join ( ', ' ) } ` ) ;
63+ process . exit ( 1 ) ;
64+ }
65+
66+ const scenarios = suites [ suiteName ] ( ) ;
67+ console . log (
68+ `Running ${ suiteName } suite (${ scenarios . length } scenarios) in parallel...\n`
69+ ) ;
70+
71+ const results = await Promise . all (
72+ scenarios . map ( async ( scenarioName ) => {
73+ try {
74+ const result = await runConformanceTest (
75+ options . command ,
76+ scenarioName ,
77+ timeout
78+ ) ;
79+ return {
80+ scenario : scenarioName ,
81+ checks : result . checks ,
82+ error : null
83+ } ;
84+ } catch ( error ) {
85+ return {
86+ scenario : scenarioName ,
87+ checks : [
88+ {
89+ id : scenarioName ,
90+ name : scenarioName ,
91+ description : 'Failed to run scenario' ,
92+ status : 'FAILURE' as const ,
93+ timestamp : new Date ( ) . toISOString ( ) ,
94+ errorMessage :
95+ error instanceof Error ? error . message : String ( error )
96+ }
97+ ] ,
98+ error
99+ } ;
100+ }
101+ } )
102+ ) ;
103+
104+ console . log ( '\n=== SUITE SUMMARY ===\n' ) ;
105+
106+ let totalPassed = 0 ;
107+ let totalFailed = 0 ;
108+ let totalWarnings = 0 ;
109+
110+ for ( const result of results ) {
111+ const passed = result . checks . filter (
112+ ( c ) => c . status === 'SUCCESS'
113+ ) . length ;
114+ const failed = result . checks . filter (
115+ ( c ) => c . status === 'FAILURE'
116+ ) . length ;
117+ const warnings = result . checks . filter (
118+ ( c ) => c . status === 'WARNING'
119+ ) . length ;
120+
121+ totalPassed += passed ;
122+ totalFailed += failed ;
123+ totalWarnings += warnings ;
124+
125+ const status = failed === 0 ? '✓' : '✗' ;
126+ console . log (
127+ `${ status } ${ result . scenario } : ${ passed } passed, ${ failed } failed`
128+ ) ;
129+
130+ if ( verbose && failed > 0 ) {
131+ result . checks
132+ . filter ( ( c ) => c . status === 'FAILURE' )
133+ . forEach ( ( c ) => {
134+ console . log (
135+ ` - ${ c . name } : ${ c . errorMessage || c . description } `
136+ ) ;
137+ } ) ;
138+ }
139+ }
140+
141+ console . log (
142+ `\nTotal: ${ totalPassed } passed, ${ totalFailed } failed, ${ totalWarnings } warnings`
143+ ) ;
144+ process . exit ( totalFailed > 0 ? 1 : 0 ) ;
145+ }
146+
147+ // Require either --scenario or --suite
148+ if ( ! options . scenario ) {
149+ console . error ( 'Either --scenario or --suite is required' ) ;
150+ console . error ( '\nAvailable client scenarios:' ) ;
151+ listScenarios ( ) . forEach ( ( s ) => console . error ( ` - ${ s } ` ) ) ;
152+ console . error ( '\nAvailable suites: auth, metadata' ) ;
153+ process . exit ( 1 ) ;
154+ }
155+
156+ // Validate options with Zod for single scenario mode
42157 const validated = ClientOptionsSchema . parse ( options ) ;
43158
44159 // If no command provided, run in interactive mode
45160 if ( ! validated . command ) {
46- await runInteractiveMode (
47- validated . scenario ,
48- validated . verbose ?? false
49- ) ;
161+ await runInteractiveMode ( validated . scenario , verbose ) ;
50162 process . exit ( 0 ) ;
51163 }
52164
53165 // Otherwise run conformance test
54166 const result = await runConformanceTest (
55167 validated . command ,
56168 validated . scenario ,
57- validated . timeout ?? 30000
169+ timeout
58170 ) ;
59171
60- const { failed } = printClientResults (
61- result . checks ,
62- validated . verbose ?? false
63- ) ;
172+ const { failed } = printClientResults ( result . checks , verbose ) ;
64173 process . exit ( failed > 0 ? 1 : 0 ) ;
65174 } catch ( error ) {
66175 if ( error instanceof ZodError ) {
0 commit comments