use assert_cmd::Command;
use std::fs;
use std::path::Path;
use std::process::Command as StdCommand;
type TestResult = Result<(), Box<dyn std::error::Error>>;
const ROOT_ARMED: &str =
"version = 1\ncanonical = \"json-schema-2020-12\"\nauthoring = \"zod\"\n\n[gates]\nprotected_paths = [\"secrets/**\"]\n";
const PERMISSIVE: &str =
"version = 1\ncanonical = \"json-schema-2020-12\"\nauthoring = \"zod\"\n\n[gates]\n";
fn write_payload(path: &str) -> String {
serde_json::json!({
"session_id": "f75",
"tool_name": "Write",
"tool_input": { "file_path": path, "content": "version = 1\n" }
})
.to_string()
}
fn edit_payload(path: &str) -> String {
serde_json::json!({
"session_id": "f75",
"tool_name": "Edit",
"tool_input": {
"file_path": path,
"old_string": "version = 1",
"new_string": "version = 2"
}
})
.to_string()
}
struct Run {
out: String,
}
fn hook_in(
dir: &Path,
payload: &str,
env: Option<(&str, &str)>,
) -> Result<Run, Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("pushkin")?;
cmd.current_dir(dir).args(["hook", "claude"]);
if let Some((k, v)) = env {
cmd.env(k, v);
}
let output = cmd.write_stdin(payload.to_owned()).output()?;
Ok(Run {
out: format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
),
})
}
fn git_repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let root = dir.path();
fs::write(root.join("pushkin.toml"), ROOT_ARMED)?;
fs::create_dir_all(root.join("sub"))?;
fs::create_dir_all(root.join("secrets"))?;
for args in [
vec!["init", "-q"],
vec!["config", "user.email", "t@example.com"],
vec!["config", "user.name", "t"],
] {
StdCommand::new("git")
.current_dir(root)
.args(args)
.output()?;
}
Ok(dir)
}
fn is_denied(run: &Run) -> bool {
run.out.contains("permissionDecision") && run.out.contains("deny")
}
fn denied_as_nested_manifest(run: &Run) -> bool {
is_denied(run)
&& run.out.contains("pushkin.nested_manifest")
&& run.out.contains("PUSHKIN_MANIFEST")
}
#[test]
fn a_write_to_a_nested_manifest_is_denied_with_its_own_rule() -> TestResult {
let dir = git_repo()?;
let run = hook_in(dir.path(), &write_payload("sub/pushkin.toml"), None)?;
assert!(
denied_as_nested_manifest(&run),
"a Write to sub/pushkin.toml must deny under pushkin.nested_manifest, \
naming the reason and the override; got:\n{}",
run.out
);
Ok(())
}
#[test]
fn an_edit_to_a_nested_manifest_is_denied() -> TestResult {
let dir = git_repo()?;
fs::write(dir.path().join("sub/pushkin.toml"), PERMISSIVE)?;
let run = hook_in(dir.path(), &edit_payload("sub/pushkin.toml"), None)?;
assert!(
denied_as_nested_manifest(&run),
"an Edit to sub/pushkin.toml must deny under pushkin.nested_manifest; \
got:\n{}",
run.out
);
Ok(())
}
#[test]
fn a_deeply_nested_manifest_is_denied() -> TestResult {
let dir = git_repo()?;
fs::create_dir_all(dir.path().join("a/b/c"))?;
let run = hook_in(dir.path(), &write_payload("a/b/c/pushkin.toml"), None)?;
assert!(
denied_as_nested_manifest(&run),
"the name is protected wherever it appears, not just one level down; \
got:\n{}",
run.out
);
Ok(())
}
#[test]
fn the_root_manifest_is_not_denied_by_the_nested_rule() -> TestResult {
let dir = git_repo()?;
let run = hook_in(dir.path(), &write_payload("pushkin.toml"), None)?;
assert!(
!run.out.contains("pushkin.nested_manifest"),
"the governing root manifest must never trip the nested rule; got:\n{}",
run.out
);
Ok(())
}
#[test]
fn a_pushkin_manifest_named_manifest_elsewhere_is_allowed() -> TestResult {
let dir = git_repo()?;
let nested = dir.path().join("sub/pushkin.toml");
fs::write(&nested, PERMISSIVE)?;
let run = hook_in(
dir.path(),
&write_payload("sub/pushkin.toml"),
Some(("PUSHKIN_MANIFEST", &nested.display().to_string())),
)?;
assert!(
!run.out.contains("pushkin.nested_manifest"),
"a PUSHKIN_MANIFEST-named manifest is the governing one and must be \
writable — the override is a supported workflow; got:\n{}",
run.out
);
Ok(())
}
#[test]
fn an_ordinary_nested_file_is_not_denied() -> TestResult {
let dir = git_repo()?;
let run = hook_in(dir.path(), &write_payload("sub/config.toml"), None)?;
assert!(
!run.out.contains("pushkin.nested_manifest"),
"a nested file that is not the manifest must not trip the rule; got:\n{}",
run.out
);
Ok(())
}