use serde_json::Value;
use std::process::{Command, Output};
const SECRET_RULE_ID: &str = "security.secret-candidate";
const SECRET_RULE_FIXTURES_TOTAL: i64 = 4;
const SECRET_RULE_EXPECTED_FINDINGS: i64 = 3;
fn repopilot() -> Command {
Command::new(env!("CARGO_BIN_EXE_repopilot"))
}
fn run_repopilot(args: &[&str]) -> Output {
repopilot().args(args).output().expect("run repopilot")
}
fn parse_json_stdout(output: &Output, context: &str) -> Value {
assert!(
output.status.success(),
"{context} failed\nstdout:\n{}\nstderr:\n{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_slice(&output.stdout).unwrap_or_else(|error| {
panic!(
"failed to parse {context} JSON: {error}\nstdout:\n{}",
String::from_utf8_lossy(&output.stdout)
)
})
}
#[test]
fn given_rule_catalog_when_inspect_rules_runs_then_metadata_and_filters_are_reported() {
let json = parse_json_stdout(
&run_repopilot(&["inspect", "rules", "--format", "json"]),
"inspect rules",
);
let rules = json["rules"].as_array().expect("rules array");
assert!(rules.iter().any(|rule| rule["rule_id"] == SECRET_RULE_ID));
let secret_rule = rules
.iter()
.find(|rule| rule["rule_id"] == SECRET_RULE_ID)
.expect("secret rule should be present");
assert_eq!(secret_rule["semantic_source"], "text-heuristic");
assert_eq!(secret_rule["required_scope"], "file-content");
assert_eq!(
secret_rule["fixture_coverage"]["fixtures_total"],
SECRET_RULE_FIXTURES_TOTAL
);
assert_eq!(secret_rule["stability_gate_status"], "fixture-covered");
let preview_json = parse_json_stdout(
&run_repopilot(&[
"inspect",
"rules",
"--lifecycle",
"preview",
"--format",
"json",
]),
"inspect rules preview",
);
assert!(
preview_json["rules"]
.as_array()
.expect("preview rules")
.iter()
.all(|rule| rule["lifecycle"] == "preview")
);
let source_json = parse_json_stdout(
&run_repopilot(&[
"inspect",
"rules",
"--source",
"text-heuristic",
"--format",
"json",
]),
"inspect rules source",
);
assert!(
source_json["rules"]
.as_array()
.expect("source rules")
.iter()
.all(|rule| rule["signal_source"] == "text-heuristic")
);
}
#[test]
fn given_rule_id_when_inspect_rule_runs_then_known_rule_succeeds_and_unknown_rule_fails() {
let json = parse_json_stdout(
&run_repopilot(&["inspect", "rule", SECRET_RULE_ID, "--format", "json"]),
"inspect rule",
);
assert_eq!(json["rule_id"], SECRET_RULE_ID);
assert_eq!(json["lifecycle"], "preview");
assert_eq!(json["false_positive_risk"], "high");
let unknown = run_repopilot(&["inspect", "rule", "missing.rule"]);
assert_eq!(unknown.status.code(), Some(2));
let stderr = String::from_utf8_lossy(&unknown.stderr);
assert!(stderr.contains("Unknown RepoPilot rule `missing.rule`"));
}
#[test]
fn given_secret_rule_fixtures_when_eval_rules_runs_then_fixture_quality_is_reported() {
let json = parse_json_stdout(
&run_repopilot(&[
"inspect",
"eval-rules",
"--rule",
SECRET_RULE_ID,
"--format",
"json",
]),
"inspect eval-rules",
);
assert_eq!(json["rules_evaluated"], 1);
assert_eq!(json["fixtures_total"], SECRET_RULE_FIXTURES_TOTAL);
assert_eq!(json["expected_findings"], SECRET_RULE_EXPECTED_FINDINGS);
assert_eq!(json["actual_findings"], SECRET_RULE_EXPECTED_FINDINGS);
assert_eq!(json["missing_findings"], 0);
assert_eq!(json["unexpected_findings"], 0);
assert_eq!(json["contract_violations"], 0);
assert_eq!(json["stable_id_failures"], 0);
assert_eq!(json["quality_gate_failures"], 0);
let rules = json["rules"].as_array().expect("per-rule details");
assert_eq!(rules.len(), 1);
assert_eq!(rules[0]["rule_id"], SECRET_RULE_ID);
assert_eq!(rules[0]["has_true_positive_fixture"], true);
assert_eq!(rules[0]["has_false_positive_fixture"], true);
assert_eq!(rules[0]["fixtures_total"], SECRET_RULE_FIXTURES_TOTAL);
assert_eq!(rules[0]["expected_findings"], SECRET_RULE_EXPECTED_FINDINGS);
assert_eq!(rules[0]["actual_findings"], SECRET_RULE_EXPECTED_FINDINGS);
assert_eq!(rules[0]["missing_findings"], 0);
assert_eq!(rules[0]["unexpected_findings"], 0);
assert_eq!(rules[0]["contract_violations"], 0);
assert_eq!(rules[0]["stable_id_failures"], 0);
assert_eq!(rules[0]["quality_gate_failures"], 0);
}