use assert_cmd::Command;
use std::fs;
use std::path::Path;
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"]
"#;
fn repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
fs::write(dir.path().join("pushkin.toml"), MANIFEST)?;
Ok(dir)
}
struct Run {
code: i32,
stdout: String,
}
fn pushkin(
dir: &Path,
args: &[&str],
stdin: Option<&str>,
) -> Result<Run, Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("pushkin")?;
cmd.current_dir(dir).env("PUSHKIN_DAEMON", "off").args(args);
if let Some(payload) = stdin {
cmd.write_stdin(payload.to_owned());
}
let out = cmd.output()?;
Ok(Run {
code: out.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
})
}
const NONCONFORMING: &str = r#"{"session_id":"c","tool_name":"Write","tool_input":{"file_path":"app/api/users/route.ts","content":"export async function POST(req){ return Response.json(await req.json()); }"}}"#;
#[test]
fn check_json_blocks_a_nonconforming_write_with_the_envelope() -> TestResult {
let dir = repo()?;
let run = pushkin(dir.path(), &["check", "--json"], Some(NONCONFORMING))?;
assert_eq!(run.code, 2, "nonconforming write blocks: {}", run.stdout);
assert!(
run.stdout.contains("contract.boundary.unvalidated_input")
&& run.stdout.contains("\"block\""),
"the --json envelope names the rule: {}",
run.stdout
);
Ok(())
}
#[test]
fn check_allows_a_conforming_write() -> TestResult {
let dir = repo()?;
let ok = r##"{"session_id":"c","tool_name":"Write","tool_input":{"file_path":"README.md","content":"# docs"}}"##;
let run = pushkin(dir.path(), &["check"], Some(ok))?;
assert_eq!(run.code, 0, "conforming write allows: {}", run.stdout);
Ok(())
}
#[test]
fn check_staged_skips_when_nothing_is_staged() -> TestResult {
let dir = repo()?;
std::process::Command::new("git")
.args(["init", "-q"])
.current_dir(dir.path())
.output()?;
let run = pushkin(dir.path(), &["check", "--staged"], None)?;
assert_eq!(run.code, 0, "empty staged sweep is exit 0: {}", run.stdout);
Ok(())
}
#[test]
fn instructions_digest_is_capped_and_has_facts() -> TestResult {
let dir = repo()?;
let run = pushkin(dir.path(), &["instructions", "--digest"], None)?;
assert_eq!(run.code, 0);
assert!(
run.stdout.len() <= 2048,
"digest is ≤2 KiB: {} bytes",
run.stdout.len()
);
assert!(
run.stdout.contains("Gated paths:"),
"digest carries the facts block: {}",
run.stdout
);
Ok(())
}
#[test]
fn instructions_for_subagent_is_the_compact_variant() -> TestResult {
let dir = repo()?;
let run = pushkin(dir.path(), &["instructions", "--for-subagent"], None)?;
assert_eq!(run.code, 0);
assert!(
run.stdout.contains("This repository is gated by Pushkin"),
"compact variant prose: {}",
run.stdout
);
Ok(())
}
#[test]
fn instructions_full_is_the_default() -> TestResult {
let dir = repo()?;
let run = pushkin(dir.path(), &["instructions"], None)?;
assert_eq!(run.code, 0);
assert!(
run.stdout
.contains("gate instructions (generated from pushkin.toml)"),
"full variant header: {}",
run.stdout
);
Ok(())
}
#[test]
fn board_register_and_status_round_trip_through_the_repack() -> TestResult {
let dir = repo()?;
let reg = pushkin(
dir.path(),
&["board", "register", "--run", "r1", "--agent", "a1"],
None,
)?;
assert_eq!(reg.code, 0, "register: {}", reg.stdout);
assert!(
reg.stdout.contains("a1 registered in r1"),
"register prose: {}",
reg.stdout
);
let status = pushkin(
dir.path(),
&[
"board",
"status",
"--message",
"working",
"--run",
"r1",
"--agent",
"a1",
],
None,
)?;
assert_eq!(status.code, 0, "status: {}", status.stdout);
assert!(
status.stdout.contains("status recorded"),
"status prose: {}",
status.stdout
);
Ok(())
}