use std::{path::Path, process::Command};
mod test_env;
const MINIMAL_CONFIG: &str = r#"
[shell]
type = "bash"
[shell.environment]
[shell.alias]
"#;
fn run_json(home: &Path, args: &[&str]) -> Vec<serde_json::Value> {
let mut cmd = Command::new(env!("CARGO_BIN_EXE_zenops"));
cmd.env("HOME", home).env_remove("NO_COLOR");
cmd.arg("-o").arg("json");
for a in args {
cmd.arg(a);
}
let output = cmd.output().expect("spawn zenops");
assert!(
output.status.success(),
"zenops {args:?} exited {}; stderr:\n{}",
output.status,
String::from_utf8_lossy(&output.stderr),
);
String::from_utf8(output.stdout)
.expect("stdout is utf-8")
.lines()
.filter_map(|line| serde_json::from_str(line).ok())
.collect()
}
#[test]
fn doctor_emits_doctor_check_ndjson_to_stdout() {
let env = test_env::TestEnv::load();
let home = env.resolve_path(test_env::paths::HOME_DIR);
let lines = run_json(&home, &["doctor"]);
assert!(!lines.is_empty(), "doctor should emit at least one event");
for line in &lines {
assert_eq!(
line["event"], "doctor_check",
"every doctor line should be doctor_check, got: {line}",
);
assert_eq!(line["kind"], "check", "got: {line}");
}
let has_os = lines
.iter()
.any(|l| l["section"] == "system" && l["label"] == "os:" && l["severity"] == "info");
assert!(has_os, "expected a system/os: info row, got: {lines:?}");
}
#[test]
fn pkg_emits_pkg_entry_ndjson_to_stdout() {
let env = test_env::TestEnv::load();
env.init_config(MINIMAL_CONFIG);
let home = env.resolve_path(test_env::paths::HOME_DIR);
let lines = run_json(&home, &["pkg"]);
let mut saw_pkg_row = false;
for line in &lines {
assert_eq!(line["event"], "pkg_entry", "got: {line}");
if line["kind"] == "pkg" {
saw_pkg_row = true;
assert!(line["state"].is_string(), "got: {line}");
}
}
assert!(
saw_pkg_row,
"expected at least one pkg_entry/pkg row, got: {lines:?}",
);
}
#[test]
fn status_emits_status_ndjson_to_stdout() {
let env = test_env::TestEnv::load();
env.init_config(MINIMAL_CONFIG);
let home = env.resolve_path(test_env::paths::HOME_DIR);
let lines = run_json(&home, &["status"]);
assert!(!lines.is_empty(), "status should emit at least one event");
for line in &lines {
assert_eq!(
line["event"], "status",
"every status line should be a status event, got: {line}",
);
}
}
#[test]
fn init_emits_init_summary_ndjson_to_stdout() {
let env = test_env::TestEnv::load();
let bare = env.seed_bare_repo(&[("config.toml", MINIMAL_CONFIG)]);
let home = env.resolve_path(test_env::paths::HOME_DIR);
let lines = run_json(&home, &["init", bare.to_str().unwrap()]);
let summaries: Vec<&serde_json::Value> = lines
.iter()
.filter(|l| l["event"] == "init_summary")
.collect();
assert_eq!(
summaries.len(),
1,
"expected exactly one init_summary, got: {lines:?}",
);
let s = summaries[0];
assert!(s["clone_path"].is_string(), "got: {s}");
assert!(s["pkg_count"].as_u64().is_some_and(|n| n > 0), "got: {s}");
}
#[test]
fn apply_emits_applied_action_ndjson_to_stdout() {
let env = test_env::TestEnv::load();
env.init_config(MINIMAL_CONFIG);
let home = env.resolve_path(test_env::paths::HOME_DIR);
let lines = run_json(&home, &["apply", "--yes"]);
let mut saw_action = false;
for line in &lines {
let event = line["event"].as_str().unwrap_or("");
assert!(
event == "status" || event == "applied_action",
"apply should only emit status/applied_action events, got: {line}",
);
if event == "applied_action" {
saw_action = true;
}
}
assert!(
saw_action,
"minimal-config apply should emit at least one applied_action, got: {lines:?}",
);
}