use std::io::Write;
use std::process::{Command, Stdio};
fn hook_stdout(payload: &str) -> String {
let mut child = Command::new(env!("CARGO_BIN_EXE_safe-chains"))
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()
.expect("spawn safe-chains");
child
.stdin
.take()
.expect("stdin was piped")
.write_all(payload.as_bytes())
.expect("write the hook payload");
let out = child.wait_with_output().expect("wait for safe-chains");
String::from_utf8_lossy(&out.stdout).into_owned()
}
#[test]
fn overreach_nudge_names_the_working_directory() {
let payload = r#"{"tool_input":{"command":"cat /other/repo/x.rs"},"cwd":"/work/here"}"#;
let out = hook_stdout(payload);
assert!(out.contains("/work/here"), "nudge must NAME the working directory (mismatch cue): {out}");
assert!(out.contains("/other/repo/x.rs"), "nudge must name the reached path: {out}");
}
fn exit_code(args: &[&str]) -> i32 {
Command::new(env!("CARGO_BIN_EXE_safe-chains"))
.args(args)
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("run safe-chains")
.code()
.unwrap_or(-1)
}
#[test]
fn cli_gate_fails_closed_on_malformed_invocation() {
assert_eq!(exit_code(&["rm -rf /", "--level", "inert"]), 1, "valid gate must refuse");
assert_eq!(exit_code(&["echo hi", "--level", "inert"]), 0, "valid gate must allow a safe cmd");
assert_ne!(exit_code(&["rm -rf /", "--levle", "inert"]), 0, "typo'd flag must FAIL CLOSED");
assert_ne!(exit_code(&["-z"]), 0, "unknown flag must fail closed");
assert_ne!(exit_code(&["rm -rf /", "--nonsense"]), 0, "unknown long flag must fail closed");
assert_eq!(exit_code(&["--version"]), 0, "--version prints and exits 0");
assert_eq!(exit_code(&["-v"]), 0, "-v prints version and exits 0");
assert_eq!(exit_code(&["-V"]), 0, "-V prints version and exits 0");
}
#[test]
fn upper_band_level_thresholds_gate_through_the_cli() {
assert_eq!(exit_code(&["git push origin main", "--level", "developer"]), 1, "developer denies push");
assert_eq!(exit_code(&["git push origin main", "--level", "network-admin"]), 0, "network-admin allows push");
assert_eq!(exit_code(&["git push origin main", "--level", "yolo"]), 0, "yolo allows push");
assert_eq!(exit_code(&["rm -rf /", "--level", "yolo"]), 1, "yolo denies rm -rf /");
assert_eq!(exit_code(&["frobnicate --wombat", "--level", "yolo"]), 1, "yolo denies an unmodeled command");
assert_eq!(exit_code(&["cat ./README.md", "--level", "network-admin"]), 0, "reads pass at network-admin");
assert_eq!(exit_code(&["git push origin main", "--level", "reader"]), 1, "reader still denies push");
}