@@ -25,6 +25,8 @@ pub struct TestResult {
2525 pub response_body : Option < Value > ,
2626 pub headers : HashMap < String , String > ,
2727 pub messages : Vec < String > ,
28+ pub method : String ,
29+ pub endpoint : String ,
2830}
2931
3032struct TestExecutionContext < ' a > {
@@ -40,6 +42,7 @@ pub struct TestRunner {
4042 pub variables : HashMap < String , String > ,
4143 pub results : Vec < TestResult > ,
4244 pub disable_color : bool ,
45+ pub no_fail_summary : bool ,
4346}
4447
4548impl TestRunner {
@@ -49,6 +52,7 @@ impl TestRunner {
4952 variables : HashMap :: new ( ) ,
5053 results : Vec :: new ( ) ,
5154 disable_color,
55+ no_fail_summary : false ,
5256 }
5357 }
5458
@@ -73,6 +77,8 @@ impl TestRunner {
7377 response_body : result. body ,
7478 headers : result. headers ,
7579 messages : result. errors ,
80+ method : test. method . clone ( ) ,
81+ endpoint : test. endpoint . clone ( ) ,
7682 }
7783 }
7884
@@ -109,6 +115,8 @@ impl TestRunner {
109115 response_body : None ,
110116 headers : HashMap :: new ( ) ,
111117 messages : error_messages. clone ( ) ,
118+ method : test. method . clone ( ) ,
119+ endpoint : test. endpoint . clone ( ) ,
112120 }
113121 } else {
114122 self . execute_test ( test, client, test_file_dir) . await
@@ -149,8 +157,10 @@ impl TestRunner {
149157 verbose : bool ,
150158 file : Option < String > ,
151159 var : Option < String > ,
160+ no_fail_summary : bool ,
152161 ) {
153162 load_env_files ( ) ;
163+ self . no_fail_summary = no_fail_summary;
154164
155165 // Parse CLI variables and add them to the runner's variables
156166 let cli_variables = crate :: cli:: Commands :: parse_variables ( var) ;
@@ -339,16 +349,45 @@ impl TestRunner {
339349 self . results . push ( result) ;
340350 }
341351
342- self . display_summary ( * skipped, context. total ) ;
352+ self . display_compact_results ( * skipped, context. total , context. verbose ) ;
353+ if !self . no_fail_summary && !context. verbose {
354+ self . display_failure_details ( ) ;
355+ }
343356 }
344357
345- fn display_summary ( & self , skipped : usize , total : usize ) {
358+ fn display_compact_results ( & self , skipped : usize , total : usize , verbose : bool ) {
359+ // Only display compact results in non-verbose mode
360+ if verbose {
361+ return ;
362+ }
363+
346364 if !self . disable_color {
347365 println ! ( "\n {}" , "━" . repeat( get_terminal_width( ) ) . blue( ) ) ;
366+ println ! ( "\n Results:" ) ;
367+
368+ for result in & self . results {
369+ let status_indicator = if result. success {
370+ "✓" . green ( )
371+ } else {
372+ "✗" . red ( )
373+ } ;
374+
375+ if result. success {
376+ println ! ( " {} {}" , status_indicator, result. name) ;
377+ } else {
378+ println ! ( " {} {} (expected {}, got {})" ,
379+ status_indicator,
380+ result. name,
381+ result. expected_status,
382+ result. actual_status. to_string( ) . red( )
383+ ) ;
384+ }
385+ }
386+
387+ println ! ( ) ;
348388 let success_count = self . results . iter ( ) . filter ( |r| r. success ) . count ( ) ;
349389 let fail_count = self . results . len ( ) - success_count;
350390
351- println ! ( "\n Summary:" ) ;
352391 if success_count > 0 {
353392 print ! ( "{} passed" , format!( "{success_count} tests" ) . green( ) ) ;
354393 }
@@ -365,6 +404,103 @@ impl TestRunner {
365404 print ! ( "{} skipped" , format!( "{skipped} tests" ) . yellow( ) ) ;
366405 }
367406 println ! ( " (total: {total})" ) ;
407+ } else {
408+ println ! ( "\n Results:" ) ;
409+ for result in & self . results {
410+ if result. success {
411+ println ! ( " ✓ {}" , result. name) ;
412+ } else {
413+ println ! ( " ✗ {} (expected {}, got {})" ,
414+ result. name, result. expected_status, result. actual_status) ;
415+ }
416+ }
417+
418+ let success_count = self . results . iter ( ) . filter ( |r| r. success ) . count ( ) ;
419+ let fail_count = self . results . len ( ) - success_count;
420+
421+ print ! ( "\n {success_count} tests passed" ) ;
422+ if fail_count > 0 {
423+ print ! ( ", {fail_count} tests failed" ) ;
424+ }
425+ if skipped > 0 {
426+ print ! ( ", {skipped} tests skipped" ) ;
427+ }
428+ println ! ( " (total: {total})" ) ;
429+ }
430+ }
431+
432+ fn display_failure_details ( & self ) {
433+ let failed_results: Vec < _ > = self . results . iter ( ) . filter ( |r| !r. success ) . collect ( ) ;
434+
435+ if failed_results. is_empty ( ) {
436+ return ;
437+ }
438+
439+ if !self . disable_color {
440+ println ! ( "\n {}" , "━" . repeat( get_terminal_width( ) ) . blue( ) ) ;
441+ println ! ( "\n {}" , "Failures:" . red( ) . bold( ) ) ;
442+ } else {
443+ println ! ( "\n Failures:" ) ;
444+ }
445+
446+ for ( i, result) in failed_results. iter ( ) . enumerate ( ) {
447+ if i > 0 {
448+ println ! ( ) ;
449+ }
450+
451+ if !self . disable_color {
452+ println ! ( "---" ) ;
453+ println ! ( "Test: {}" , result. name. bold( ) ) ;
454+ println ! ( "Endpoint: {} {}" ,
455+ result. method. yellow( ) ,
456+ result. endpoint. yellow( )
457+ ) ;
458+ println ! ( "Status: {} (expected {})" ,
459+ result. actual_status. to_string( ) . red( ) ,
460+ result. expected_status. to_string( ) . bold( )
461+ ) ;
462+ } else {
463+ println ! ( "---" ) ;
464+ println ! ( "Test: {}" , result. name) ;
465+ println ! ( "Endpoint: {} {}" , result. method, result. endpoint) ;
466+ println ! ( "Status: {} (expected {})" , result. actual_status, result. expected_status) ;
467+ }
468+
469+ if !result. messages . is_empty ( ) {
470+ println ! ( "Messages:" ) ;
471+ for msg in & result. messages {
472+ if !self . disable_color {
473+ println ! ( " - {}" , msg. red( ) ) ;
474+ } else {
475+ println ! ( " - {msg}" ) ;
476+ }
477+ }
478+ }
479+
480+ if let Some ( body) = & result. response_body {
481+ println ! ( "Response Body:" ) ;
482+ let body_str = self . format_response_body ( body) ;
483+ println ! ( "{body_str}" ) ;
484+ } else {
485+ println ! ( "Response Body: <none>" ) ;
486+ }
487+ }
488+ }
489+
490+ pub const MAX_SUMMARY_BODY_BYTES : usize = 8192 ;
491+
492+ pub fn format_response_body ( & self , body : & Value ) -> String {
493+ let formatted = if body. is_object ( ) || body. is_array ( ) {
494+ serde_json:: to_string_pretty ( body) . unwrap_or_else ( |_| body. to_string ( ) )
495+ } else {
496+ body. to_string ( )
497+ } ;
498+
499+ if formatted. len ( ) > Self :: MAX_SUMMARY_BODY_BYTES {
500+ let truncated = & formatted[ ..Self :: MAX_SUMMARY_BODY_BYTES ] ;
501+ format ! ( "{truncated}\n ... (truncated)" )
502+ } else {
503+ formatted
368504 }
369505 }
370506}
0 commit comments