use colored::Colorize;
use serde_json::Value;
use crate::{
models::{
error::AlixtError,
test_data::{AssertionOutcome, FailureType, TestData},
},
reporting::table::{
BOTTOM_LEFT, BOTTOM_RIGHT, HORIZONTAL, TOP_LEFT, TOP_RIGHT, Table, VERTICAL,
},
};
pub fn generate_text<W: std::io::Write>(
writer: &mut W,
outcome: TestData,
) -> Result<(), AlixtError> {
writeln!(writer, "[TEST RESULTS]")?;
for run in outcome.run_data {
writeln!(writer, "\n[RUN]: '{}'", run.name)?;
for req in run.outcomes {
writeln!(
writer,
"\n[REQUEST]: '{}',\nTarget: '{}',\nPassed: {},\nBreaking: {},\nDuration: {} seconds,",
req.name,
req.url,
req.passing.is_passing(),
req.breaking,
req.duration.as_secs_f64(),
)?;
if let Some(body) = req.response_body {
let body = if let Ok(json) = serde_json::from_str::<Value>(&body) {
serde_json::to_string_pretty(&json).unwrap_or(body.clone())
} else {
body
};
writeln!(writer, "Body = ```\n{}```", body)?;
} else {
writeln!(writer)?;
}
}
}
Ok(())
}
struct TableData {
table: Table<5>,
assertions: Vec<(String, AssertionOutcome)>,
}
pub fn generate_table<W: std::io::Write>(
writer: &mut W,
outcome: TestData,
verbose: bool,
) -> Result<(), AlixtError> {
let mut tables: Vec<TableData> = vec![];
let mut passing: u16 = 0;
let mut failing: u16 = 0;
for mut run in outcome.run_data {
let mut failed = false;
let mut table = TableData {
table: Table::<5>::new()
.title(run.name.blue())
.headers([
"".white(),
"Result".blue(),
"Name".blue(),
"Code".blue(),
"Duration".blue(),
])
.collect()?,
assertions: Vec::new(),
};
for request in &mut run.outcomes {
let passed = if request.passing.is_passing() {
"PASS".green()
} else {
table
.assertions
.push((request.name.clone(), request.passing.take()));
failed = true;
"FAIL".red()
};
let breaking = if request.breaking && !request.passing.is_passing() {
"BREAK".red()
} else {
"".white()
};
let status = if let Some(status) = request.status {
format!("{status}").yellow()
} else {
"".white()
};
table.table.push_row([
breaking,
passed,
request.name.yellow(),
status,
format!("{}s", request.duration.as_secs_f32()).yellow(),
])?;
}
if failed {
failing += 1;
} else {
passing += 1;
}
tables.push(table);
}
let passing_text = format!("{passing}").green();
let failing_text = format!("{failing}").red();
let message = vec![
"All runs finished. ".blue(),
passing_text,
" passing, ".blue(),
failing_text,
" failing.".blue(),
];
let message_len: usize = message.iter().fold(0, |acc, w| acc + w.len());
writeln!(
writer,
"{}{}{}",
TOP_LEFT.blue(),
HORIZONTAL.repeat(message_len).blue(),
TOP_RIGHT.blue(),
)?;
write!(writer, "{}", VERTICAL.blue())?;
for word in &message {
write!(writer, "{word}")?;
}
writeln!(
writer,
"{}\n{}{}{}",
VERTICAL.blue(),
BOTTOM_LEFT.blue(),
HORIZONTAL.repeat(message_len).blue(),
BOTTOM_RIGHT.blue()
)?;
for table in tables {
table.table.render(writer)?;
if !verbose || table.assertions.is_empty() {
continue;
}
for (name, assertion_data) in table.assertions {
let AssertionOutcome::Failed(fails) = assertion_data else {
continue;
};
let mut request_table = Table::<3>::new()
.title(format!("{name} failed assertions").red())
.headers(["reason".blue(), "expected".green(), "found".red()])
.collect()?;
for fail in fails {
match fail {
FailureType::StatusMismatch { expected, found } => {
request_table.push_row([
"StatusMismatch".blue(),
format!("{}", expected).green(),
found.map_or("None".to_string(), |n| n.to_string()).red(),
])?;
}
FailureType::InvalidJson() => {
request_table.push_row([
"InvalidJson".blue(),
"Json".green(),
"Not Json".red(),
])?;
},
FailureType::JsonMissingField { path } => {
request_table.push_row([
"JsonMissingField".blue(),
path.green(),
"None".red(),
])?;
},
FailureType::JsonExtraField { path } => {
request_table.push_row([
"JsonExtraField".blue(),
"None".green(),
path.red(),
])?;
},
FailureType::JsonValueMismatch {
path,
expected,
found,
} => {
request_table.push_row([
"JsonValueMismatch".blue(),
format!("{} = {}", path, expected).green(),
found.red(),
])?;
},
FailureType::JsonRegexMismatch {
path,
pattern,
found,
} => {
request_table.push_row([
"JsonRegexMismatch".blue(),
format!("{} = {}", path, pattern).green(),
found.red(),
])?;
},
FailureType::JsonNotString { path } => {
request_table.push_row([
"JsonNotString".blue(),
format!("{} = <String>", path).green(),
"Not A String".red(),
])?;
},
}
}
request_table.render(writer)?;
}
}
if failing == 0 {
writeln!(
writer,
"\n{}{}{}",
TOP_LEFT.blue(),
HORIZONTAL.repeat(7).blue(),
TOP_RIGHT.blue()
)?;
writeln!(
writer,
"{}{}{}",
VERTICAL.blue(),
"SUCCESS".green(),
VERTICAL.blue()
)?;
writeln!(
writer,
"{}{}{}",
BOTTOM_LEFT.blue(),
HORIZONTAL.repeat(7).blue(),
BOTTOM_RIGHT.blue()
)?;
} else {
writeln!(
writer,
"\n{}{}{}",
TOP_LEFT.yellow(),
HORIZONTAL.repeat(6).yellow(),
TOP_RIGHT.yellow()
)?;
writeln!(
writer,
"{}{}{}",
VERTICAL.yellow(),
"FAILED".red(),
VERTICAL.yellow()
)?;
writeln!(
writer,
"{}{}{}",
BOTTOM_LEFT.yellow(),
HORIZONTAL.repeat(6).yellow(),
BOTTOM_RIGHT.yellow()
)?;
}
Ok(())
}
pub fn generate_json<W: std::io::Write>(
writer: &mut W,
outcome: TestData,
) -> Result<(), AlixtError> {
serde_json::to_writer_pretty(&mut *writer, &outcome)?;
writeln!(writer)?;
Ok(())
}