use std::io::Write;
use std::process::{Command, Stdio};
const HOW_IT_WORKS: &str = "https://www.michaeldhopkins.com/docs/safe-chains/how-it-works.html";
const REACHES: &[(&str, &str, bool)] = &[
("cat ~/.ssh/id_rsa", "/.ssh/id_rsa", true),
("echo x > /other/repo/x.rs", "/other/repo/x.rs", true),
("grep -r TODO /other/repo", "/other/repo", true),
("tee /etc/sudoers", "/etc/sudoers", false),
];
fn bare_home() -> tempfile::TempDir {
tempfile::tempdir().expect("tempdir")
}
fn run(args: &[&str], home: &std::path::Path, stdin_payload: Option<&str>) -> String {
let mut child = Command::new(env!("CARGO_BIN_EXE_safe-chains"))
.args(args)
.current_dir(env!("CARGO_MANIFEST_DIR"))
.env("HOME", home)
.stdin(if stdin_payload.is_some() { Stdio::piped() } else { Stdio::null() })
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn safe-chains");
if let Some(payload) = stdin_payload {
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");
format!("{}{}", String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr))
}
#[test]
fn every_why_surface_names_the_path_and_the_remedy() {
let home = bare_home();
for (command, reached, grantable) in REACHES {
let payload = serde_json::json!({
"tool_name": "Bash",
"tool_input": {"command": command},
"cwd": env!("CARGO_MANIFEST_DIR"),
})
.to_string();
for (surface, text) in [
("hook", run(&[], home.path(), Some(&payload))),
("--explain", run(&["--explain", command], home.path(), None)),
("--suggest", run(&["--suggest", command], home.path(), None)),
] {
assert!(
text.contains(reached),
"`{surface}` on `{command}` never names the path it refused over \
(`{reached}`):\n{text}"
);
assert_eq!(
text.contains("~/.config/safe-chains.toml"),
*grantable,
"`{surface}` on `{command}`: grant remedy offered={}, expected={grantable}\n{text}",
text.contains("~/.config/safe-chains.toml")
);
assert!(
text.contains(HOW_IT_WORKS),
"`{surface}` on `{command}` links somewhere other than the page documenting \
the path model and `[[grant]]`:\n{text}"
);
}
}
}
#[test]
fn a_grammar_refusal_still_points_at_custom_commands() {
let home = bare_home();
for command in ["git push origin main", "npm install left-pad"] {
let text = run(&["--suggest", command], home.path(), None);
assert!(
text.contains("custom-commands.html"),
"`--suggest` on `{command}` is a grammar refusal and must still point at \
custom-commands:\n{text}"
);
assert!(
!text.contains(HOW_IT_WORKS),
"`--suggest` on `{command}` reaches no path, so it must not offer the grant \
remedy:\n{text}"
);
}
}