use crate::helpers::skill_eval;
use predicates::prelude::*;
use predicates::str::contains;
use std::fs;
use tempfile::TempDir;
#[test]
fn guard_subcommand_is_hidden_but_callable() {
skill_eval()
.arg("--help")
.assert()
.success()
.stdout(contains("PreToolUse hook entry point").not());
skill_eval().arg("guard").arg("--help").assert().success();
skill_eval()
.args(["guard-hook", "--help"])
.assert()
.success();
}
fn write_armed_marker(root: &std::path::Path, allowed: &std::path::Path) -> std::path::PathBuf {
let skills = root.join(".claude").join("skills");
fs::create_dir_all(&skills).unwrap();
let marker = skills.join(".slow-powers-eval-guard.json");
fs::write(
&marker,
format!(
r#"{{ "active": true, "allowedRoots": ["{}"], "expiresAt": "2999-01-01T00:00:00.000Z" }}"#,
allowed.display()
),
)
.unwrap();
marker
}
fn write_codex_armed_marker(
root: &std::path::Path,
allowed: &std::path::Path,
) -> std::path::PathBuf {
let skills = root.join(".agents").join("skills");
fs::create_dir_all(&skills).unwrap();
let marker = skills.join(".slow-powers-eval-guard.json");
fs::write(
&marker,
format!(
r#"{{ "active": true, "allowedRoots": ["{}"], "expiresAt": "2999-01-01T00:00:00.000Z" }}"#,
allowed.display()
),
)
.unwrap();
marker
}
#[test]
fn guard_denies_out_of_bounds_write() {
let tmp = TempDir::new().unwrap();
let marker = write_armed_marker(tmp.path(), &tmp.path().join(".eval-magic"));
skill_eval()
.arg("guard")
.arg(&marker)
.write_stdin(r#"{ "tool_name": "Write", "tool_input": { "file_path": "/etc/passwd" } }"#)
.assert()
.success()
.stdout(contains(r#""permissionDecision":"deny""#));
}
#[test]
fn guard_allows_in_bounds_write() {
let tmp = TempDir::new().unwrap();
let workspace = tmp.path().join(".eval-magic");
let marker = write_armed_marker(tmp.path(), &workspace);
skill_eval()
.arg("guard")
.arg(&marker)
.write_stdin(format!(
r#"{{ "tool_name": "Write", "tool_input": {{ "file_path": "{}/out.md" }} }}"#,
workspace.display()
))
.assert()
.success()
.stdout("");
}
#[test]
fn guard_codex_subcommand_blocks_with_codex_verdict_shape() {
let tmp = TempDir::new().unwrap();
let marker = write_codex_armed_marker(tmp.path(), &tmp.path().join(".eval-magic"));
skill_eval()
.arg("guard-codex")
.arg(&marker)
.write_stdin(
r#"{ "tool_name": "Bash", "tool_input": { "command": "npm install left-pad" } }"#,
)
.assert()
.success()
.stdout(contains(r#""decision":"block""#))
.stdout(contains("blocked Bash"));
}
#[test]
fn guard_deny_verdict_bytes_are_stable() {
let tmp = TempDir::new().unwrap();
let marker = tmp.path().join("marker.json");
fs::write(
&marker,
r#"{"active":true,"allowedRoots":["/work/env"],"expiresAt":"2999-01-01T00:00:00.000Z"}"#,
)
.unwrap();
skill_eval()
.arg("guard")
.arg(&marker)
.write_stdin(r#"{ "tool_name": "Write", "tool_input": { "file_path": "/etc/passwd" } }"#)
.assert()
.success()
.stdout(
"{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\
\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\
\"eval guard: Write to /etc/passwd is outside the eval sandbox \
(allowed: /work/env)\"}}",
);
}
#[test]
fn guard_codex_block_verdict_bytes_are_stable() {
let tmp = TempDir::new().unwrap();
let marker = tmp.path().join("marker.json");
fs::write(
&marker,
r#"{"active":true,"allowedRoots":["/work/env"],"expiresAt":"2999-01-01T00:00:00.000Z"}"#,
)
.unwrap();
skill_eval()
.arg("guard-codex")
.arg(&marker)
.write_stdin(
r#"{ "tool_name": "Bash", "tool_input": { "command": "npm install left-pad" } }"#,
)
.assert()
.success()
.stdout(
"{\"decision\":\"block\",\"reason\":\"eval guard: blocked Bash \
(package install/add) — runs outside the eval sandbox\"}",
);
}
#[test]
fn guard_fails_open_without_marker() {
let tmp = TempDir::new().unwrap();
skill_eval()
.arg("guard")
.arg(tmp.path().join("nope.json"))
.write_stdin(r#"{ "tool_name": "Write", "tool_input": { "file_path": "/etc/passwd" } }"#)
.assert()
.success()
.stdout("");
}
#[test]
fn guard_hook_resolves_the_harness_verdict_shape() {
let tmp = TempDir::new().unwrap();
let marker = write_armed_marker(tmp.path(), &tmp.path().join(".eval-magic"));
skill_eval()
.args(["guard-hook", "--harness", "claude-code"])
.arg(&marker)
.write_stdin(r#"{ "tool_name": "Write", "tool_input": { "file_path": "/etc/passwd" } }"#)
.assert()
.success()
.stdout(contains(r#""permissionDecision":"deny""#));
skill_eval()
.args(["guard-hook", "--harness", "codex"])
.arg(&marker)
.write_stdin(
r#"{ "tool_name": "Bash", "tool_input": { "command": "npm install left-pad" } }"#,
)
.assert()
.success()
.stdout(contains(r#""decision":"block""#));
}
#[test]
fn guard_hook_fails_open_for_an_unknown_harness() {
let tmp = TempDir::new().unwrap();
let marker = write_armed_marker(tmp.path(), &tmp.path().join(".eval-magic"));
skill_eval()
.args(["guard-hook", "--harness", "mystery"])
.arg(&marker)
.write_stdin(r#"{ "tool_name": "Write", "tool_input": { "file_path": "/etc/passwd" } }"#)
.assert()
.success()
.stdout("");
}
#[test]
fn guard_hook_generic_skips_descriptor_discovery() {
let tmp = TempDir::new().unwrap();
let marker = write_armed_marker(tmp.path(), &tmp.path().join(".eval-magic"));
let harnesses = tmp.path().join(".eval-magic").join("harnesses");
fs::create_dir_all(&harnesses).unwrap();
fs::write(harnesses.join("broken.toml"), "label = ").unwrap();
skill_eval()
.args(["guard-hook", "--harness", "claude-code"])
.arg(&marker)
.current_dir(tmp.path())
.write_stdin(r#"{ "tool_name": "Write", "tool_input": { "file_path": "/etc/passwd" } }"#)
.assert()
.success()
.stdout(contains(r#""permissionDecision":"deny""#))
.stderr(contains("skipping harness descriptor").not());
}
#[test]
fn teardown_guard_reports_nothing_to_remove() {
let tmp = TempDir::new().unwrap();
skill_eval()
.arg("teardown-guard")
.current_dir(tmp.path())
.assert()
.success()
.stdout(contains("nothing to remove"));
}
#[test]
fn teardown_guard_removes_installed_guard() {
let tmp = TempDir::new().unwrap();
write_armed_marker(tmp.path(), &tmp.path().join(".eval-magic"));
skill_eval()
.arg("teardown-guard")
.current_dir(tmp.path())
.assert()
.success()
.stdout(contains("Write guard removed"));
assert!(
!tmp.path()
.join(".claude/skills/.slow-powers-eval-guard.json")
.exists()
);
}
#[test]
fn guard_hook_skips_descriptor_discovery() {
let tmp = TempDir::new().unwrap();
let marker = write_armed_marker(tmp.path(), &tmp.path().join(".eval-magic"));
let harnesses = tmp.path().join(".eval-magic").join("harnesses");
fs::create_dir_all(&harnesses).unwrap();
fs::write(harnesses.join("broken.toml"), "label = ").unwrap();
skill_eval()
.arg("guard")
.arg(&marker)
.current_dir(tmp.path())
.write_stdin(r#"{ "tool_name": "Write", "tool_input": { "file_path": "/etc/passwd" } }"#)
.assert()
.success()
.stdout(contains(r#""permissionDecision":"deny""#))
.stderr(contains("skipping harness descriptor").not());
}