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