use pushkin_core::manifest::Manifest;
use pushkin_core::pipeline::{check_write, WriteRequest};
const MANIFEST: &str = r#"
version = 1
canonical = "json-schema-2020-12"
authoring = "zod"
[[contracts]]
name = "user"
source = "contracts/user.zod.ts"
emit = ["zod", "pydantic", "rust", "sql"]
[[mappings]]
glob = "app/api/**/*.ts"
contracts = ["user"]
require = "boundary-validation"
[gates]
suppression_comments = "deny"
protected_paths = ["pushkin.toml", ".claude/settings.json", "schemas/**"]
"#;
const NONCONFORMING: &str = r"export async function POST(req: Request) {
const body = await req.json();
return Response.json({ name: body.name });
}
";
const CONFORMING: &str = r#"import { UserCreateSchema } from "../../../contracts/user.zod";
export async function POST(req: Request) {
const body = UserCreateSchema.parse(await req.json());
return Response.json(body);
}
"#;
fn request(file_path: &str, content: &str) -> WriteRequest {
WriteRequest {
file_path: file_path.to_owned(),
content: content.to_owned(),
}
}
#[test]
fn blocks_unvalidated_handler_write() {
let result = check_write(
&Manifest::parse(MANIFEST).unwrap(),
&request("app/api/users/route.ts", NONCONFORMING),
);
assert_eq!(result.decision.as_str(), "block");
let rules: Vec<&str> = result.violations.iter().map(|v| v.rule.as_str()).collect();
assert!(rules.contains(&"contract.boundary.unvalidated_input"));
}
#[test]
fn allows_conforming_write() {
let result = check_write(
&Manifest::parse(MANIFEST).unwrap(),
&request("app/api/users/route.ts", CONFORMING),
);
assert_eq!(result.decision.as_str(), "allow");
assert!(result.violations.is_empty());
}
#[test]
fn allows_unmapped_path_write() {
let result = check_write(
&Manifest::parse(MANIFEST).unwrap(),
&request("docs/notes.md", NONCONFORMING),
);
assert_eq!(result.decision.as_str(), "allow");
}
#[test]
fn blocks_new_suppression_comment() {
let content = format!("// @ts-ignore\n{CONFORMING}");
let result = check_write(
&Manifest::parse(MANIFEST).unwrap(),
&request("app/api/users/route.ts", &content),
);
assert_eq!(result.decision.as_str(), "block");
let rules: Vec<&str> = result.violations.iter().map(|v| v.rule.as_str()).collect();
assert!(rules.contains(&"pushkin.suppression.new"));
}
#[test]
fn blocks_protected_path_write() {
let result = check_write(
&Manifest::parse(MANIFEST).unwrap(),
&request(".claude/settings.json", "{}"),
);
assert_eq!(result.decision.as_str(), "block");
let rules: Vec<&str> = result.violations.iter().map(|v| v.rule.as_str()).collect();
assert!(rules.contains(&"pushkin.protected_path"));
let schema = check_write(
&Manifest::parse(MANIFEST).unwrap(),
&request("schemas/user.schema.json", "{}"),
);
assert_eq!(schema.decision.as_str(), "block");
}
#[test]
fn deny_reason_contains_fix_hint() {
let result = check_write(
&Manifest::parse(MANIFEST).unwrap(),
&request("app/api/users/route.ts", NONCONFORMING),
);
let violation = result
.violations
.iter()
.find(|v| v.rule == "contract.boundary.unvalidated_input")
.unwrap();
assert_eq!(violation.file, "app/api/users/route.ts");
assert!(violation.line >= 1);
assert_eq!(violation.contract.as_deref(), Some("user"));
assert!(!violation.fix_hint.is_empty());
assert!(!violation.suggestions.is_empty());
}