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 manifest_list_emits_every_registered_adapter() {
let (code, stdout, stderr) = run(&["manifest", "list"]);
assert_eq!(code, 0, "stderr=`{stderr}`");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("list output is JSON");
let arr = v.as_array().expect("array");
let ids: Vec<&str> = arr
.iter()
.map(|e| e.get("adapter_id").and_then(|s| s.as_str()).unwrap())
.collect();
for required in &[
"codex", "claude", "hermes", "openclaw", "gemini", "opencode",
] {
assert!(ids.contains(required), "missing `{required}` in {ids:?}");
}
}
#[test]
fn manifest_show_returns_the_full_manifest_for_codex() {
let (code, stdout, stderr) = run(&["manifest", "show", "codex"]);
assert_eq!(code, 0, "stderr=`{stderr}`");
let v: serde_json::Value = serde_json::from_str(&stdout).expect("manifest is JSON");
assert_eq!(v.get("adapter_id").and_then(|s| s.as_str()), Some("codex"));
assert!(v.get("integration_modes").is_some());
}
#[test]
fn manifest_show_unknown_id_is_validation_error() {
let (code, _stdout, stderr) = run(&["manifest", "show", "nope"]);
assert_eq!(code, 1, "expected validation exit (1), got {code}");
assert!(stderr.contains("unknown adapter_id"));
}
#[test]
fn manifest_inspect_accepts_id_at_known_version() {
let (_, show_stdout, _) = run(&["manifest", "show", "codex"]);
let v: serde_json::Value = serde_json::from_str(&show_stdout).unwrap();
let version = v.get("adapter_version").and_then(|s| s.as_str()).unwrap();
let spec = format!("codex@{version}");
let (code, stdout, stderr) = run(&["manifest", "inspect", &spec]);
assert_eq!(code, 0, "stderr=`{stderr}`");
let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(
parsed.get("adapter_id").and_then(|s| s.as_str()),
Some("codex")
);
}
#[test]
fn manifest_inspect_rejects_wrong_version_with_validation_exit() {
let (code, _stdout, stderr) = run(&["manifest", "inspect", "codex@99.99.99"]);
assert_eq!(code, 1, "expected validation exit (1), got {code}");
assert!(stderr.contains("not `99.99.99`") || stderr.contains("not `99.99.99"));
}
#[test]
fn manifest_inspect_missing_at_separator_is_usage_error() {
let (code, _stdout, stderr) = run(&["manifest", "inspect", "codex"]);
assert_eq!(code, 2, "expected usage exit (2), got {code}");
assert!(stderr.contains("expected <adapter_id>@<version>"));
}
#[test]
fn manifest_unknown_subcommand_is_usage_error() {
let (code, _stdout, stderr) = run(&["manifest", "bogus"]);
assert_eq!(code, 2);
assert!(stderr.contains("unknown subcommand"));
}