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 MANIFEST: &str = r#"
version = 1
canonical = "json-schema-2020-12"
authoring = "zod"
[[contracts]]
name = "user"
source = "contracts/user.zod.ts"
emit = ["zod"]
[[mappings]]
glob = "app/api/**/*.ts"
contracts = ["user"]
require = "boundary-validation"
[gates]
suppression_comments = "deny"
protected_paths = ["pushkin.toml"]
read_only_paths = ["crates/**/tests/**"]
retrieval_paths = ["crates/**/*.rs"]
retrieval_tool = "mcp__codebase-retrieval__codebase-retrieval"
"#;
const PROTECTED: &str = "pushkin.toml";
const COMMITTED_TEST: &str = "crates/pushkin-cli/tests/committed_suite.rs";
const MAPPED: &str = "app/api/users/route.ts";
const RULE_PROTECTED: &str = "pushkin.protected_path";
const RULE_READ_ONLY: &str = "pushkin.read_only_path";
const RULE_UNVALIDATED: &str = "contract.boundary.unvalidated_input";
fn git(dir: &Path, args: &[&str]) -> TestResult {
let status = StdCommand::new("git")
.current_dir(dir)
.args(args)
.status()?;
assert!(status.success(), "git {args:?} failed");
Ok(())
}
fn repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let p = dir.path();
fs::write(p.join(PROTECTED), MANIFEST)?;
fs::create_dir_all(p.join("contracts"))?;
fs::write(p.join("contracts/user.zod.ts"), "export const user = 1;\n")?;
fs::create_dir_all(p.join("app/api/users"))?;
fs::write(p.join(MAPPED), "export const handler = 1;\n")?;
fs::create_dir_all(p.join("crates/pushkin-cli/tests"))?;
fs::write(p.join(COMMITTED_TEST), "// committed suite\n")?;
git(p, &["init", "-q", "."])?;
git(p, &["add", "-A"])?;
git(
p,
&[
"-c",
"user.name=Shape2 Equivalence",
"-c",
"user.email=shape2@test",
"commit",
"-qm",
"fixture",
],
)?;
Ok(dir)
}
fn check_json(dir: &Path, payload: &str) -> Result<String, Box<dyn std::error::Error>> {
let out = Command::cargo_bin("pushkin")?
.current_dir(dir)
.env("PUSHKIN_DAEMON", "off")
.args(["check", "--json"])
.write_stdin(payload.to_owned())
.output()?;
Ok(String::from_utf8(out.stdout)?)
}
fn hook_claude(dir: &Path, payload: &str) -> Result<String, Box<dyn std::error::Error>> {
let out = Command::cargo_bin("pushkin")?
.current_dir(dir)
.env("PUSHKIN_DAEMON", "off")
.args(["hook", "claude"])
.write_stdin(payload.to_owned())
.output()?;
Ok(String::from_utf8(out.stdout)?)
}
#[test]
fn write_leaf_blocks_through_decide_on_both_surfaces() -> TestResult {
let dir = repo()?;
let payload = r#"{"session_id":"s2-write","tool_name":"Write","tool_input":{"file_path":"pushkin.toml","content":"version = 1"}}"#;
let check = check_json(dir.path(), payload)?;
assert!(
check.contains(RULE_PROTECTED) && check.contains("\"block\""),
"Floor Write leaf must reach decide and block: {check}"
);
let hook = hook_claude(dir.path(), payload)?;
assert!(
hook.contains(RULE_PROTECTED) && hook.contains("deny"),
"AgentWriteTime Write leaf must reach decide and deny: {hook}"
);
Ok(())
}
#[test]
fn mutate_no_content_leaf_blocks_through_decide_on_both_surfaces() -> TestResult {
let dir = repo()?;
let payload = r#"{"session_id":"s2-edit","tool_name":"Edit","tool_input":{"file_path":"crates/pushkin-cli/tests/committed_suite.rs","old_string":"// committed suite","new_string":"// tampered"}}"#;
let check = check_json(dir.path(), payload)?;
assert!(
check.contains(RULE_READ_ONLY) && check.contains("\"block\""),
"Floor MutateNoContent leaf must reach decide and block: {check}"
);
let hook = hook_claude(dir.path(), payload)?;
assert!(
hook.contains(RULE_READ_ONLY) && hook.contains("deny"),
"AgentWriteTime MutateNoContent leaf must reach decide and deny: {hook}"
);
Ok(())
}
#[test]
fn conforming_write_allows_through_decide_on_both_surfaces() -> TestResult {
let dir = repo()?;
let payload = r#"{"session_id":"s2-ok","tool_name":"Write","tool_input":{"file_path":"app/api/users/route.ts","content":"import { UserCreateSchema } from \"contracts/user.zod\";\nexport async function POST(req){ const b = UserCreateSchema.parse(await req.json()); return Response.json(b); }"}}"#;
let check = check_json(dir.path(), payload)?;
assert!(
check.contains("\"allow\"") && !check.contains(RULE_UNVALIDATED),
"Floor conforming write must allow through decide: {check}"
);
let hook = hook_claude(dir.path(), payload)?;
assert!(
!hook.contains("deny") && !hook.contains(RULE_UNVALIDATED),
"AgentWriteTime conforming write must allow through decide: {hook}"
);
Ok(())
}