use cleanlib_client::{transport, types};
use wiremock::matchers::{method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
const VERBS: &[&str] = &["verdict", "scan", "audit", "policy_preview"];
const WRAPPERS: &[&str] = &["npm", "pip", "cargo", "go"];
const ECOSYSTEMS: &[(&str, &str)] = &[
("npm", "npm"),
("pip", "pypi"),
("cargo", "crates"),
("go", "go"),
];
const STATUSES: &[&str] = &[
"ALLOWED_NO_FINDINGS",
"RISK_ACCEPTANCE_REQUIRED",
"VECTOR_VERDICT",
"INSUFFICIENT_DATA",
];
fn status_for_policy(status: &str) -> String {
match status {
"ALLOWED_NO_FINDINGS" => "ALLOW".to_string(),
"RISK_ACCEPTANCE_REQUIRED" => "WARN".to_string(),
"VECTOR_VERDICT" => "DENY".to_string(),
"INSUFFICIENT_DATA" => "INSUFFICIENT_DATA".to_string(),
other => other.to_string(),
}
}
fn fixture_pkg_for_wrapper(wrapper: &str) -> (&'static str, &'static str) {
match wrapper {
"npm" => ("cors", "2.8.5"),
"pip" => ("requests", "2.32.5"),
"cargo" => ("serde", "1.0.200"),
"go" => ("github.com/sirupsen/logrus", "v1.9.0"),
_ => unreachable!(),
}
}
#[tokio::test]
async fn matrix_64_cases() {
let mut failed: Vec<(String, String, String, String)> = Vec::new();
for verb in VERBS {
for (wrapper, ecosystem) in ECOSYSTEMS {
for status in STATUSES {
let case_id = format!("{}/{}/{}", verb, wrapper, status);
if let Err(msg) = run_case(verb, wrapper, ecosystem, status).await {
failed.push((
verb.to_string(),
wrapper.to_string(),
status.to_string(),
msg,
));
eprintln!("CASE FAIL [{}]: {}", case_id, failed.last().unwrap().3);
} else {
eprintln!("CASE PASS [{}]", case_id);
}
}
}
}
let total = VERBS.len() * WRAPPERS.len() * STATUSES.len();
assert_eq!(total, 64, "matrix dimensions changed; update assertion");
if !failed.is_empty() {
for (v, w, s, m) in &failed {
eprintln!("FAIL ({}, {}, {}): {}", v, w, s, m);
}
panic!(
"{} of {} integration cases failed",
failed.len(),
total
);
}
}
async fn run_case(
verb: &str,
wrapper: &str,
ecosystem: &str,
status: &str,
) -> Result<(), String> {
let server = MockServer::start().await;
let (pkg, version) = fixture_pkg_for_wrapper(wrapper);
match verb {
"verdict" => {
let resp = ResponseTemplate::new(200).set_body_json(serde_json::json!({
"verdict_id": format!("01J-{}-{}", wrapper, status),
"verdict": status,
"source": status,
"confidence": 0.9,
"composite_score": match status {
"VECTOR_VERDICT" => 90,
"RISK_ACCEPTANCE_REQUIRED" => 60,
_ => 5,
},
"reasoning": format!("synthetic {} case", status),
"computed_at": "2026-05-28T00:00:00Z"
}));
Mock::given(method("GET"))
.and(path_regex(r"^/v1/customer/verdicts/"))
.respond_with(resp)
.mount(&server)
.await;
let client = transport::Client::new(&server.uri(), Some("test-bearer".into()))
.map_err(|e| e.to_string())?;
let v = client
.fetch_verdict(ecosystem, pkg, version)
.await
.map_err(|e| e.to_string())?;
if v.verdict != status {
return Err(format!("expected verdict={} got {}", status, v.verdict));
}
Ok(())
}
"scan" | "policy_preview" => {
let decision = status_for_policy(status);
let resp = ResponseTemplate::new(200).set_body_json(serde_json::json!({
"decisions": [{
"ecosystem": ecosystem,
"package": pkg,
"version": version,
"decision": decision.clone(),
"reason": format!("{} reason", decision)
}]
}));
Mock::given(method("POST"))
.and(path_regex(r"^/v1/customer/policy/preview"))
.respond_with(resp)
.mount(&server)
.await;
let client = transport::Client::new(&server.uri(), Some("test-bearer".into()))
.map_err(|e| e.to_string())?;
let req = types::PolicyPreviewRequest {
packages: vec![types::PackageRef {
ecosystem: ecosystem.to_string(),
name: pkg.to_string(),
version: version.to_string(),
}],
policy: None,
};
let r = client.policy_preview(&req).await.map_err(|e| e.to_string())?;
if r.decisions.len() != 1 {
return Err(format!("expected 1 decision; got {}", r.decisions.len()));
}
if r.decisions[0].decision != decision {
return Err(format!(
"expected decision={} got {}",
decision, r.decisions[0].decision
));
}
Ok(())
}
"audit" => {
let decision = status_for_policy(status);
let resp = ResponseTemplate::new(200).set_body_json(serde_json::json!({
"entries": [{
"request_id": format!("req-{}-{}", wrapper, status),
"at": "2026-05-28T00:00:00Z",
"ecosystem": ecosystem,
"package": pkg,
"version": version,
"decision": decision,
"reason": "audit synthetic"
}]
}));
Mock::given(method("GET"))
.and(path_regex(r"^/v1/customer/audit"))
.respond_with(resp)
.mount(&server)
.await;
let client = transport::Client::new(&server.uri(), Some("test-bearer".into()))
.map_err(|e| e.to_string())?;
let r = client
.audit(None, Some(&decision), Some(ecosystem))
.await
.map_err(|e| e.to_string())?;
if r.entries.len() != 1 {
return Err(format!("expected 1 entry; got {}", r.entries.len()));
}
if r.entries[0].decision != decision {
return Err(format!(
"expected entry decision={} got {}",
decision, r.entries[0].decision
));
}
Ok(())
}
other => Err(format!("unknown verb {}", other)),
}
}