use std::path::PathBuf;
use agent_plugins::{load_dir, McpOutcome};
use serde_json::Value;
fn fixture_cases() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("fixtures/cases")
}
#[test]
fn conformance_corpus() {
let mut ran = 0;
let mut failures = Vec::new();
let mut cases: Vec<_> = std::fs::read_dir(fixture_cases())
.expect("fixture corpus present")
.map(|e| e.expect("readable fixture entry").path())
.filter(|p| p.is_dir())
.collect();
cases.sort();
for case_dir in cases {
let case = case_dir.file_name().unwrap().to_string_lossy().into_owned();
let expected: Value = serde_json::from_slice(
&std::fs::read(case_dir.join("expected.json")).expect("expected.json present"),
)
.expect("expected.json is valid JSON");
if let Err(problem) = check_case(&case_dir.join("plugin"), &expected) {
failures.push(format!("{case}: {problem}"));
}
ran += 1;
}
assert!(ran > 0, "no fixture cases found");
assert!(
failures.is_empty(),
"{} of {ran} conformance case(s) failed:\n {}",
failures.len(),
failures.join("\n ")
);
}
fn check_case(plugin_dir: &std::path::Path, expected: &Value) -> Result<(), String> {
let outcome = load_dir(plugin_dir);
match expected["outcome"].as_str().expect("outcome field") {
"rejected" => {
let rejection = match outcome {
Err(rejection) => rejection,
Ok(plugin) => {
return Err(format!(
"expected rejection, but plugin {:?} loaded",
plugin.manifest.name.as_str()
))
}
};
let want = expected["reason"].as_str().expect("reason field");
if rejection.code() != want {
return Err(format!(
"expected rejection reason {want}, got {} ({rejection})",
rejection.code()
));
}
}
"loaded" => {
let plugin = outcome.map_err(|r| format!("unexpected rejection: {r}"))?;
let want_name = expected["name"].as_str().expect("name field");
if plugin.manifest.name.as_str() != want_name {
return Err(format!("expected name {want_name}, got {}", plugin.manifest.name));
}
let got_skills: Vec<&str> =
plugin.skills.iter().map(|s| s.meta.name.as_str()).collect();
let want_skills: Vec<&str> = expected["skills"]
.as_array()
.expect("skills field")
.iter()
.map(|v| v.as_str().unwrap())
.collect();
if got_skills != want_skills {
return Err(format!("expected skills {want_skills:?}, got {got_skills:?}"));
}
let (got_mcp, got_servers) = match &plugin.mcp {
McpOutcome::Absent => ("absent", Vec::new()),
McpOutcome::Disabled(_) => ("disabled", Vec::new()),
McpOutcome::Configured(config) => {
("configured", config.servers.iter().map(|e| e.name.as_str()).collect())
}
other => return Err(format!("unrecognized MCP outcome {other:?}")),
};
let want_mcp = expected["mcp"].as_str().expect("mcp field");
if got_mcp != want_mcp {
return Err(format!("expected mcp {want_mcp}, got {got_mcp}"));
}
if let Some(want_servers) = expected["servers"].as_array() {
let want_servers: Vec<&str> =
want_servers.iter().map(|v| v.as_str().unwrap()).collect();
if got_servers != want_servers {
return Err(format!("expected servers {want_servers:?}, got {got_servers:?}"));
}
}
let mut got_rules: Vec<&str> =
plugin.diagnostics.iter().map(|d| d.rule.code()).collect();
let mut want_rules: Vec<&str> = expected["diagnostics"]
.as_array()
.expect("diagnostics field")
.iter()
.map(|v| v.as_str().unwrap())
.collect();
got_rules.sort_unstable();
want_rules.sort_unstable();
if got_rules != want_rules {
return Err(format!(
"expected diagnostics {want_rules:?}, got {got_rules:?} — {:#?}",
plugin.diagnostics
));
}
}
other => panic!("unknown expected outcome {other:?}"),
}
Ok(())
}