use cleanlib_client::transport;
use wiremock::matchers::{method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
#[tokio::test]
async fn wrappers_compose_with_verdict() {
struct Case {
wrapper: &'static str,
argv: Vec<&'static str>,
expected_eco: &'static str,
expected_pkg: &'static str,
expected_ver: &'static str,
}
let cases = vec![
Case {
wrapper: "npm",
argv: vec!["npm", "install", "cors@2.8.5"],
expected_eco: "npm",
expected_pkg: "cors",
expected_ver: "2.8.5",
},
Case {
wrapper: "pip",
argv: vec!["pip", "install", "requests==2.32.5"],
expected_eco: "pypi",
expected_pkg: "requests",
expected_ver: "2.32.5",
},
Case {
wrapper: "cargo",
argv: vec!["cargo", "add", "serde@1.0.200"],
expected_eco: "crates",
expected_pkg: "serde",
expected_ver: "1.0.200",
},
Case {
wrapper: "go",
argv: vec!["go", "get", "github.com/sirupsen/logrus@v1.9.0"],
expected_eco: "go",
expected_pkg: "github.com/sirupsen/logrus",
expected_ver: "v1.9.0",
},
];
for case in cases {
let triple = parse_with_wrapper(case.wrapper, &case.argv);
let (eco, name, ver) = triple
.first()
.map(|t| (t.0.as_str(), t.1.as_str(), t.2.as_str()))
.unwrap_or_else(|| panic!("wrapper {} produced no packages", case.wrapper));
assert_eq!(eco, case.expected_eco, "wrapper {} eco", case.wrapper);
assert_eq!(name, case.expected_pkg, "wrapper {} pkg", case.wrapper);
assert_eq!(ver, case.expected_ver, "wrapper {} ver", case.wrapper);
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path_regex(r"^/v1/customer/verdicts/"))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"verdict_id": "round-trip",
"verdict": "ALLOWED_NO_FINDINGS",
"source": "ALLOWED_NO_FINDINGS"
})))
.mount(&server)
.await;
let client = transport::Client::new(&server.uri(), Some("t".into())).unwrap();
let v = client.fetch_verdict(eco, name, ver).await.unwrap();
assert_eq!(v.verdict_id, "round-trip", "wrapper {} round-trip", case.wrapper);
}
}
fn parse_with_wrapper(wrapper: &str, argv: &[&str]) -> Vec<(String, String, String)> {
match wrapper {
"npm" => {
let mut out = Vec::new();
let verb_pos = argv.iter().position(|a| matches!(*a, "install" | "i" | "add"));
if let Some(p) = verb_pos {
for arg in argv.iter().skip(p + 1) {
if arg.starts_with('-') || arg.is_empty() {
continue;
}
let (name, ver) = npm_split(arg);
out.push(("npm".to_string(), name, ver));
}
}
out
}
"pip" => {
let mut out = Vec::new();
let verb_pos = argv.iter().position(|a| *a == "install");
if let Some(p) = verb_pos {
for arg in argv.iter().skip(p + 1) {
if arg.starts_with('-') || arg.is_empty() {
continue;
}
if let Some((n, v)) = arg.split_once("==") {
out.push(("pypi".to_string(), n.trim().to_string(), v.trim().to_string()));
} else {
out.push(("pypi".to_string(), arg.to_string(), "latest".to_string()));
}
}
}
out
}
"cargo" => {
let mut out = Vec::new();
let verb_pos = argv.iter().position(|a| *a == "add");
if let Some(p) = verb_pos {
for arg in argv.iter().skip(p + 1) {
if arg.starts_with('-') || arg.is_empty() {
continue;
}
let (n, v) = match arg.split_once('@') {
Some((n, v)) if !n.is_empty() && !v.is_empty() => {
(n.to_string(), v.to_string())
}
_ => (arg.to_string(), "latest".to_string()),
};
out.push(("crates".to_string(), n, v));
}
}
out
}
"go" => {
let mut out = Vec::new();
let verb_pos = argv.iter().position(|a| *a == "get");
if let Some(p) = verb_pos {
for arg in argv.iter().skip(p + 1) {
if arg.starts_with('-') || arg.is_empty() {
continue;
}
let (n, v) = match arg.rsplit_once('@') {
Some((n, v)) if !n.is_empty() && !v.is_empty() => {
(n.to_string(), v.to_string())
}
_ => (arg.to_string(), "latest".to_string()),
};
out.push(("go".to_string(), n, v));
}
}
out
}
_ => Vec::new(),
}
}
fn npm_split(spec: &str) -> (String, String) {
if let Some(rest) = spec.strip_prefix('@') {
match rest.split_once('/') {
Some((scope, after)) => match after.split_once('@') {
Some((name, ver)) => (format!("@{}/{}", scope, name), ver.to_string()),
None => (format!("@{}/{}", scope, after), "latest".to_string()),
},
None => (spec.to_string(), "latest".to_string()),
}
} else {
match spec.rsplit_once('@') {
Some((n, v)) if !n.is_empty() && !v.is_empty() => (n.to_string(), v.to_string()),
_ => (spec.to_string(), "latest".to_string()),
}
}
}