use clap::Args;
use serde::Serialize;
use homeboy::module::ModuleRunner;
use homeboy::utils::command::CapturedOutput;
use super::CmdResult;
#[derive(Args)]
pub struct TestArgs {
component: String,
#[arg(long)]
skip_lint: bool,
#[arg(long, value_parser = parse_key_val)]
setting: Vec<(String, String)>,
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
args: Vec<String>,
#[arg(long, hide = true)]
json: bool,
}
#[derive(Serialize)]
pub struct TestOutput {
status: String,
component: String,
#[serde(flatten)]
output: CapturedOutput,
exit_code: i32,
#[serde(skip_serializing_if = "Option::is_none")]
hints: Option<Vec<String>>,
}
fn parse_key_val(s: &str) -> Result<(String, String), String> {
let pos = s
.find('=')
.ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
Ok((s[..pos].to_string(), s[pos + 1..].to_string()))
}
pub fn run_json(args: TestArgs) -> CmdResult<TestOutput> {
let output = ModuleRunner::new(&args.component, "test-runner.sh")
.settings(&args.setting)
.env_if(args.skip_lint, "HOMEBOY_SKIP_LINT", "1")
.script_args(&args.args)
.run()?;
let status = if output.success { "passed" } else { "failed" };
let mut hints = Vec::new();
if !output.success && args.args.is_empty() {
hints.push(format!(
"To run specific tests: homeboy test {} -- --filter=TestName",
args.component
));
}
if args.args.is_empty() {
hints.push("Pass args to test runner: homeboy test <component> -- [args]".to_string());
}
hints.push("Full options: homeboy docs commands/test".to_string());
let hints = if hints.is_empty() { None } else { Some(hints) };
Ok((
TestOutput {
status: status.to_string(),
component: args.component,
output: output.output,
exit_code: output.exit_code,
hints,
},
output.exit_code,
))
}