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 comment a splice must not disturb
[gates]
protected_paths = ["pushkin.toml"]
"#;
const NONCONFORMING: &str = "export async function POST(req: Request) {\n\
const body = await req.json();\n\
return Response.json({ name: body.name });\n\
}\n";
const ROUTE: &str = "app/api/users/route.ts";
fn disabled_manifest() -> String {
format!("{MANIFEST}\n[features]\ngit_hooks = false\n")
}
fn repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = tempfile::tempdir()?;
fs::write(dir.path().join("pushkin.toml"), MANIFEST)?;
Ok(dir)
}
fn git_repo() -> Result<tempfile::TempDir, Box<dyn std::error::Error>> {
let dir = repo()?;
for args in [
["init", "--initial-branch=main"].as_slice(),
["config", "user.email", "test@example.invalid"].as_slice(),
["config", "user.name", "Pushkin Test"].as_slice(),
["config", "commit.gpgsign", "false"].as_slice(),
] {
git(dir.path(), args)?;
}
Ok(dir)
}
fn git(dir: &Path, args: &[&str]) -> TestResult {
let output = StdCommand::new("git")
.args(args)
.current_dir(dir)
.output()?;
assert!(
output.status.success(),
"git {args:?}: {}",
String::from_utf8_lossy(&output.stderr)
);
Ok(())
}
struct Verdict {
code: Option<i32>,
stdout: String,
stderr: String,
}
fn run(dir: &Path, args: &[&str]) -> Result<Verdict, Box<dyn std::error::Error>> {
let output = Command::cargo_bin("pushkin")?
.current_dir(dir)
.args(args)
.output()?;
Ok(Verdict {
code: output.status.code(),
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
})
}
fn manifest_text(dir: &Path) -> Result<String, Box<dyn std::error::Error>> {
Ok(fs::read_to_string(dir.join("pushkin.toml"))?)
}
#[test]
fn disable_records_the_flag_and_removes_floor_and_shim() -> TestResult {
let dir = git_repo()?;
let installed_floor = run(dir.path(), &["init", "--agent", "lefthook"])?;
assert_eq!(installed_floor.code, Some(0), "{}", installed_floor.stderr);
let installed_shim = run(dir.path(), &["init", "--agent", "git"])?;
assert_eq!(installed_shim.code, Some(0), "{}", installed_shim.stderr);
assert!(dir.path().join(".git/hooks/pre-commit").exists());
let verdict = run(dir.path(), &["disable", "git-hooks"])?;
assert_eq!(
verdict.code,
Some(0),
"{}{}",
verdict.stdout,
verdict.stderr
);
let manifest = manifest_text(dir.path())?;
assert!(manifest.contains("[features]"), "flag recorded: {manifest}");
assert!(manifest.contains("git_hooks = false"));
assert!(
!dir.path().join("lefthook.yml").exists()
|| !fs::read_to_string(dir.path().join("lefthook.yml"))?.contains("pushkin"),
"the pushkin floor is gone"
);
assert!(
!dir.path().join(".git/hooks/pre-commit").exists(),
"the native shim is gone"
);
Ok(())
}
#[test]
fn disable_preserves_manifest_comments_and_reparses() -> TestResult {
let dir = repo()?;
let verdict = run(dir.path(), &["disable", "git-hooks"])?;
assert_eq!(
verdict.code,
Some(0),
"{}{}",
verdict.stdout,
verdict.stderr
);
let manifest = manifest_text(dir.path())?;
assert!(
manifest.contains("# gates comment a splice must not disturb"),
"comments survive the splice: {manifest}"
);
assert!(manifest.contains("protected_paths = [\"pushkin.toml\"]"));
Ok(())
}
#[test]
fn disable_then_enable_round_trips_to_one_flag_line() -> TestResult {
let dir = repo()?;
run(dir.path(), &["disable", "git-hooks"])?;
run(dir.path(), &["enable", "git-hooks"])?;
let manifest = manifest_text(dir.path())?;
assert_eq!(
manifest.matches("git_hooks").count(),
1,
"repeat toggles rewrite one line, never stack: {manifest}"
);
assert!(manifest.contains("git_hooks = true"));
Ok(())
}
#[test]
fn disable_without_a_manifest_is_a_loud_error() -> TestResult {
let dir = tempfile::tempdir()?;
let verdict = run(dir.path(), &["disable", "git-hooks"])?;
assert_ne!(verdict.code, Some(0));
assert!(
verdict.stderr.contains("pushkin.toml"),
"the error names the missing manifest: {}",
verdict.stderr
);
Ok(())
}
#[test]
fn unknown_feature_is_named_with_the_known_set() -> TestResult {
let dir = repo()?;
let verdict = run(dir.path(), &["enable", "telemetry"])?;
assert_ne!(verdict.code, Some(0));
assert!(
verdict.stderr.contains("telemetry") && verdict.stderr.contains("git-hooks"),
"unknown feature errors name the known set: {}",
verdict.stderr
);
Ok(())
}
#[test]
fn init_lefthook_refuses_while_disabled() -> TestResult {
let dir = repo()?;
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let verdict = run(dir.path(), &["init", "--agent", "lefthook"])?;
assert_ne!(verdict.code, Some(0), "install must refuse");
assert!(
verdict.stderr.contains("pushkin enable git-hooks"),
"the refusal names the re-enable verb: {}",
verdict.stderr
);
assert!(
!dir.path().join("lefthook.yml").exists(),
"nothing was installed"
);
Ok(())
}
#[test]
fn init_git_shim_refuses_while_disabled() -> TestResult {
let dir = git_repo()?;
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let verdict = run(dir.path(), &["init", "--agent", "git"])?;
assert_ne!(verdict.code, Some(0), "install must refuse");
assert!(verdict.stderr.contains("pushkin enable git-hooks"));
assert!(!dir.path().join(".git/hooks/pre-commit").exists());
Ok(())
}
#[test]
fn enable_reallows_install_without_installing_itself() -> TestResult {
let dir = repo()?;
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let enabled = run(dir.path(), &["enable", "git-hooks"])?;
assert_eq!(
enabled.code,
Some(0),
"{}{}",
enabled.stdout,
enabled.stderr
);
assert!(
!dir.path().join("lefthook.yml").exists(),
"enable records the flag; install stays the explicit init path"
);
let installed = run(dir.path(), &["init", "--agent", "lefthook"])?;
assert_eq!(installed.code, Some(0), "{}", installed.stderr);
assert!(fs::read_to_string(dir.path().join("lefthook.yml"))?.contains("pushkin:begin"));
Ok(())
}
#[test]
fn other_agent_packs_are_out_of_the_flag_scope() -> TestResult {
let dir = repo()?;
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let verdict = run(dir.path(), &["init", "--agent", "claude"])?;
assert_eq!(
verdict.code,
Some(0),
"agent write-time packs install regardless of the git-plane switch: {}",
verdict.stderr
);
assert!(dir.path().join(".claude/settings.json").exists());
Ok(())
}
#[test]
fn staged_check_blocks_when_enabled_and_skips_when_disabled() -> TestResult {
let dir = git_repo()?;
let route = dir.path().join(ROUTE);
if let Some(parent) = route.parent() {
fs::create_dir_all(parent)?;
}
fs::write(&route, NONCONFORMING)?;
git(dir.path(), &["add", ROUTE])?;
let enabled = run(dir.path(), &["check", "--staged", "--json"])?;
assert_eq!(
enabled.code,
Some(2),
"{}{}",
enabled.stdout,
enabled.stderr
);
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let disabled = run(dir.path(), &["check", "--staged", "--json"])?;
assert_eq!(
disabled.code,
Some(0),
"disabled plane passes the same staged violation: {}{}",
disabled.stdout,
disabled.stderr
);
assert!(
disabled.stderr.contains("git hooks disabled"),
"the skip is a notice, never silence: {}",
disabled.stderr
);
Ok(())
}
#[test]
fn stdin_write_gate_ignores_the_switch() -> TestResult {
let dir = repo()?;
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let payload = format!(
"{{\"tool_name\":\"Write\",\"tool_input\":{{\"file_path\":\"{ROUTE}\",\"content\":{}}}}}",
serde_json::to_string(NONCONFORMING)?
);
let output = Command::cargo_bin("pushkin")?
.current_dir(dir.path())
.args(["check"])
.write_stdin(payload)
.output()?;
assert_eq!(
output.status.code(),
Some(2),
"write-time gating is out of the flag's scope: {}",
String::from_utf8_lossy(&output.stdout)
);
Ok(())
}
fn doctor(dir: &Path, args: &[&str]) -> Result<Verdict, Box<dyn std::error::Error>> {
let mut command = Command::cargo_bin("pushkin")?;
command
.current_dir(dir)
.env("HERMES_HOME", dir.join(".hermes-home"))
.args(args);
if let Some(binary_dir) = Path::new(env!("CARGO_BIN_EXE_pushkin")).parent() {
let ambient = std::env::var("PATH").unwrap_or_default();
command.env(
"PATH",
format!("{}:{ambient}", binary_dir.to_string_lossy()),
);
}
let output = command.output()?;
Ok(Verdict {
code: output.status.code(),
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
})
}
fn baseline_packs(dir: &Path) -> TestResult {
let verdict = run(dir, &["init"])?;
assert_eq!(verdict.code, Some(0), "{}", verdict.stderr);
Ok(())
}
#[test]
fn doctor_reports_disabled_as_healthy_when_nothing_is_installed() -> TestResult {
let dir = repo()?;
baseline_packs(dir.path())?;
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let verdict = doctor(dir.path(), &["doctor"])?;
assert_eq!(
verdict.code,
Some(0),
"{}{}",
verdict.stdout,
verdict.stderr
);
assert!(
verdict.stdout.contains("lefthook: git hooks disabled")
&& verdict.stdout.contains("git shim: git hooks disabled"),
"doctor names the disabled plane instead of checking it: {}",
verdict.stdout
);
Ok(())
}
#[test]
fn doctor_flags_a_floor_still_installed_while_disabled_and_repair_removes_it() -> TestResult {
let dir = git_repo()?;
baseline_packs(dir.path())?;
let installed = run(dir.path(), &["init", "--agent", "lefthook"])?;
assert_eq!(installed.code, Some(0), "{}", installed.stderr);
fs::write(dir.path().join("pushkin.toml"), disabled_manifest())?;
let unhealthy = doctor(dir.path(), &["doctor"])?;
assert_eq!(
unhealthy.code,
Some(1),
"still-installed is the finding: {}",
unhealthy.stdout
);
assert!(
unhealthy.stdout.contains("still"),
"the finding names the leftover floor: {}",
unhealthy.stdout
);
let repaired = doctor(dir.path(), &["doctor", "--repair"])?;
assert!(
!fs::read_to_string(dir.path().join("lefthook.yml"))
.unwrap_or_default()
.contains("pushkin:begin"),
"repair removed the floor: {}{}",
repaired.stdout,
repaired.stderr
);
let healthy = doctor(dir.path(), &["doctor"])?;
assert_eq!(
healthy.code,
Some(0),
"after removal the disabled plane is healthy: {}",
healthy.stdout
);
Ok(())
}