use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::tempdir;
fn aiguard() -> Command {
Command::cargo_bin("aiguard").expect("aiguard binary not found")
}
fn write_blocking_config(dir: &std::path::Path) -> std::path::PathBuf {
let cfg = dir.join("aiguard.toml");
std::fs::write(
&cfg,
r#"
schema = "1.0"
[policy]
default_action = "allow"
strict = false
fail_open = false
ask_on_first_run = false
[scanners.prompt_injection]
enabled = false
[scanners.secrets]
enabled = false
[scanners.mcp]
enabled = false
[tools]
deny_shell_patterns = [
"rm -rf /",
"rm -rf /*",
]
"#,
)
.expect("write aiguard.toml");
cfg
}
#[test]
fn help_exits_zero() {
aiguard()
.arg("--help")
.assert()
.success()
.stdout(predicate::str::contains("hook"))
.stdout(predicate::str::contains("doctor"))
.stdout(predicate::str::contains("log"));
}
#[test]
fn version_exits_zero() {
aiguard()
.arg("--version")
.assert()
.success()
.stdout(predicate::str::is_match(r"\d+\.\d+\.\d+").unwrap());
}
#[test]
fn hook_claude_code_pre_allow() {
let tmp = tempdir().expect("tempdir");
let cfg = write_blocking_config(tmp.path());
let payload = serde_json::json!({
"hook_event_name": "PreToolUse",
"session_id": "test-123",
"tool": "Read",
"input": { "file_path": "/tmp/test.txt" }
})
.to_string();
aiguard()
.args(["hook", "claude-code", "pre_tool"])
.env("AIGUARD_CONFIG", cfg.to_str().unwrap())
.write_stdin(payload)
.assert()
.success()
.stdout(predicate::str::contains("allow"));
}
#[test]
fn hook_claude_code_pre_block_rm_rf() {
let tmp = tempdir().expect("tempdir");
let cfg = write_blocking_config(tmp.path());
let payload = serde_json::json!({
"hook_event_name": "PreToolUse",
"session_id": "test-456",
"tool": "Bash",
"input": { "command": "rm -rf /" }
})
.to_string();
aiguard()
.args(["hook", "claude-code", "pre_tool"])
.env("AIGUARD_CONFIG", cfg.to_str().unwrap())
.write_stdin(payload)
.assert()
.code(2)
.stdout(predicate::str::contains("block"));
}
#[test]
fn doctor_exits_zero_or_one() {
let output = aiguard()
.arg("doctor")
.output()
.expect("failed to run aiguard doctor");
let code = output.status.code().expect("process was killed by signal");
assert!(
code == 0 || code == 1,
"doctor exited with unexpected code {code}"
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("aiguard doctor") || stdout.contains("Summary"),
"unexpected doctor output: {stdout}"
);
}
#[test]
fn log_help_exits_zero() {
aiguard()
.args(["log", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("tail").or(predicate::str::contains("log")));
}
#[test]
fn replay_help_exits_zero() {
aiguard()
.args(["replay", "--help"])
.assert()
.success()
.stdout(predicate::str::contains("replay").or(predicate::str::contains("session")));
}
#[test]
fn hook_invalid_json_exits_nonzero() {
let tmp = tempdir().expect("tempdir");
let cfg = write_blocking_config(tmp.path());
aiguard()
.args(["hook", "claude-code", "pre_tool"])
.env("AIGUARD_CONFIG", cfg.to_str().unwrap())
.write_stdin("this is not json }{")
.assert()
.failure(); }