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]
protected_paths = ["pushkin.toml"]
"#;
const CONTRACT: &str = "import { z } from \"zod\";\n\
export const UserCreateSchema = z.object({ name: z.string() });\n\
export type UserCreate = z.infer<typeof UserCreateSchema>;\n";
const COMPLIANT: &str = "import { UserCreateSchema } from \"../../../contracts/user.zod\";\n\n\
export async function POST(req: Request) {\n \
const body = UserCreateSchema.parse(await req.json());\n \
return Response.json(body);\n}\n";
fn git(root: &Path, args: &[&str]) -> Result<(), Box<dyn std::error::Error>> {
let status = StdCommand::new("git")
.current_dir(root)
.args(args)
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::null())
.status()?;
if !status.success() {
return Err(format!("git {args:?} failed").into());
}
Ok(())
}
fn binary_dir() -> Result<String, Box<dyn std::error::Error>> {
Ok(Path::new(env!("CARGO_BIN_EXE_pushkin"))
.parent()
.ok_or("binary under test has no parent")?
.to_string_lossy()
.into_owned())
}
fn staged_repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
let root = dir.path();
fs::write(root.join("pushkin.toml"), MANIFEST)?;
fs::create_dir_all(root.join("contracts"))?;
fs::create_dir_all(root.join("app/api/users"))?;
fs::write(root.join("contracts/user.zod.ts"), CONTRACT)?;
fs::write(root.join("app/api/users/route.ts"), COMPLIANT)?;
git(root, &["init", "-q", "."])?;
git(root, &["add", "-A"])?;
Ok(dir)
}
fn check_staged(
dir: &Path,
json: bool,
) -> Result<(Option<i32>, String, String), Box<dyn std::error::Error>> {
let mut args = vec!["check", "--staged"];
if json {
args.push("--json");
}
let output = Command::cargo_bin("pushkin")?
.current_dir(dir)
.env("PATH", format!("{}:/usr/bin:/bin", binary_dir()?))
.env("HERMES_HOME", dir.join(".hermes-home"))
.args(args)
.output()?;
Ok((
output.status.code(),
String::from_utf8_lossy(&output.stdout).into_owned(),
String::from_utf8_lossy(&output.stderr).into_owned(),
))
}
#[test]
fn a_staged_protected_file_prints_the_advisory_without_blocking() -> TestResult {
let dir = staged_repo()?;
let (code, _, stderr) = check_staged(dir.path(), false)?;
assert_eq!(
code,
Some(0),
"the advisory never blocks — a human commit of the manifest is \
legitimate: {stderr}"
);
assert!(
stderr.contains("protected surface staged") && stderr.contains("pushkin.toml"),
"the notice is loud and names the file: {stderr}"
);
assert!(
!stderr.contains("failing open"),
"a floor that RAN is never described as failing open: {stderr}"
);
Ok(())
}
#[test]
fn the_json_verdict_stays_parseable_beside_the_notice() -> TestResult {
let dir = staged_repo()?;
let (code, stdout, stderr) = check_staged(dir.path(), true)?;
assert_eq!(code, Some(0), "advisory only: {stderr}");
let verdict: serde_json::Value = serde_json::from_str(&stdout)?;
assert_eq!(
verdict["decision"], "allow",
"the envelope is unchanged by the advisory: {stdout}"
);
assert!(
stderr.contains("protected surface staged"),
"the notice travels on stderr, never inside the envelope: {stderr}"
);
Ok(())
}
#[test]
fn no_staged_protected_file_means_no_notice() -> TestResult {
let dir = staged_repo()?;
git(
dir.path(),
&[
"-c",
"user.name=Notice Suite",
"-c",
"user.email=notice@test",
"commit",
"-qm",
"fixture",
],
)?;
fs::write(
dir.path().join("app/api/users/route.ts"),
format!("{COMPLIANT}// touched\n"),
)?;
git(dir.path(), &["add", "app/api/users/route.ts"])?;
let (code, _, stderr) = check_staged(dir.path(), false)?;
assert_eq!(code, Some(0), "compliant route stages clean: {stderr}");
assert!(
!stderr.contains("protected surface staged"),
"no protected file staged, no notice: {stderr}"
);
Ok(())
}