use assert_cmd::Command;
use std::fs;
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]
protected_paths = ["pushkin.toml"]
"#;
const CONTRACT_SOURCE: &str = "import { z } from \"zod\";\n\n\
export const UserCreateSchema = z.object({ name: z.string(), email: z.string() }).strict();\n";
const NONCONFORMING: &str = "export async function POST(req: Request) {\n\
const body = await req.json();\n\
return Response.json({ name: body.name });\n\
}\n";
fn repo() -> std::io::Result<tempfile::TempDir> {
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"), CONTRACT_SOURCE)?;
Ok(dir)
}
fn run(dir: &tempfile::TempDir, args: &[&str], stdin: Option<String>) -> (i32, String) {
let Ok(mut command) = Command::cargo_bin("pushkin") else {
return (-1, "cargo_bin resolution failed".to_owned());
};
command.current_dir(dir.path()).args(args);
if let Some(payload) = stdin {
command.write_stdin(payload);
}
let Ok(output) = command.output() else {
return (-1, "spawn failed".to_owned());
};
(
output.status.code().unwrap_or(-1),
String::from_utf8_lossy(&output.stdout).into_owned(),
)
}
#[test]
fn stats_reports_blocks_by_rule_and_tokens_saved() {
let dir = repo().unwrap();
let payload = serde_json::json!({
"session_id": "stats-1",
"tool_name": "Write",
"tool_input": { "file_path": "app/api/users/route.ts", "content": NONCONFORMING }
})
.to_string();
run(&dir, &["hook", "claude"], Some(payload.clone()));
run(&dir, &["hook", "claude"], Some(payload));
let (code, stdout) = run(&dir, &["stats"], None);
assert_eq!(code, 0);
assert!(
stdout.contains("contract.boundary.unvalidated_input"),
"blocks grouped by rule: {stdout}"
);
assert!(
stdout.contains("saved"),
"compression savings surfaced: {stdout}"
);
}