use std::path::PathBuf;
use klasp_core::{Verdict, PLUGIN_PROTOCOL_VERSION};
fn fixtures_dir() -> PathBuf {
let manifest = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("."));
manifest.join("tests").join("fixtures").join("plugin")
}
fn path_with_fixtures() -> String {
let fixtures = fixtures_dir();
let host_path = std::env::var("PATH").unwrap_or_default();
format!("{}:{}", fixtures.display(), host_path)
}
fn run_gate_with_plugin(
plugin_name: &str,
path: &str,
extra_env: &[(&str, &str)],
) -> (i32, String) {
use std::io::Write;
use std::process::{Command, Stdio};
use tempfile::TempDir;
let tmp = TempDir::new().expect("tempdir");
let repo = tmp.path();
let toml = format!(
r#"version = 1
[gate]
agents = ["claude_code"]
[[checks]]
name = "plugin-check"
[checks.source]
type = "plugin"
name = "{plugin_name}"
"#
);
std::fs::write(repo.join("klasp.toml"), toml).expect("write klasp.toml");
let payload = r#"{"tool_name":"Bash","tool_input":{"command":"git commit -m 'test'"}}"#;
let binary = env!("CARGO_BIN_EXE_klasp");
let mut cmd = Command::new(binary);
cmd.arg("gate")
.env("KLASP_GATE_SCHEMA", "2")
.env("PATH", path)
.env("CLAUDE_PROJECT_DIR", repo)
.env("KLASP_BASE_REF", "test-base-ref")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
for (k, v) in extra_env {
cmd.env(k, v);
}
let mut child = cmd.spawn().expect("spawn klasp gate");
child
.stdin
.take()
.unwrap()
.write_all(payload.as_bytes())
.expect("write stdin");
let output = child.wait_with_output().expect("wait");
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let exit_code = output.status.code().unwrap_or(-1);
(exit_code, stderr)
}
#[test]
fn plugin_passing_returns_verdict_pass() {
let path = path_with_fixtures();
let (exit_code, stderr) = run_gate_with_plugin("mock-passing", &path, &[]);
assert_eq!(
exit_code, 0,
"expected exit 0 (pass), got {exit_code}; stderr: {stderr}"
);
}
#[test]
fn plugin_failing_returns_findings() {
let path = path_with_fixtures();
let (exit_code, stderr) = run_gate_with_plugin("mock-failing", &path, &[]);
assert_eq!(
exit_code, 2,
"expected exit 2 (fail/block), got {exit_code}; stderr: {stderr}"
);
}
#[test]
fn plugin_not_on_path_warns_and_continues() {
let empty_path = "/usr/bin:/bin";
let (exit_code, stderr) = run_gate_with_plugin("missing-plugin", empty_path, &[]);
assert_eq!(
exit_code, 0,
"expected exit 0 (warn/pass), got {exit_code}; stderr: {stderr}"
);
assert!(
stderr.contains("missing-plugin") || stderr.contains("plugin"),
"expected stderr to mention the missing plugin; stderr: {stderr}"
);
}
#[test]
fn plugin_crashing_exits_nonzero_warns() {
let path = path_with_fixtures();
let (exit_code, _stderr) = run_gate_with_plugin("mock-crashing", &path, &[]);
assert_eq!(
exit_code, 0,
"expected exit 0 (warn/pass) for crashing plugin, got {exit_code}"
);
}
#[test]
fn plugin_malformed_json_warns() {
let path = path_with_fixtures();
let (exit_code, _stderr) = run_gate_with_plugin("mock-malformed", &path, &[]);
assert_eq!(
exit_code, 0,
"expected exit 0 (warn/pass) for malformed JSON plugin, got {exit_code}"
);
}
#[test]
fn plugin_future_protocol_version_warns() {
let path = path_with_fixtures();
let (exit_code, stderr) = run_gate_with_plugin("mock-future-version", &path, &[]);
assert_eq!(
exit_code, 0,
"expected exit 0 (warn) for future protocol version, got {exit_code}; stderr: {stderr}"
);
assert!(
stderr.contains("protocol_version") || stderr.contains("plugin"),
"expected stderr to mention protocol_version mismatch; stderr: {stderr}"
);
}
#[test]
fn plugin_timeout_warns() {
let path = path_with_fixtures();
let (exit_code, _stderr) =
run_gate_with_plugin("mock-slow", &path, &[("KLASP_PLUGIN_TIMEOUT_SECS", "1")]);
assert_eq!(
exit_code, 0,
"expected exit 0 (warn/pass) after timeout, got {exit_code}"
);
}
#[test]
fn plugin_receives_klasp_env() {
let path = path_with_fixtures();
let (exit_code, stderr) = run_gate_with_plugin("mock-env-check", &path, &[]);
assert_eq!(
exit_code, 0,
"expected exit 0 (KLASP_BASE_REF was set), got {exit_code}; stderr: {stderr}"
);
}
#[test]
fn plugin_protocol_version_constant_is_zero() {
assert_eq!(
PLUGIN_PROTOCOL_VERSION, 0,
"PLUGIN_PROTOCOL_VERSION must be 0 (experimental tier)"
);
}
#[allow(unused_imports)]
use Verdict as _Verdict;