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/**"]
"#;
const COMMITTED_TEST: &str = "crates/pushkin-cli/tests/committed_suite.rs";
const MAPPED: &str = "app/api/users/route.ts";
const UNGATED: &str = "docs/notes.md";
const RULE_READ_ONLY: &str = "pushkin.read_only_path";
const RULE_PROTECTED: &str = "pushkin.protected_path";
const RULE_UNVALIDATED: &str = "contract.boundary.unvalidated_input";
const RULE_CONTENT_UNAVAILABLE: &str = "pushkin.content_unavailable";
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()?;
fs::write(dir.path().join("pushkin.toml"), MANIFEST)?;
fs::create_dir_all(dir.path().join("contracts"))?;
fs::write(
dir.path().join("contracts/user.zod.ts"),
"import { z } from 'zod';\nexport const user = z.object({ id: z.string() });\n",
)?;
fs::create_dir_all(dir.path().join("crates/pushkin-cli/tests"))?;
fs::write(dir.path().join(COMMITTED_TEST), "// committed suite\n")?;
fs::create_dir_all(dir.path().join("app/api/users"))?;
fs::write(dir.path().join(MAPPED), "export async function POST() {}\n")?;
fs::create_dir_all(dir.path().join("docs"))?;
fs::write(dir.path().join(UNGATED), "notes\n")?;
git(dir.path(), &["init", "-q", "."])?;
git(dir.path(), &["add", "-A"])?;
git(
dir.path(),
&[
"-c",
"user.name=F48 Suite",
"-c",
"user.email=f48@test",
"commit",
"-qm",
"fixture",
],
)?;
Ok(dir)
}
fn edit_payload(file_path: &str) -> String {
serde_json::json!({
"session_id": "f48-phase-a",
"tool_name": "Edit",
"tool_input": {
"file_path": file_path,
"old_string": "committed",
"new_string": "tampered",
},
})
.to_string()
}
fn multiedit_payload(file_path: &str) -> String {
serde_json::json!({
"session_id": "f48-phase-a",
"tool_name": "MultiEdit",
"tool_input": {
"file_path": file_path,
"edits": [
{ "old_string": "committed", "new_string": "tampered" },
{ "old_string": "suite", "new_string": "gone" },
],
},
})
.to_string()
}
fn write_payload(file_path: &str, content: &str) -> String {
serde_json::json!({
"session_id": "f48-phase-a",
"tool_name": "Write",
"tool_input": { "file_path": file_path, "content": content },
})
.to_string()
}
fn check(dir: &Path, payload: &str) -> Result<(Option<i32>, String), Box<dyn std::error::Error>> {
let output = Command::cargo_bin("pushkin")?
.current_dir(dir)
.write_stdin(payload.to_owned())
.arg("check")
.output()?;
Ok((
output.status.code(),
format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
),
))
}
fn hook(
dir: &Path,
payload: &str,
daemon: Option<&str>,
) -> Result<String, Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("pushkin")?;
cmd.current_dir(dir).write_stdin(payload.to_owned());
if let Some(mode) = daemon {
cmd.env("PUSHKIN_DAEMON", mode);
}
let output = cmd.args(["hook", "claude"]).output()?;
Ok(format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
))
}
#[test]
fn edit_of_a_committed_read_only_file_is_denied_by_the_check_verb() -> TestResult {
let dir = repo()?;
let (code, output) = check(dir.path(), &edit_payload(COMMITTED_TEST))?;
assert_eq!(
code,
Some(2),
"an Edit is a mutation, not a malformed payload: {output}"
);
assert!(
output.contains(RULE_READ_ONLY),
"the deny names its rule: {output}"
);
Ok(())
}
#[test]
fn multiedit_of_a_committed_read_only_file_is_denied_by_the_check_verb() -> TestResult {
let dir = repo()?;
let (code, output) = check(dir.path(), &multiedit_payload(COMMITTED_TEST))?;
assert_eq!(
code,
Some(2),
"edits[] is the same mutation in another shape: {output}"
);
assert!(
output.contains(RULE_READ_ONLY),
"the deny names its rule: {output}"
);
Ok(())
}
#[test]
fn edit_of_a_committed_read_only_file_is_denied_by_the_hook() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &edit_payload(COMMITTED_TEST), Some("off"))?;
assert!(
output.contains(RULE_READ_ONLY),
"the hook is the surface agents hit; it must not fail open: {output}"
);
assert!(
!output.contains("failing open"),
"an Edit is recognized, so nothing may narrate a fail-open: {output}"
);
Ok(())
}
#[test]
fn multiedit_of_a_committed_read_only_file_is_denied_by_the_hook() -> TestResult {
let dir = repo()?;
let output = hook(dir.path(), &multiedit_payload(COMMITTED_TEST), Some("off"))?;
assert!(
output.contains(RULE_READ_ONLY),
"same rule, same surface: {output}"
);
Ok(())
}
#[test]
fn an_edit_to_a_mapped_contract_path_denies_under_the_content_unavailable_rule() -> TestResult {
let dir = repo()?;
let (code, output) = check(dir.path(), &edit_payload(MAPPED))?;
assert_eq!(
code,
Some(2),
"content-requiring rules fail CLOSED: {output}"
);
assert!(
output.contains(RULE_CONTENT_UNAVAILABLE),
"the interim refusal carries its own id: {output}"
);
Ok(())
}
#[test]
fn the_content_unavailable_deny_names_the_tool_the_rule_and_the_remedy() -> TestResult {
let dir = repo()?;
let (_, output) = check(dir.path(), &edit_payload(MAPPED))?;
assert!(
output.contains("Edit"),
"name the tool that was refused: {output}"
);
assert!(
output.contains(RULE_UNVALIDATED),
"name the rule that could not be evaluated: {output}"
);
assert!(
output.contains("Write"),
"name the remediation — re-issue as Write with full content: {output}"
);
assert!(output.contains("F48"), "cite the finding: {output}");
Ok(())
}
#[test]
fn the_content_unavailable_rule_id_is_distinct_from_a_real_content_violation() -> TestResult {
let dir = repo()?;
let nonconforming = "export async function POST(req) { const b = await req.json(); }\n";
let (_, interim) = check(dir.path(), &edit_payload(MAPPED))?;
let (_, real) = check(dir.path(), &write_payload(MAPPED, nonconforming))?;
assert!(
interim.contains(RULE_CONTENT_UNAVAILABLE),
"interim id: {interim}"
);
assert!(
!interim.contains(RULE_UNVALIDATED) || interim.contains(RULE_CONTENT_UNAVAILABLE),
"the interim deny must not masquerade as a real content violation: {interim}"
);
assert!(
real.contains(RULE_UNVALIDATED),
"a Write with real content still produces the real rule: {real}"
);
assert!(
!real.contains(RULE_CONTENT_UNAVAILABLE),
"content WAS available here; the interim id must not appear: {real}"
);
Ok(())
}
#[test]
fn the_content_unavailable_rule_is_waivable() -> TestResult {
let dir = repo()?;
let waive = Command::cargo_bin("pushkin")?
.current_dir(dir.path())
.env("GIT_AUTHOR_NAME", "F48 Suite")
.args([
"waive",
RULE_CONTENT_UNAVAILABLE,
"--path",
"app/api/**/*.ts",
"--ttl",
"2h",
"--reason",
"F48 phase A suite",
])
.output()?;
assert!(
waive.status.success(),
"the grant must be accepted: {}",
String::from_utf8_lossy(&waive.stderr)
);
let (code, output) = check(dir.path(), &edit_payload(MAPPED))?;
assert_eq!(
code,
Some(0),
"a loud, recorded waiver clears the interim refusal: {output}"
);
Ok(())
}
#[test]
fn edit_of_a_protected_path_is_still_denied() -> TestResult {
let dir = repo()?;
let (code, output) = check(dir.path(), &edit_payload("pushkin.toml"))?;
assert_eq!(
code,
Some(2),
"protected paths deny on every tool: {output}"
);
assert!(
output.contains(RULE_PROTECTED),
"unchanged rule id: {output}"
);
Ok(())
}
#[test]
fn an_edit_to_an_ungated_unmapped_path_is_allowed() -> TestResult {
let dir = repo()?;
let (code, output) = check(dir.path(), &edit_payload(UNGATED))?;
assert_eq!(
code,
Some(0),
"no rule applies, so nothing to refuse: {output}"
);
assert!(
!output.contains(RULE_CONTENT_UNAVAILABLE),
"the interim refusal fires only where a content rule actually maps: {output}"
);
Ok(())
}
#[test]
fn a_write_carrying_content_is_unchanged() -> TestResult {
let dir = repo()?;
let (code, output) = check(dir.path(), &write_payload(COMMITTED_TEST, "// tampered\n"))?;
assert_eq!(
code,
Some(2),
"Write to a committed read-only file still denies: {output}"
);
assert!(
output.contains(RULE_READ_ONLY),
"unchanged rule id: {output}"
);
Ok(())
}