#![cfg(feature = "gui")]
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{SystemTime, UNIX_EPOCH};
use holger_ui::data::UiData;
use holger_ui::modgunn::core::Severity;
use holger_ui::modgunn::scan::Advisory;
use holger_ui::modgunn::warehouse::Warehouse;
use server_lib::exposed::fast_routes::FastRoutes;
use server_lib::LocalHolger;
use traits::{ArtifactFormat, ArtifactId, HolgerObject, RepositoryBackendTrait};
struct MemRepo {
name: String,
store: Mutex<HashMap<(Option<String>, String, String), Vec<u8>>>,
}
impl MemRepo {
fn new(name: &str) -> Self {
Self { name: name.to_string(), store: Mutex::new(HashMap::new()) }
}
}
impl RepositoryBackendTrait for MemRepo {
fn name(&self) -> &str {
&self.name
}
fn format(&self) -> ArtifactFormat {
ArtifactFormat::Rust
}
fn is_writable(&self) -> bool {
true
}
fn fetch(&self, id: &ArtifactId) -> anyhow::Result<Option<Vec<u8>>> {
let k = (id.namespace.clone(), id.name.clone(), id.version.clone());
Ok(self.store.lock().unwrap().get(&k).cloned())
}
fn put(&self, id: &ArtifactId, data: &[u8]) -> anyhow::Result<()> {
let k = (id.namespace.clone(), id.name.clone(), id.version.clone());
self.store.lock().unwrap().insert(k, data.to_vec());
Ok(())
}
fn list(&self, _f: Option<&str>, _l: usize) -> anyhow::Result<Vec<traits::ArtifactEntry>> {
Ok(Vec::new())
}
fn archive_files(&self, _prefix: Option<&str>) -> anyhow::Result<Vec<String>> {
Ok(vec!["serde-1.0.0.crate".to_string(), "tokio-1.20.0.crate".to_string()])
}
fn archive_info(&self) -> anyhow::Result<traits::ArchiveInfo> {
Ok(traits::ArchiveInfo { file_count: 2, total_uncompressed_bytes: 0, archive_path: self.name.clone() })
}
fn handle_http2_request(
&self,
_m: &str,
_s: &str,
_b: &[u8],
) -> anyhow::Result<(u16, Vec<(String, String)>, Vec<u8>)> {
Ok((404, Vec::new(), b"not found".to_vec()))
}
}
#[test]
fn tools3_scan_uses_warehouse_advisories_when_configured() {
let nanos = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos();
let root = std::env::temp_dir().join(format!("holger-ui-wh-scan-{nanos}"));
std::fs::create_dir_all(&root).unwrap();
{
let wh = Warehouse::open(&root).unwrap();
wh.ensure_modgunn_tables().unwrap();
wh.append_advisories(
"demo-rev-2026",
&[Advisory::basic(
"RUSTSEC-DEMO-0001",
"cargo",
"serde",
vec!["1.0.0".into()],
Severity::High,
"demo: serde 1.0.0 flagged from the warehouse",
)],
)
.unwrap();
}
std::env::set_var("HOLGER_WAREHOUSE", &root);
let repo: Arc<dyn RepositoryBackendTrait> = Arc::new(MemRepo::new("rust-dev"));
let routes = FastRoutes::new(vec![("rust-dev".to_string(), repo)]);
let holger: Arc<dyn HolgerObject> = Arc::new(LocalHolger::new(routes));
let mut ui = UiData::new(holger).expect("runtime");
ui.run_security_scan("rust-dev", "rust");
let s = &ui.security;
std::env::remove_var("HOLGER_WAREHOUSE");
assert!(s.error.is_none(), "scan error: {:?}", s.error);
assert!(s.loaded, "scan populated the view");
assert!(
s.source.starts_with("warehouse rev 'demo-rev-2026'"),
"advisories sourced from the warehouse, got: {:?}",
s.source
);
assert_eq!(s.scanned, 2, "both crates scanned");
assert_eq!(s.block, 1, "serde 1.0.0 blocked by the warehouse advisory");
assert_eq!(s.decision, "block");
assert!(
s.findings.iter().any(|f| f.name == "serde" && f.ids.iter().any(|i| i == "RUSTSEC-DEMO-0001")),
"the serde finding carries the warehouse advisory id: {:?}",
s.findings
);
std::fs::remove_dir_all(&root).ok();
}