use std::path::Path;
use std::process::Command;
use octl_core::{append_and_apply_event, ensure_root, NodeId, RunPaths};
use serde_json::{json, Value};
use tempfile::TempDir;
const RUN_ID: &str = "01jxwd0000000000000000000w";
fn bin(home: &TempDir) -> Command {
let mut c = Command::new(env!("CARGO_BIN_EXE_orchestratectl"));
c.env("ORCHESTRATECTL_HOME", home.path());
c
}
fn seed_run(home: &Path) -> RunPaths {
ensure_root(home).unwrap();
let dir = home.join("runs").join(RUN_ID);
std::fs::create_dir_all(&dir).unwrap();
let paths = RunPaths::new(dir, RUN_ID).unwrap();
append_and_apply_event(
&paths,
"run.created",
None,
None,
json!({ "kind": "spinoff", "lifecycle": "autonomous", "title": "t" }),
)
.unwrap();
append_and_apply_event(
&paths,
"node.created",
Some(&NodeId::parse_str("n-0001").unwrap()),
None,
json!({ "kind": "spinoff" }),
)
.unwrap();
paths
}
fn worker_exited_event(paths: &RunPaths) -> Value {
let log = std::fs::read_to_string(paths.events()).unwrap();
let line = log
.lines()
.find(|l| l.contains("\"worker.exited\""))
.expect("a worker.exited event must be recorded");
serde_json::from_str(line).unwrap()
}
#[test]
fn shim_records_clean_exit_and_propagates_zero() {
let home = TempDir::new().unwrap();
let paths = seed_run(home.path());
let status = bin(&home)
.args(["run-worker", RUN_ID, "n-0001", "--", "sh", "-c", "exit 0"])
.status()
.expect("spawn shim");
assert_eq!(
status.code(),
Some(0),
"shim propagates the worker's exit 0"
);
let ev = worker_exited_event(&paths);
assert_eq!(ev["node_id"], "n-0001");
assert_eq!(ev["data"]["exit_code"], 0);
assert!(ev["data"].get("signal").is_none());
}
#[test]
fn shim_records_nonzero_exit_and_propagates_it() {
let home = TempDir::new().unwrap();
let paths = seed_run(home.path());
let status = bin(&home)
.args(["run-worker", RUN_ID, "n-0001", "--", "sh", "-c", "exit 7"])
.status()
.expect("spawn shim");
assert_eq!(
status.code(),
Some(7),
"shim propagates the worker's non-zero code"
);
let ev = worker_exited_event(&paths);
assert_eq!(ev["data"]["exit_code"], 7);
}
#[test]
fn shim_records_signal_death() {
let home = TempDir::new().unwrap();
let paths = seed_run(home.path());
let status = bin(&home)
.args([
"run-worker",
RUN_ID,
"n-0001",
"--",
"sh",
"-c",
"kill -9 $$",
])
.status()
.expect("spawn shim");
assert_eq!(status.code(), Some(128 + 9));
let ev = worker_exited_event(&paths);
assert_eq!(ev["data"]["signal"], 9);
assert!(ev["data"].get("exit_code").is_none());
}
#[test]
fn shim_rejects_unknown_run() {
let home = TempDir::new().unwrap();
let out = bin(&home)
.args(["run-worker", RUN_ID, "n-0001", "--", "sh", "-c", "exit 0"])
.output()
.expect("spawn shim");
assert!(
!out.status.success(),
"an unknown run must fail, not launch"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("run_not_found"),
"expected run_not_found, got: {stderr}"
);
}
#[test]
fn shim_rejects_unknown_node() {
let home = TempDir::new().unwrap();
let _paths = seed_run(home.path());
let out = bin(&home)
.args(["run-worker", RUN_ID, "n-9999", "--", "sh", "-c", "exit 0"])
.output()
.expect("spawn shim");
assert!(
!out.status.success(),
"an unknown node must fail, not launch"
);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
stderr.contains("node_not_found"),
"expected node_not_found, got: {stderr}"
);
}
#[test]
fn shim_records_a_told_failure_when_the_worker_cannot_launch() {
let home = TempDir::new().unwrap();
let paths = seed_run(home.path());
let out = bin(&home)
.args([
"run-worker",
RUN_ID,
"n-0001",
"--",
"/nonexistent/orchestratectl-no-such-worker",
])
.output()
.expect("spawn shim");
assert!(!out.status.success(), "a spawn failure exits non-zero");
let ev = worker_exited_event(&paths);
assert_eq!(ev["data"]["exit_code"], 127);
}
#[test]
fn shim_survives_sigterm_and_records_the_childs_true_exit() {
let home = TempDir::new().unwrap();
let paths = seed_run(home.path());
let mut child = bin(&home)
.args([
"run-worker",
RUN_ID,
"n-0001",
"--",
"sh",
"-c",
"sleep 2; exit 0",
])
.spawn()
.expect("spawn shim");
let shim_pid = child.id().to_string();
std::thread::sleep(std::time::Duration::from_millis(400));
let killed = Command::new("kill")
.args(["-TERM", &shim_pid])
.status()
.expect("send SIGTERM")
.success();
assert!(killed, "SIGTERM to the shim should be delivered");
let status = child.wait().expect("await shim");
assert_eq!(
status.code(),
Some(0),
"the shim ignored SIGTERM, waited for the child, and propagated its clean exit"
);
let ev = worker_exited_event(&paths);
assert_eq!(ev["data"]["exit_code"], 0);
}