use std::process::{Command, Stdio};
fn lifeloop_bin() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_BIN_EXE_lifeloop"))
}
fn run(args: &[&str]) -> (i32, String, String) {
let out = Command::new(lifeloop_bin())
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.output()
.expect("spawn lifeloop");
(
out.status.code().unwrap_or(-1),
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
)
}
#[test]
fn asset_preview_claude_native_hook_lists_settings_target() {
let (code, stdout, stderr) = run(&[
"asset",
"preview",
"--host",
"claude",
"--mode",
"native_hook",
]);
assert_eq!(code, 0, "stderr=`{stderr}`");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("rows JSON");
let rows = v.as_array().unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(
rows[0].get("relative_path").and_then(|s| s.as_str()),
Some(".claude/settings.json")
);
}
#[test]
fn asset_preview_codex_manual_skill_yields_no_applied_assets() {
let (code, stdout, stderr) = run(&[
"asset",
"preview",
"--host",
"codex",
"--mode",
"manual_skill",
]);
assert_eq!(code, 0, "stderr=`{stderr}`");
let v: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(v.as_array().unwrap().len(), 0);
}
#[test]
fn asset_preview_accepts_ccd_renewal_profile() {
let (code, stdout, stderr) = run(&[
"asset",
"preview",
"--host",
"codex",
"--mode",
"native_hook",
"--profile",
"ccd-renewal",
]);
assert_eq!(code, 0, "stderr=`{stderr}`");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("rows JSON");
let rows = v.as_array().unwrap();
let hooks = rows
.iter()
.find(|row| row.get("relative_path").and_then(|s| s.as_str()) == Some(".codex/hooks.json"))
.expect("hooks row");
let contents = hooks["contents"].as_str().expect("contents");
assert!(contents.contains("lifeloop"));
assert!(contents.contains("--client-cmd"));
assert!(contents.contains("CCD_BIN"));
}
#[test]
fn asset_unknown_profile_is_validation_error() {
let (code, _stdout, stderr) = run(&[
"asset",
"preview",
"--host",
"codex",
"--mode",
"native_hook",
"--profile",
"nope",
]);
assert_eq!(code, 1);
assert!(stderr.contains("unknown profile"));
}
#[test]
fn asset_apply_is_rejected_with_pointer_to_preview() {
let (code, _stdout, stderr) = run(&[
"asset",
"apply",
"--host",
"claude",
"--mode",
"native_hook",
]);
assert_eq!(code, 2);
assert!(
stderr.contains("asset preview"),
"rejection should redirect callers to `asset preview`; stderr=`{stderr}`"
);
}
#[test]
fn asset_unknown_host_is_validation_error() {
let (code, _stdout, stderr) = run(&[
"asset",
"preview",
"--host",
"nope",
"--mode",
"native_hook",
]);
assert_eq!(code, 1);
assert!(stderr.contains("unknown host"));
}
#[test]
fn asset_unknown_mode_is_validation_error() {
let (code, _stdout, stderr) = run(&[
"asset",
"preview",
"--host",
"claude",
"--mode",
"weird_mode",
]);
assert_eq!(code, 1);
assert!(stderr.contains("unknown mode"));
}
#[test]
fn asset_unsupported_combination_is_validation_error() {
let (code, _stdout, stderr) = run(&[
"asset",
"preview",
"--host",
"claude",
"--mode",
"manual_skill",
]);
assert_eq!(code, 1);
assert!(stderr.contains("does not support"));
}
#[test]
fn asset_missing_host_flag_is_usage_error() {
let (code, _stdout, stderr) = run(&["asset", "preview", "--mode", "native_hook"]);
assert_eq!(code, 2);
assert!(stderr.contains("--host"));
}