use crate::core::runner::TestResult;
use colored::Colorize;
use serde_json::Value;
use std::env;
pub struct TestSummaryFormatter {
pub disable_color: bool,
}
impl TestSummaryFormatter {
pub fn new(disable_color: bool) -> Self {
Self { disable_color }
}
pub fn format_compact_results(&self, results: &[TestResult], skipped: usize, total: usize) -> String {
let mut output = String::new();
if !self.disable_color {
output.push_str(&format!("\n{}\n", "━".repeat(get_terminal_width()).blue()));
output.push_str("\nResults:\n");
for result in results {
let status_indicator = if result.success {
"✓".green()
} else {
"✗".red()
};
if result.success {
output.push_str(&format!(" {} {}\n", status_indicator, result.name));
} else {
output.push_str(&format!(
" {} {} (expected {}, got {})\n",
status_indicator,
result.name,
result.expected_status,
result.actual_status.to_string().red()
));
}
}
output.push('\n');
let success_count = results.iter().filter(|r| r.success).count();
let fail_count = results.len() - success_count;
if success_count > 0 {
output.push_str(&format!("{} passed", format!("{success_count} tests").green()));
}
if fail_count > 0 {
if success_count > 0 {
output.push_str(", ");
}
output.push_str(&format!("{} failed", format!("{fail_count} tests").red()));
}
if skipped > 0 {
if success_count > 0 || fail_count > 0 {
output.push_str(", ");
}
output.push_str(&format!("{} skipped", format!("{skipped} tests").yellow()));
}
output.push_str(&format!(" (total: {total})\n"));
} else {
output.push_str("\nResults:\n");
for result in results {
if result.success {
output.push_str(&format!(" ✓ {}\n", result.name));
} else {
output.push_str(&format!(
" ✗ {} (expected {}, got {})\n",
result.name, result.expected_status, result.actual_status
));
}
}
let success_count = results.iter().filter(|r| r.success).count();
let fail_count = results.len() - success_count;
output.push_str(&format!("\n{success_count} tests passed"));
if fail_count > 0 {
output.push_str(&format!(", {fail_count} tests failed"));
}
if skipped > 0 {
output.push_str(&format!(", {skipped} tests skipped"));
}
output.push_str(&format!(" (total: {total})\n"));
}
output
}
pub fn format_failure_details(&self, results: &[TestResult]) -> String {
let failed_results: Vec<_> = results.iter().filter(|r| !r.success).collect();
if failed_results.is_empty() {
return String::new();
}
let mut output = String::new();
if !self.disable_color {
output.push_str(&format!("\n{}\n", "━".repeat(get_terminal_width()).blue()));
output.push_str(&format!("\n{}\n", "Failures:".red().bold()));
} else {
output.push_str("\nFailures:\n");
}
for (i, result) in failed_results.iter().enumerate() {
if i > 0 {
output.push('\n');
}
if !self.disable_color {
output.push_str("---\n");
output.push_str(&format!("Test: {}\n", result.name.bold()));
output.push_str(&format!(
"Endpoint: {} {}\n",
result.method.yellow(),
result.endpoint.yellow()
));
output.push_str(&format!(
"Status: {} (expected {})\n",
result.actual_status.to_string().red(),
result.expected_status.to_string().bold()
));
} else {
output.push_str("---\n");
output.push_str(&format!("Test: {}\n", result.name));
output.push_str(&format!("Endpoint: {} {}\n", result.method, result.endpoint));
output.push_str(&format!("Status: {} (expected {})\n", result.actual_status, result.expected_status));
}
if !result.messages.is_empty() {
output.push_str("Messages:\n");
for msg in &result.messages {
if !self.disable_color {
output.push_str(&format!(" - {}\n", msg.red()));
} else {
output.push_str(&format!(" - {msg}\n"));
}
}
}
if let Some(body) = &result.response_body {
output.push_str("Response Body:\n");
let body_str = self.format_response_body(body);
output.push_str(&format!("{body_str}\n"));
} else {
output.push_str("Response Body: <none>\n");
}
}
output
}
pub const MAX_SUMMARY_BODY_BYTES: usize = 8192;
pub fn format_response_body(&self, body: &Value) -> String {
let formatted = if body.is_object() || body.is_array() {
serde_json::to_string_pretty(body).unwrap_or_else(|_| body.to_string())
} else {
body.to_string()
};
if formatted.len() > Self::MAX_SUMMARY_BODY_BYTES {
let truncated = &formatted[..Self::MAX_SUMMARY_BODY_BYTES];
format!("{truncated}\n... (truncated)")
} else {
formatted
}
}
}
fn get_terminal_width() -> usize {
env::var("COLUMNS")
.ok()
.and_then(|cols| cols.parse().ok())
.unwrap_or(80)
}