server/lib/tests/pull_through_npm_maven_crates.rs
//! **npm, Maven and cargo pull through the intake chain** (lane HOLGER-PT2,
//! `server/lib/src/pull_intake/{npm,maven,crates,ecosystems}.rs`).
//!
//! Each test runs one real Holger (four `raw` intake repositories, static-token
//! principals with duties, the job queue) in front of a local FAKE upstream that
//! serves REAL bytes fetched once from the internet and kept under
//! `tests/fixtures/pull_through/` (fetched 2026-10-24):
//!
//! * `npm/`: `sigstore 5.0.0` from registry.npmjs.org: its packument (trimmed to
//! that one version; the version document is as published, with
//! `dist.integrity`, `dist.signatures` and `dist.attestations`), its tarball,
//! its attestations (`/-/npm/v1/attestations/sigstore@5.0.0`: npm's SLSA
//! provenance, keyless, and its publish attestation, key-signed) and the
//! registry's keys (`/-/npm/v1/keys`);
//! * `maven/`: `org.hamcrest:hamcrest-core:1.3` from Maven Central (POM, its `.sha1`
//! and `.md5`, `maven-metadata.xml`) and its parent POM. The JAR and its checksums
//! are MADE here ([`hamcrest_jar`], since 2026-10-30): a committed `.jar` is a binary
//! artifact to OpenSSF Scorecard, and nothing here reads the classes inside it;
//! `org.slf4j:slf4j-api:2.0.16`'s POM with its `.sha1` and `.asc` (T9, fetched
//! 2026-10-24; the signer's key is in `provenance/tests/fixtures/maven/`);
//! * `crates/`: `cfg-if` from index.crates.io (the sparse index file,
//! `config.json`) and `cfg-if-1.0.5.crate` from static.crates.io;
//! * Go (Holger v2.0 H6): a GOPROXY (`@v/<v>.info`, `.mod`, `.zip`) over modules made
//! here: `example.com/hello`, one whose zip carries a file outside its module, and one
//! whose `.mod` is not its zip's `go.mod`.
//!
//! What the tests measure: the client's first request is its ecosystem's "not
//! yet" (`503` + `Retry-After`); the package lands INGESTED as
//! `pull-through:<source>`, is scanned (and its provenance verified) at SCANNED
//! and waits at POLICY_CHECKED for a person; the indexes (packument,
//! `maven-metadata.xml`, the sparse index file) list only AVAILABLE versions;
//! after the approval the bytes come from the SEALED store.
// A server's test, never a gateway build's (lane HOL-CLEANUP): a gateway build is a
// downstream, and a pull-through fetches from its sources, which a downstream refuses (holger-downstream-1).
#![cfg(not(feature = "gateway"))]
use std::collections::BTreeMap;
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use holger_server_lib::promote::intake::{
ArtifactState, EvidenceKind, EvidenceVerdict, IntakeChain, TransitionRequest,
};
use holger_server_lib::pull_intake::provenance::{
Demand, HolgerProvenance, Material, Outcome, Verdict, Verifier,
};
use holger_server_lib::pull_intake::{crates, maven, npm, Want};
use holger_server_lib::{read_ron_config, wire_holger, Holger};
use sha2::Digest as _;
use traits::test_support::hold_port;
const FX: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/pull_through");
const NPM_KEY_ID: &str = "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U";
const NPM_KEY: &str = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEY6Ya7W++7aUPzvMTrezH6Ycx3c+HOKYCcNGybJZSCJq/fd7Qa8uuAKtdIkUQtQiEKERhAmE5lMMJhP8OkDOa2g==";
/// npm's key before 2025-01-29 (`/-/npm/v1/keys` still lists it, with that expiry):
/// packages published before then, `is-number 7.0.0` among them, carry its signature.
const NPM_OLD_KEY_ID: &str = "SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA";
const NPM_OLD_KEY: &str = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg==";
const GITHUB: &str = "https://token.actions.githubusercontent.com";
const SIGSTORE_JS: &str =
"https://github.com/sigstore/sigstore-js/.github/workflows/release.yml@refs/heads/main";
const HAMCREST: &str = "org/hamcrest/hamcrest-core/1.3";
const CG_PULL_TOKEN: &str = "a-chainguard-pull-token-for-the-fake";
/// `hamcrest-core-1.3.jar` as the fake Central serves it: a JAR (a zip) with a manifest and
/// a source file, the same bytes every call. The tests follow its bytes through the chain
/// (digest, seal, checksums, the mirror on disk); none of them opens a class.
fn hamcrest_jar() -> Vec<u8> {
static JAR: std::sync::OnceLock<Vec<u8>> = std::sync::OnceLock::new();
JAR.get_or_init(|| {
zip_entry::fixture::zip(&[
(
"META-INF/MANIFEST.MF",
b"Manifest-Version: 1.0\r\nImplementation-Title: hamcrest-core\r\nImplementation-Version: 1.3\r\nCreated-By: holger tests\r\n\r\n",
),
(
"org/hamcrest/Matcher.java",
b"package org.hamcrest;\n\npublic interface Matcher<T> extends SelfDescribing {\n boolean matches(Object item);\n}\n",
),
("LICENSE.txt", b"BSD License: a fixture made by holger's tests, not Maven Central's bytes.\n"),
])
})
.clone()
}
/// The fixtures made here instead of read: the JAR and its Central checksums.
fn made(p: &str) -> Option<Vec<u8>> {
let jar = format!("maven/{HAMCREST}/hamcrest-core-1.3.jar");
if p == jar {
return Some(hamcrest_jar());
}
let algo = p.strip_prefix(&jar)?.strip_prefix('.')?;
maven::digest(algo, &hamcrest_jar())
.filter(|_| matches!(algo, "sha1" | "md5"))
.map(String::into_bytes)
}
fn fixture(p: &str) -> Vec<u8> {
made(p).unwrap_or_else(|| {
std::fs::read(Path::new(FX).join(p)).unwrap_or_else(|e| panic!("{p}: {e}"))
})
}
fn fixture_opt(p: &str) -> Option<Vec<u8>> {
made(p).or_else(|| std::fs::read(Path::new(FX).join(p)).ok())
}
fn sha256_hex(b: &[u8]) -> String {
hex::encode(sha2::Sha256::digest(b))
}
fn token(who: &str) -> String {
format!("{who}-token-of-sufficient-length-for-holger")
}
// ── the fake upstream ───────────────────────────────────────────────────────
struct Upstream {
base: String,
hits: Arc<Mutex<BTreeMap<String, usize>>>,
}
impl Upstream {
fn hits(&self, path: &str) -> usize {
self.hits.lock().unwrap().get(path).copied().unwrap_or(0)
}
}
struct Reply {
status: u16,
ctype: String,
body: Vec<u8>,
}
fn reply(status: u16, ctype: &str, body: Vec<u8>) -> Reply {
Reply {
status,
ctype: ctype.into(),
body,
}
}
fn not_found() -> Reply {
reply(404, "text/plain", b"not found".to_vec())
}
/// The packument as the registry publishes it, its links on the fake. `signed`
/// false strips the signatures and the attestations (an unsigned package).
fn packument(base: &str, signed: bool) -> Vec<u8> {
let mut v: serde_json::Value =
serde_json::from_slice(&fixture("npm/sigstore.packument.json")).unwrap();
let dist = v.pointer_mut("/versions/5.0.0/dist").unwrap();
dist["tarball"] = format!("{base}/npm/sigstore/-/sigstore-5.0.0.tgz").into();
if signed {
dist["attestations"]["url"] =
format!("{base}/npm/-/npm/v1/attestations/sigstore@5.0.0").into();
} else {
let d = dist.as_object_mut().unwrap();
d.remove("attestations");
d.remove("signatures");
}
serde_json::to_vec(&v).unwrap()
}
/// A Chainguard-style `.slsa-attestation.json` beside the JAR. Chainguard does not
/// document the file's format and the fixture is not a fetched one: this is an
/// in-toto statement naming the JAR, so the test can see it reach the verifier.
fn cg_attestation() -> Vec<u8> {
let jar = fixture(&format!("maven/{HAMCREST}/hamcrest-core-1.3.jar"));
serde_json::to_vec(&serde_json::json!({
"_type": "https://in-toto.io/Statement/v1",
"subject": [{ "name": "hamcrest-core-1.3.jar", "digest": { "sha256": sha256_hex(&jar) } }],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {}
}))
.unwrap()
}
/// A two-version npm package of the fake registry (`leftpad-field` 1.0.0 and 2.0.0, `latest`
/// 2.0.0): each tarball made here, the same bytes every time.
fn leftpad_tgz(version: &str) -> Vec<u8> {
let manifest =
serde_json::json!({ "name": "leftpad-field", "version": version, "main": "index.js" });
let files: [(&str, Vec<u8>); 2] = [
(
"package/package.json",
serde_json::to_vec(&manifest).unwrap(),
),
(
"package/index.js",
format!("module.exports = {version:?};\n").into_bytes(),
),
];
let mut tar = tar::Builder::new(Vec::new());
for (name, body) in &files {
let mut h = tar::Header::new_gnu();
h.set_size(body.len() as u64);
h.set_mode(0o644);
h.set_mtime(0);
h.set_cksum();
tar.append_data(&mut h, name, body.as_slice()).unwrap();
}
let mut gz = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
gz.write_all(&tar.into_inner().unwrap()).unwrap();
gz.finish().unwrap()
}
fn leftpad_packument(base: &str) -> Vec<u8> {
let version = |v: &str| {
serde_json::json!({
"name": "leftpad-field", "version": v, "main": "index.js",
"dist": {
"tarball": format!("{base}/npm/leftpad-field/-/leftpad-field-{v}.tgz"),
"integrity": npm::integrity_of(&leftpad_tgz(v)),
}
})
};
serde_json::to_vec(&serde_json::json!({
"name": "leftpad-field",
"dist-tags": { "latest": "2.0.0" },
"versions": { "1.0.0": version("1.0.0"), "2.0.0": version("2.0.0") },
}))
.unwrap()
}
fn route(base: &str, path: &str, auth: Option<&str>) -> Reply {
if let Some(p) = path.strip_prefix("/npm/") {
if p == "leftpad-field" {
return reply(200, "application/json", leftpad_packument(base));
}
if let Some(v) = p
.strip_prefix("leftpad-field/-/leftpad-field-")
.and_then(|f| f.strip_suffix(".tgz"))
{
return reply(200, "application/octet-stream", leftpad_tgz(v));
}
return match p {
"sigstore" => reply(200, "application/json", packument(base, true)),
"sigstore/-/sigstore-5.0.0.tgz" => reply(
200,
"application/octet-stream",
fixture("npm/sigstore-5.0.0.tgz"),
),
"-/npm/v1/attestations/sigstore@5.0.0" => reply(
200,
"application/json",
fixture("npm/sigstore-5.0.0.attestations.json"),
),
_ => not_found(),
};
}
if path == "/bare/sigstore" {
return reply(200, "application/json", packument(base, false));
}
if let Some(p) = path.strip_prefix("/maven2/") {
return fixture_opt(&format!("maven/{p}"))
.map(|b| reply(200, "application/octet-stream", b))
.unwrap_or_else(not_found);
}
if let Some(p) = path.strip_prefix("/java/") {
use base64::Engine as _;
let want = format!(
"Basic {}",
base64::engine::general_purpose::STANDARD
.encode(format!("cg-identity:{CG_PULL_TOKEN}"))
);
if auth != Some(want.as_str()) {
return reply(401, "text/plain", b"pull token required".to_vec());
}
if p == format!("{HAMCREST}/hamcrest-core-1.3.slsa-attestation.json") {
return reply(200, "application/json", cg_attestation());
}
return fixture_opt(&format!("maven/{p}"))
.map(|b| reply(200, "application/octet-stream", b))
.unwrap_or_else(not_found);
}
if let Some(p) = path.strip_prefix("/goproxy/") {
return go_route(p).unwrap_or_else(not_found);
}
if path == "/index/config.json" {
return reply(
200,
"application/json",
serde_json::json!({ "dl": format!("{base}/crates"), "api": base })
.to_string()
.into_bytes(),
);
}
if path == "/index/cf/g-/cfg-if" {
return reply(200, "text/plain", fixture("crates/cfg-if.index"));
}
if path == "/crates/cfg-if/1.0.0/download" {
return reply(
200,
"application/x-tar",
fixture("crates/cfg-if-1.0.0.crate"),
);
}
if path == "/crates/cfg-if/1.0.5/download" {
return reply(
200,
"application/x-tar",
fixture("crates/cfg-if-1.0.5.crate"),
);
}
not_found()
}
fn serve_one(mut s: TcpStream, base: &str, hits: &Mutex<BTreeMap<String, usize>>) {
let _ = s.set_read_timeout(Some(Duration::from_secs(10)));
let mut buf = Vec::new();
let mut chunk = [0u8; 4096];
while !buf.windows(4).any(|w| w == b"\r\n\r\n") {
match s.read(&mut chunk) {
Ok(0) | Err(_) => return,
Ok(n) => buf.extend_from_slice(&chunk[..n]),
}
}
let head = String::from_utf8_lossy(&buf).to_string();
let mut lines = head.lines();
let path = lines
.next()
.unwrap_or("")
.split_whitespace()
.nth(1)
.unwrap_or("/")
.to_string();
let auth = lines
.find_map(|l| {
l.strip_prefix("authorization: ")
.or_else(|| l.strip_prefix("Authorization: "))
})
.map(str::to_string);
let bare = path.split('?').next().unwrap_or("").to_string();
*hits.lock().unwrap().entry(bare.clone()).or_default() += 1;
let r = route(base, &bare, auth.as_deref());
let out = format!(
"HTTP/1.1 {} X\r\nContent-Length: {}\r\nContent-Type: {}\r\nConnection: close\r\n\r\n",
r.status,
r.body.len(),
r.ctype
);
let _ = s.write_all(out.as_bytes());
let _ = s.write_all(&r.body);
}
fn upstream() -> Upstream {
let l = TcpListener::bind("127.0.0.1:0").unwrap();
let base = format!("http://{}", l.local_addr().unwrap());
let hits: Arc<Mutex<BTreeMap<String, usize>>> = Arc::default();
let (b2, h2) = (base.clone(), Arc::clone(&hits));
std::thread::spawn(move || {
for s in l.incoming().flatten() {
let (b3, h3) = (b2.clone(), Arc::clone(&h2));
std::thread::spawn(move || serve_one(s, &b3, &h3));
}
});
Upstream { base, hits }
}
// ── one Holger ──────────────────────────────────────────────────────────────
struct Running {
holger: Holger,
_rt: tokio::runtime::Runtime,
_data: tempfile::TempDir,
_cfg: tempfile::NamedTempFile,
http: String,
}
impl Running {
fn chain(&self) -> Arc<IntakeChain> {
self.holger.intake().expect("the intake chain")
}
}
fn start(pull_through: &str) -> Running {
start_as(pull_through, false)
}
/// A Holger whose production repository is NOT public and whose reads are gated (an estate's
/// private repository): every read needs a credential.
fn start_gated(pull_through: &str) -> Running {
start_as(pull_through, true)
}
fn start_as(pull_through: &str, gated: bool) -> Running {
let mut last = String::new();
for _ in 0..8 {
let (grpc, http) = (hold_port(), hold_port());
match try_start(grpc, http, pull_through, gated) {
Ok(r) => return r,
Err(e) => last = e,
}
}
panic!("server never started; last error: {last}");
}
fn try_start(grpc: u16, http: u16, pull_through: &str, gated: bool) -> Result<Running, String> {
let data = tempfile::tempdir().unwrap();
let d = data.path().display().to_string();
let repo = |name: &str| {
format!(
r#"(ron_name: "{name}", ron_repo_type: "raw", ron_upstreams: [], ron_in: None,
ron_out: Some((ron_storage_endpoint: "storage", ron_exposed_endpoint: "door")),
ron_data_dir: Some("{d}/{name}")),"#
)
};
let methods: Vec<String> = ["ida", "bob"]
.iter()
.map(|p| {
format!(
r#"StaticToken(subject: "{p}", token: Some("{}"))"#,
token(p)
)
})
.collect();
let pull_through = pull_through.replace("{data}", &d);
let ron = format!(
r#"(
repositories: [ {incoming} {quarantine} {approved} {production} ],
exposed_endpoints: [ (ron_name: "door", ron_url: "127.0.0.1:{grpc}", ron_http_url: Some("127.0.0.1:{http}")) ],
storage_endpoints: [ (ron_name: "storage", ron_storage_type: "znippy", ron_path: "{d}") ],
audit: (dir: "{d}/audit", enabled: true),
auth: Some((
methods: [ {methods} ],
duties: {{ "ida": [Importer], "bob": [SecurityApprover] }},
require_auth_for_reads: {gated},
)),
promotion: Some((
ladder_name: "intake",
stages: [
(name: "incoming", repo: "incoming"), (name: "quarantine", repo: "quarantine"),
(name: "approved", repo: "approved"), (name: "production", repo: "production"),
],
intake: Some((
incoming: "incoming", quarantine: "quarantine", approved: "approved", production: "production",
rules: (sandbox: off),
approvals: 1,
scan: None,
)),
)),
pull_through: Some(({pull_through})),
)"#,
incoming = repo("incoming"),
quarantine = repo("quarantine"),
approved = repo("approved"),
// HCON-5: an estate build gates reads; the repository clients read is public (as the
// ONE box feeds an estate's `public_repositories`), the intake stages stay gated.
production = if gated {
repo("production")
} else {
repo("production").replacen("ron_in: None,", "ron_in: None, visibility: Public,", 1)
},
methods = methods.join(", "),
);
let mut cfg = tempfile::NamedTempFile::new().unwrap();
cfg.write_all(ron.as_bytes()).unwrap();
let mut holger: Holger = read_ron_config(cfg.path()).map_err(|e| format!("config: {e}"))?;
holger
.instantiate_backends()
.map_err(|e| format!("backends: {e}"))?;
wire_holger(&mut holger).map_err(|e| format!("wire: {e:#}"))?;
let rt = tokio::runtime::Builder::new_multi_thread()
.worker_threads(4)
.enable_all()
.build()
.unwrap();
{
let _in = rt.enter();
holger.start().map_err(|e| format!("start: {e:#}"))?;
}
let r = Running {
holger,
_rt: rt,
_data: data,
_cfg: cfg,
http: format!("127.0.0.1:{http}"),
};
for _ in 0..200 {
if get(&r.http, "/healthz", &[]).status == 200 {
return Ok(r);
}
std::thread::sleep(Duration::from_millis(20));
}
Err("never healthy".into())
}
struct Got {
status: u16,
headers: BTreeMap<String, String>,
body: Vec<u8>,
}
impl Got {
fn text(&self) -> String {
String::from_utf8_lossy(&self.body).to_string()
}
fn json(&self) -> serde_json::Value {
serde_json::from_slice(&self.body).unwrap_or_else(|e| panic!("{e}: {}", self.text()))
}
}
fn get(addr: &str, path: &str, headers: &[(&str, &str)]) -> Got {
let Ok(mut s) = TcpStream::connect(addr) else {
return Got {
status: 0,
headers: BTreeMap::new(),
body: Vec::new(),
};
};
let mut req = format!("GET {path} HTTP/1.1\r\nHost: {addr}\r\nConnection: close\r\n");
for (k, v) in headers {
req.push_str(&format!("{k}: {v}\r\n"));
}
req.push_str("\r\n");
s.write_all(req.as_bytes()).unwrap();
let mut all = Vec::new();
let _ = s.read_to_end(&mut all);
let split = all
.windows(4)
.position(|w| w == b"\r\n\r\n")
.map(|i| i + 4)
.unwrap_or(all.len());
let head = String::from_utf8_lossy(&all[..split]).to_string();
let status = head
.split_whitespace()
.nth(1)
.and_then(|s| s.parse().ok())
.unwrap_or(0);
let headers = head
.lines()
.skip(1)
.filter_map(|l| {
l.split_once(':')
.map(|(k, v)| (k.trim().to_ascii_lowercase(), v.trim().to_string()))
})
.collect();
Got {
status,
headers,
body: all[split..].to_vec(),
}
}
fn wait_state(chain: &IntakeChain, id: &traits::ArtifactId, want: ArtifactState) {
let t = Instant::now();
while t.elapsed() < Duration::from_secs(60) {
if chain.state(id) == want {
return;
}
std::thread::sleep(Duration::from_millis(50));
}
let jobs = chain.pull().map(|p| p.debug_records()).unwrap_or_default();
panic!(
"{} never reached {want}; it stands at {} ({:#?}); pull records: {jobs}",
id.name,
chain.state(id),
chain.history(id)
);
}
/// A GET once the pull job is done: while the job still runs, the answer is 503.
fn get_settled(addr: &str, path: &str) -> Got {
let t = Instant::now();
loop {
let g = get(addr, path, &[]);
if g.status != 503 || t.elapsed() > Duration::from_secs(60) {
return g;
}
std::thread::sleep(Duration::from_millis(50));
}
}
fn approve(chain: &IntakeChain, id: &traits::ArtifactId) {
chain
.advance(
TransitionRequest::new(id.clone(), ArtifactState::Approved, "bob")
.with_reason("reviewed"),
)
.unwrap_or_else(|e| panic!("bob approves {}: {e}", id.name));
}
/// The walk a pull takes before a person: INGESTED, SCANNED, POLICY_CHECKED, all as
/// `pull-through:<source>`.
fn assert_walked_as(chain: &IntakeChain, id: &traits::ArtifactId, source: &str) {
let h = chain.history(id);
let states: Vec<ArtifactState> = h.iter().map(|e| e.to).collect();
assert_eq!(
states,
[
ArtifactState::Ingested,
ArtifactState::Scanned,
ArtifactState::PolicyChecked
],
"{h:#?}"
);
assert!(
h.iter()
.all(|e| e.actor == format!("pull-through:{source}")),
"{h:#?}"
);
assert!(
h[0].evidence
.iter()
.any(|e| e.kind == EvidenceKind::Malware && e.source == "holger-inspect"),
"{:?}",
h[0].evidence
);
}
/// The real verifier (`holger-provenance`), counted, with Sigstore's public-good
/// trust root and the floor's keys.
struct Real {
calls: AtomicUsize,
names: Mutex<Vec<String>>,
inner: HolgerProvenance,
}
impl Real {
fn new(keys: Vec<holger_provenance::TrustedKey>) -> Self {
let root = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../provenance/tests/fixtures/sigstore/trusted_root.json"
))
.unwrap();
let trust = holger_provenance::TrustRoot::from_trusted_root_json(&root).unwrap();
let mut inner = HolgerProvenance::new(Some(Arc::new(trust)));
inner.keys = keys;
Self {
calls: AtomicUsize::new(0),
names: Mutex::default(),
inner,
}
}
}
impl Verifier for Real {
fn verify(&self, subject: &[u8], materials: &[Material], demand: &Demand) -> Outcome {
self.calls.fetch_add(1, Ordering::SeqCst);
self.names
.lock()
.unwrap()
.extend(materials.iter().map(|m| m.name.clone()));
self.inner.verify(subject, materials, demand)
}
}
fn npm_key() -> holger_provenance::TrustedKey {
holger_provenance::TrustedKey {
id: NPM_KEY_ID.into(),
alg: holger_provenance::KeyAlg::EcdsaP256,
public_key: NPM_KEY.into(),
}
}
// ── npm ─────────────────────────────────────────────────────────────────────
fn npm_sources(up: &Upstream) -> String {
format!(
r#"sources: [ (name: "npmjs", kind: npm, url: "{base}/npm/",
gates: (provenance: Some(require(identity: Some((issuer: "{GITHUB}", subject: Some("{SIGSTORE_JS}"))))))),
(name: "bare", kind: npm, url: "{base}/bare/") ],
rules: (require_signed: true, keys: [(id: "{NPM_KEY_ID}", alg: ecdsa_p256, public_key: "{NPM_KEY}")]),
retry_after_secs: Some(1),"#,
base = up.base
)
}
#[test]
fn an_npm_package_with_provenance_is_pulled_verified_held_and_served_from_the_sealed_store() {
let up = upstream();
let r = start(&npm_sources(&up));
let chain = r.chain();
let real = Arc::new(Real::new(vec![npm_key()]));
chain.pull().unwrap().replace_verifier(real.clone());
// 1. npm's first request: the packument is not yet there; a pull is on its way.
let first = get(
&r.http,
"/production/npmjs/sigstore",
&[("Accept", "application/json")],
);
assert_eq!(first.status, 503, "{}", first.text());
assert!(
first.headers.contains_key("retry-after"),
"{:?}",
first.headers
);
assert!(
first.json()["error"]
.as_str()
.unwrap()
.contains("pulling it into its intake chain"),
"{}",
first.text()
);
// 2. `latest` (5.0.0) lands INGESTED as pull-through:npmjs, its provenance and
// the registry's signature verified OFFLINE at SCANNED, and waits for a person.
let id = npm::id("npmjs", "sigstore", "5.0.0");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
assert_walked_as(&chain, &id, "npmjs");
let tgz = fixture("npm/sigstore-5.0.0.tgz");
assert_eq!(chain.history(&id)[0].sha256, sha256_hex(&tgz));
let prov = chain.history(&id)[1]
.evidence
.iter()
.find(|e| e.kind == EvidenceKind::Provenance)
.cloned()
.expect("a provenance slot");
assert_eq!(
(prov.source.as_str(), prov.verdict),
("holger-provenance", EvidenceVerdict::Pass),
"{prov:?}"
);
assert!(
prov.reference.contains("3 verified"),
"the SLSA provenance, the publish attestation and the registry signature: {}",
prov.reference
);
let names = real.names.lock().unwrap().clone();
assert!(
names
.iter()
.any(|n| n == "sigstore-5.0.0.tgz.registry-signatures.json"),
"{names:?}"
);
assert!(
names
.iter()
.any(|n| n == "sigstore-5.0.0.tgz.attestations.json"),
"{names:?}"
);
// 3. While it waits, the packument and the tarball say so, and serve nothing.
// The packument lists it DEPRECATED with its state (npm installs no range onto it).
let held = get_settled(&r.http, "/production/npmjs/sigstore");
assert_eq!(held.status, 200, "{}", held.text());
assert!(
held.json()["versions"]["5.0.0"]["deprecated"]
.as_str()
.unwrap()
.contains("waiting for approval"),
"{}",
held.text()
);
assert_eq!(
get(
&r.http,
"/production/npmjs/sigstore/-/sigstore-5.0.0.tgz",
&[]
)
.status,
404
);
// 4. Approved: the packument lists the AVAILABLE version, its integrity the sealed
// bytes', its tarball under Holger; the tarball is the sealed bytes.
approve(&chain, &id);
let doc = get(&r.http, "/production/npmjs/sigstore", &[]);
assert_eq!(doc.status, 200, "{}", doc.text());
let doc = doc.json();
assert_eq!(doc["dist-tags"]["latest"], "5.0.0", "{doc}");
let versions: Vec<&String> = doc["versions"].as_object().unwrap().keys().collect();
assert_eq!(versions, ["5.0.0"]);
let dist = &doc["versions"]["5.0.0"]["dist"];
assert_eq!(dist["integrity"], npm::integrity_of(&tgz));
assert_eq!(
dist["tarball"],
format!(
"http://{}/production/npmjs/sigstore/-/sigstore-5.0.0.tgz",
r.http
)
);
assert_eq!(
doc["versions"]["5.0.0"]["dependencies"]["@sigstore/core"], "^4.0.0",
"the manifest is the tarball's own"
);
let t = get(
&r.http,
"/production/npmjs/sigstore/-/sigstore-5.0.0.tgz",
&[],
);
assert_eq!(t.status, 200, "{}", t.text());
assert_eq!(
t.headers.get("x-holger-intake-state").map(String::as_str),
Some("AVAILABLE")
);
assert_eq!(t.body, tgz);
assert_eq!(
up.hits("/npm/sigstore/-/sigstore-5.0.0.tgz"),
1,
"the upstream gave the tarball once"
);
assert_eq!(up.hits("/npm/-/npm/v1/attestations/sigstore@5.0.0"), 1);
}
#[test]
fn an_npm_package_without_signatures_is_refused_by_the_floor() {
let up = upstream();
let r = start(&npm_sources(&up));
let chain = r.chain();
let real = Arc::new(Real::new(vec![npm_key()]));
chain.pull().unwrap().replace_verifier(real.clone());
assert_eq!(get(&r.http, "/production/bare/sigstore", &[]).status, 503);
let id = npm::id("bare", "sigstore", "5.0.0");
wait_state(&chain, &id, ArtifactState::Blocked);
let last = chain.history(&id).last().cloned().unwrap();
assert!(
last.reason
.contains("no signature or attestation came with it"),
"{}",
last.reason
);
assert_eq!(
real.calls.load(Ordering::SeqCst),
0,
"refused before the verifier"
);
let denied = get_settled(&r.http, "/production/bare/sigstore");
assert_eq!(denied.status, 403, "{}", denied.text());
assert!(
denied.json()["error"].as_str().unwrap().contains("refused"),
"{}",
denied.text()
);
}
// ── Maven ───────────────────────────────────────────────────────────────────
fn maven_sources(up: &Upstream) -> String {
format!(
r#"sources: [ (name: "central", kind: maven, url: "{base}/maven2/"),
(name: "cg-java", kind: maven, url: "{base}/java/", gates: (provenance: Some(prefer)), wait_secs: Some(0),
credential: Some((username: "cg-identity", password_file: Some("{{data}}/cg-token")))) ],
rules: (require_signed: false),
retry_after_secs: Some(1),"#,
base = up.base
)
}
#[test]
fn a_maven_jar_and_its_pom_are_pulled_against_their_checksums_and_served_after_approval() {
let up = upstream();
let r = start_with_token(&maven_sources(&up));
let chain = r.chain();
let pom_path = format!("{HAMCREST}/hamcrest-core-1.3.pom");
let jar_path = format!("{HAMCREST}/hamcrest-core-1.3.jar");
// 1. Maven asks for the POM first. Maven before 3.9.5 does not retry a 503 (3.9.5 and
// later retry it three times; measured, HOL-DOCFIND i), so Holger holds
// the request while the POM and the JAR are pulled together, then says where
// they stand: waiting for a person (404, the state named).
let first = get(&r.http, &format!("/production/central/{pom_path}"), &[]);
assert_eq!(first.status, 404, "{}", first.text());
assert!(
first.text().contains("waiting for approval"),
"{}",
first.text()
);
let (pom, jar) = (
maven::id("central", &pom_path),
maven::id("central", &jar_path),
);
wait_state(&chain, &pom, ArtifactState::PolicyChecked);
wait_state(&chain, &jar, ArtifactState::PolicyChecked);
assert_walked_as(&chain, &jar, "central");
assert_eq!(
chain.history(&jar)[0].sha256,
sha256_hex(&fixture(&format!("maven/{jar_path}")))
);
assert!(
up.hits(&format!("/maven2/{jar_path}.sha1")) >= 1,
"the JAR was held against its .sha1"
);
// 2. Held: 404, the state named; metadata lists nothing yet.
let held = get_settled(&r.http, &format!("/production/central/{jar_path}"));
assert_eq!(held.status, 404, "{}", held.text());
assert!(
held.text().contains("waiting for approval"),
"{}",
held.text()
);
// 3. Approved: the files, their checksums from the sealed bytes, and metadata
// over the AVAILABLE versions only (Central's lists 1.1 to 3.0).
approve(&chain, &pom);
approve(&chain, &jar);
let j = get(&r.http, &format!("/production/central/{jar_path}"), &[]);
assert_eq!(j.status, 200, "{}", j.text());
assert_eq!(j.body, fixture(&format!("maven/{jar_path}")));
assert_eq!(
j.headers.get("content-type").map(String::as_str),
Some("application/java-archive")
);
let sha1 = get(
&r.http,
&format!("/production/central/{jar_path}.sha1"),
&[],
);
assert_eq!(sha1.status, 200, "{}", sha1.text());
assert_eq!(
sha1.text(),
String::from_utf8(fixture(&format!("maven/{jar_path}.sha1")))
.unwrap()
.trim()
);
let md5 = get(&r.http, &format!("/production/central/{pom_path}.md5"), &[]);
assert_eq!(
md5.text(),
String::from_utf8(fixture(&format!("maven/{pom_path}.md5")))
.unwrap()
.trim()
);
let meta = get(
&r.http,
"/production/central/org/hamcrest/hamcrest-core/maven-metadata.xml",
&[],
);
assert_eq!(meta.status, 200, "{}", meta.text());
assert!(
meta.text().contains("<version>1.3</version>")
&& meta.text().contains("<release>1.3</release>"),
"{}",
meta.text()
);
assert!(
!meta.text().contains("<version>3.0</version>"),
"only AVAILABLE versions: {}",
meta.text()
);
}
/// Chainguard Libraries for Java: the pull token goes to the source, and the
/// `.slsa-attestation.json` beside the JAR reaches the provenance verifier.
#[test]
fn a_chainguard_java_jar_carries_its_slsa_attestation_to_the_verifier() {
let up = upstream();
let r = start_with_token(&maven_sources(&up));
let chain = r.chain();
#[derive(Default)]
struct Seen(Mutex<Vec<(String, Vec<String>)>>);
impl Verifier for Seen {
fn verify(&self, subject: &[u8], materials: &[Material], _: &Demand) -> Outcome {
self.0.lock().unwrap().push((
sha256_hex(subject),
materials.iter().map(|m| m.name.clone()).collect(),
));
Outcome {
verdict: Verdict::Pass,
detail: "seen".into(),
code: None,
verified: materials.len(),
}
}
}
let seen = Arc::new(Seen::default());
chain.pull().unwrap().replace_verifier(seen.clone());
let jar_path = format!("{HAMCREST}/hamcrest-core-1.3.jar");
assert_eq!(
get(&r.http, &format!("/production/cg-java/{jar_path}"), &[]).status,
503
);
let jar = maven::id("cg-java", &jar_path);
wait_state(&chain, &jar, ArtifactState::PolicyChecked);
assert_walked_as(&chain, &jar, "cg-java");
let jar_sha = sha256_hex(&fixture(&format!("maven/{jar_path}")));
let seen = seen.0.lock().unwrap().clone();
let (_, names) = seen
.iter()
.find(|(sha, _)| *sha == jar_sha)
.expect("the verifier saw the JAR");
assert_eq!(
names,
&["hamcrest-core-1.3.slsa-attestation.json"],
"{seen:?}"
);
assert_eq!(
up.hits(&format!(
"/java/{HAMCREST}/hamcrest-core-1.3.slsa-attestation.json"
)),
1
);
}
/// T9: Maven Central's `.asc` beside a POM is fetched with it and verified
/// OFFLINE against the OpenPGP keys the trust root pins, narrowed per source by
/// `require(pgp_keys: [...])`. An unpinned signer is unverified: refused, never AVAILABLE.
#[test]
fn a_maven_central_pom_is_held_to_its_asc_and_the_pinned_pgp_key_per_source() {
const CEKI: &str = "ceki-60200AC4AE761F1614D6C46766D68DAA073BE985.asc";
const POVIRK: &str = "povirk-BDB5FA4FE719D787FB3D3197F6D4A1D411E9D1AE.asc";
let up = upstream();
let cfg = format!(
r#"sources: [ (name: "central", kind: maven, url: "{base}/maven2/", gates: (provenance: Some(require(pgp_keys: ["{CEKI}"])))),
(name: "apache-only", kind: maven, url: "{base}/maven2/", gates: (provenance: Some(require(pgp_keys: ["{POVIRK}"])))) ],
rules: (require_signed: false),
retry_after_secs: Some(1),"#,
base = up.base
);
if !holger_provenance::pgp::BUILT {
// T9b: a server built without `maven-pgp` refuses the pinned sources at boot, by
// name, and never pulls through them unpinned.
let e = try_start(hold_port(), hold_port(), &cfg, false)
.err()
.expect("a pgp pin without the feature refuses the boot");
assert!(
e.contains("[provenance-pgp-not-built]")
&& e.contains("'central'")
&& e.contains("`maven-pgp`"),
"{e}"
);
assert_eq!(
up.hits("/maven2/org/slf4j/slf4j-api/2.0.16/slf4j-api-2.0.16.pom"),
0,
"nothing was fetched"
);
return;
}
let r = start(&cfg);
let chain = r.chain();
let key = |n: &str| {
(
n.to_string(),
std::fs::read(format!(
"{}/../../provenance/tests/fixtures/maven/{n}",
env!("CARGO_MANIFEST_DIR")
))
.unwrap(),
)
};
let trust = holger_provenance::TrustRoot::default()
.with_pgp_keys(&[key(CEKI), key(POVIRK)])
.unwrap();
let real = Arc::new(Real {
calls: AtomicUsize::new(0),
names: Mutex::default(),
inner: HolgerProvenance::new(Some(Arc::new(trust))),
});
chain.pull().unwrap().replace_verifier(real.clone());
let pom_path = "org/slf4j/slf4j-api/2.0.16/slf4j-api-2.0.16.pom";
// 1. Signed by Ceki's key, which this source pins: verified, waits for a person.
assert_eq!(
get(&r.http, &format!("/production/central/{pom_path}"), &[]).status,
404
);
let pom = maven::id("central", pom_path);
wait_state(&chain, &pom, ArtifactState::PolicyChecked);
let prov = chain.history(&pom)[1]
.evidence
.iter()
.find(|e| e.kind == EvidenceKind::Provenance)
.cloned()
.expect("a provenance slot");
assert_eq!(
(prov.source.as_str(), prov.verdict),
("holger-provenance", EvidenceVerdict::Pass),
"{prov:?}"
);
assert!(prov.reference.contains("1 verified"), "{}", prov.reference);
assert!(
real.names
.lock()
.unwrap()
.iter()
.any(|n| n == "slf4j-api-2.0.16.pom.asc"),
"{:?}",
real.names.lock().unwrap()
);
assert!(
up.hits(&format!("/maven2/{pom_path}.asc")) >= 1,
"the .asc was fetched beside the POM"
);
// 2. The same bytes from a source that pins only another key: unverified, refused.
assert_ne!(
get(&r.http, &format!("/production/apache-only/{pom_path}"), &[]).status,
200
);
let other = maven::id("apache-only", pom_path);
wait_state(&chain, &other, ArtifactState::Blocked);
let last = chain.history(&other).last().cloned().unwrap();
assert!(
last.reason.contains("holger-provenance-4"),
"{}",
last.reason
);
}
fn start_with_token(pt: &str) -> Running {
// The pull token is read from a file at boot; the config names `{data}/cg-token`.
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("cg-token");
std::fs::write(&file, CG_PULL_TOKEN).unwrap();
let pt = pt.replace("{data}/cg-token", &file.display().to_string());
let r = start(&pt);
std::mem::forget(dir);
r
}
// ── cargo ───────────────────────────────────────────────────────────────────
fn crates_sources(up: &Upstream, signed: bool) -> String {
format!(
r#"sources: [ (name: "crates-io", kind: crates, url: "sparse+{}/index/") ], rules: (require_signed: {signed}), retry_after_secs: Some(1),"#,
up.base
)
}
#[test]
fn a_crate_is_pulled_against_its_index_checksum_and_the_index_lists_only_available_versions() {
let up = upstream();
let r = start(&crates_sources(&up, false));
let chain = r.chain();
// cargo reads config.json first: downloads come from Holger.
let cfg = get(&r.http, "/production/crates-io/config.json", &[]);
assert_eq!(cfg.status, 200, "{}", cfg.text());
assert_eq!(
cfg.json()["dl"],
format!(
"http://{}/production/crates-io/{{crate}}/{{crate}}-{{version}}.crate",
r.http
)
);
// The index file: not yet; the newest live release (1.0.5) is pulled.
let first = get(&r.http, "/production/crates-io/cf/g-/cfg-if", &[]);
assert_eq!(first.status, 503, "{}", first.text());
assert!(first.headers.contains_key("retry-after"));
let id = crates::id("crates-io", "cfg-if", "1.0.5");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
assert_walked_as(&chain, &id, "crates-io");
let crate_bytes = fixture("crates/cfg-if-1.0.5.crate");
assert_eq!(chain.history(&id)[0].sha256, sha256_hex(&crate_bytes));
// Held: nothing AVAILABLE, so every line is yanked (cargo resolves nothing new onto it).
let held = get_settled(&r.http, "/production/crates-io/cf/g-/cfg-if");
assert_eq!(held.status, 200, "{}", held.text());
assert!(
held.text().lines().all(|l| l.contains("\"yanked\":true")),
"held: nothing AVAILABLE is installable: {}",
held.text()
);
approve(&chain, &id);
let index = get(&r.http, "/production/crates-io/cf/g-/cfg-if", &[]);
assert_eq!(index.status, 200, "{}", index.text());
// The AVAILABLE version is the one installable line; the registry's others ride yanked.
let lines: Vec<serde_json::Value> = index
.text()
.lines()
.map(|l| serde_json::from_str::<serde_json::Value>(l).unwrap())
.filter(|l| l["yanked"] == false)
.collect();
assert_eq!(
lines.len(),
1,
"only the AVAILABLE version is installable (crates.io lists eleven): {}",
index.text()
);
assert_eq!(lines[0]["vers"], "1.0.5");
assert_eq!(lines[0]["cksum"], sha256_hex(&crate_bytes));
assert_eq!(
lines[0]["deps"][0]["package"], "rustc-std-workspace-core",
"the registry's own line"
);
let c = get(
&r.http,
"/production/crates-io/cfg-if/cfg-if-1.0.5.crate",
&[],
);
assert_eq!(c.status, 200, "{}", c.text());
assert_eq!(c.body, crate_bytes);
assert_eq!(up.hits("/crates/cfg-if/1.0.5/download"), 1);
}
/// ★ **A gated crates source says `auth-required`** (HOL-DOCFIND b): on a Holger whose reads
/// are gated (an estate), cargo reads `config.json` with no token, is refused 401 and asks
/// again with its token; the answer must say `"auth-required": true`, or cargo never sends the
/// token for the index files and the `.crate`s, and every read after `config.json` is a 401.
/// A public source says nothing of it (cargo then sends no token at all).
#[test]
fn a_gated_crates_source_says_auth_required_and_a_public_one_does_not() {
let up = upstream();
let r = start_gated(&crates_sources(&up, false));
let anon = get(&r.http, "/production/crates-io/config.json", &[]);
assert_eq!(anon.status, 401, "a gated read: {}", anon.text());
assert!(
anon.headers.contains_key("www-authenticate"),
"cargo is told to ask again with its token"
);
// cargo's spelling: the token verbatim, no scheme.
let tok = token("bob");
let cfg = get(
&r.http,
"/production/crates-io/config.json",
&[("Authorization", tok.as_str())],
);
assert_eq!(cfg.status, 200, "{}", cfg.text());
assert_eq!(
cfg.json()["auth-required"],
serde_json::json!(true),
"a gated source tells cargo to send its token: {}",
cfg.text()
);
let index = get(&r.http, "/production/crates-io/cf/g-/cfg-if", &[]);
assert_eq!(
index.status,
401,
"the index is gated too: {}",
index.text()
);
drop(r);
let open = start(&crates_sources(&up, false));
let cfg = get(&open.http, "/production/crates-io/config.json", &[]);
assert_eq!(cfg.status, 200, "{}", cfg.text());
assert!(
cfg.json().get("auth-required").is_none(),
"a public source asks no token: {}",
cfg.text()
);
}
/// A verifier that holds each caller until a second one arrives (or 3 s pass), so two pulls
/// of one coordinate meet inside the INGESTED step: the interleave the field hit when cargo
/// asked for a crate's index and its download at once (lane HOL-FIELD-2).
#[derive(Default)]
struct Rendezvous {
arrived: Mutex<usize>,
cv: std::sync::Condvar,
}
impl Verifier for Rendezvous {
fn verify(&self, _: &[u8], _: &[Material], _: &Demand) -> Outcome {
let mut n = self.arrived.lock().unwrap();
*n += 1;
self.cv.notify_all();
let t = Instant::now();
while *n < 2 && t.elapsed() < Duration::from_secs(3) {
n = self
.cv
.wait_timeout(n, Duration::from_millis(100))
.unwrap()
.0;
}
Outcome {
verdict: Verdict::Pass,
detail: "met the other pull".into(),
code: None,
verified: 0,
}
}
}
/// Every file called `name` under `dir`, read.
fn files_named(dir: &Path, name: &str) -> Vec<String> {
let mut out = Vec::new();
let Ok(rd) = std::fs::read_dir(dir) else {
return out;
};
for e in rd.flatten() {
let p = e.path();
if p.is_dir() {
out.extend(files_named(&p, name));
} else if p.file_name().is_some_and(|f| f == name) {
out.push(std::fs::read_to_string(&p).unwrap_or_default());
}
}
out
}
/// ★ **HOL-DOGFOOD-8: every pull and every closure walk reads ONE shared index.** On the
/// oden dogfood Holger, `cargo fetch` of gunnar drove ~1000 crate pulls at once; each walk
/// read every dependency's sparse-index file from the upstream again, held its own copy (all
/// versions, raw JSON, cloned per edge) and appended the whole listing to
/// `crates-listings.jsonl` again, which every index request then read and parsed whole: 40 GB
/// in 30 s. Now three pulls of one crate read its index from the upstream ONCE, and its
/// listing is recorded once.
#[test]
fn three_pulls_of_one_crate_read_its_upstream_index_once() {
let up = upstream();
let r = start(&crates_sources(&up, false));
let chain = r.chain();
let pull = chain.pull().expect("the pull-through").clone();
for w in [
Want::CrateName {
name: "cfg-if".into(),
},
Want::CrateVersion {
name: "cfg-if".into(),
version: "1.0.5".into(),
},
Want::CrateName {
name: "cfg-if".into(),
},
] {
pull.run(&chain, "crates-io", &w)
.unwrap_or_else(|e| panic!("{w:?}: {e:#}"));
}
assert_eq!(
up.hits("/index/cf/g-/cfg-if"),
1,
"the upstream index file is read once and shared by every pull"
);
let listings = files_named(r._data.path(), "crates-listings.jsonl").join("");
assert_eq!(
listings
.lines()
.filter(|l| l.contains("\"cfg-if\""))
.count(),
1,
"the listing is recorded once per upstream read, not once per pull: {listings}"
);
}
/// **Two pulls of one crate at once never block it** (lane HOL-FIELD-2): cargo asks for the
/// index file (the newest release) and the download (that version) together, two jobs drive
/// the same coordinate, and the one that lost the race used to BLOCK it for good with
/// "'SCANNED' → 'SCANNED' does not move forward" (ryu 1.0.23 in the field). The loser walks
/// on from where the winner left it.
#[test]
fn two_pulls_of_one_crate_at_once_never_block_it() {
let up = upstream();
let r = start(&format!(
r#"sources: [ (name: "crates-io", kind: crates, url: "sparse+{}/index/", gates: (provenance: Some(prefer))) ], retry_after_secs: Some(1),"#,
up.base
));
let chain = r.chain();
let pull = chain.pull().expect("the pull-through").clone();
pull.replace_verifier(Arc::new(Rendezvous::default()));
let wants = [
Want::CrateName {
name: "cfg-if".into(),
},
Want::CrateVersion {
name: "cfg-if".into(),
version: "1.0.5".into(),
},
];
let lines: Vec<String> = std::thread::scope(|s| {
let runs: Vec<_> = wants
.iter()
.map(|w| {
let (chain, pull) = (chain.clone(), pull.clone());
s.spawn(move || {
pull.run(&chain, "crates-io", w)
.unwrap_or_else(|e| format!("{e:#}"))
})
})
.collect();
runs.into_iter().map(|h| h.join().unwrap()).collect()
});
let id = crates::id("crates-io", "cfg-if", "1.0.5");
assert_eq!(
chain.state(&id),
ArtifactState::PolicyChecked,
"both pulls end at POLICY_CHECKED, waiting for a person: {lines:?} {:#?}",
chain.history(&id)
);
}
#[test]
fn an_unsigned_crate_is_refused_where_the_floor_requires_a_signature() {
let up = upstream();
let r = start(&crates_sources(&up, true));
let chain = r.chain();
assert_eq!(
get(&r.http, "/production/crates-io/cf/g-/cfg-if", &[]).status,
503
);
let id = crates::id("crates-io", "cfg-if", "1.0.5");
wait_state(&chain, &id, ArtifactState::Blocked);
// The refused version is never on the index; its `.crate` says why.
let index = get_settled(&r.http, "/production/crates-io/cf/g-/cfg-if");
assert!(
!index.text().contains("\"vers\":\"1.0.5\""),
"a refused version is never listed: {}",
index.text()
);
let denied = get_settled(&r.http, "/production/crates-io/cfg-if/cfg-if-1.0.5.crate");
assert_eq!(denied.status, 403, "{}", denied.text());
assert!(
denied
.text()
.contains("no signature or attestation came with it"),
"{}",
denied.text()
);
}
// ── an invalidation from the upstream (H4) ───────────────────────────────────
//
// `IntakeChain::invalidate` is what an applied sync manifest does to each package it
// invalidates (`server_lib::sync`): the package goes to QUARANTINED at once, its
// bytes stay in the sealed store, every index drops it, a read is refused with the
// reason, and no release brings it back.
const INVALIDATED: &str =
"invalidated by upstream 'dmz' (generation 2): malware: found after its approval";
fn invalidate(chain: &IntakeChain, id: &traits::ArtifactId) {
use holger_server_lib::promote::intake::InvalidateOutcome;
match chain
.invalidate(id, "holger-sync:dmz", "sync", INVALIDATED)
.unwrap_or_else(|e| panic!("invalidate {}: {e}", id.name))
{
InvalidateOutcome::Held(e) => assert_eq!(e.to, ArtifactState::Quarantined, "{e:?}"),
other => panic!("{}: {other:?}", id.name),
}
assert_eq!(chain.state(id), ArtifactState::Quarantined);
// Again: nothing more happens.
assert!(matches!(
chain
.invalidate(id, "holger-sync:dmz", "sync", INVALIDATED)
.unwrap(),
InvalidateOutcome::AlreadyHeld(ArtifactState::Quarantined)
));
// No release brings it back; only a Block moves it on.
let back = chain.advance(
TransitionRequest::new(id.clone(), ArtifactState::Available, "bob").with_reason("release"),
);
assert!(
back.is_err(),
"a release of an invalidated package is refused"
);
assert!(
back.unwrap_err().to_string().contains("invalidated"),
"the refusal names the invalidation"
);
}
#[test]
fn an_invalidated_maven_version_drops_from_its_metadata_and_is_refused() {
let up = upstream();
let r = start_with_token(&maven_sources(&up));
let chain = r.chain();
let pom_path = format!("{HAMCREST}/hamcrest-core-1.3.pom");
let jar_path = format!("{HAMCREST}/hamcrest-core-1.3.jar");
get(&r.http, &format!("/production/central/{pom_path}"), &[]);
let (pom, jar) = (
maven::id("central", &pom_path),
maven::id("central", &jar_path),
);
wait_state(&chain, &pom, ArtifactState::PolicyChecked);
wait_state(&chain, &jar, ArtifactState::PolicyChecked);
approve(&chain, &pom);
approve(&chain, &jar);
assert_eq!(
get(&r.http, &format!("/production/central/{jar_path}"), &[]).status,
200
);
let meta = "/production/central/org/hamcrest/hamcrest-core/maven-metadata.xml";
assert!(get(&r.http, meta, &[])
.text()
.contains("<version>1.3</version>"));
invalidate(&chain, &pom);
invalidate(&chain, &jar);
let j = get_settled(&r.http, &format!("/production/central/{jar_path}"));
assert_eq!(j.status, 403, "{}", j.text());
assert!(
j.text().contains("invalidated by upstream 'dmz'"),
"the refusal names the reason: {}",
j.text()
);
let m = get_settled(&r.http, meta);
assert!(
!m.text().contains("<version>1.3</version>"),
"maven-metadata.xml drops it: {} {}",
m.status,
m.text()
);
// Quarantine, not delete: the record and the sealed bytes stay.
let h = chain.history(&jar);
assert_eq!(h.last().unwrap().reason, INVALIDATED);
assert_eq!(h.last().unwrap().from, ArtifactState::Available);
// Audited on the artifact's trail.
let audit = std::fs::read_dir(r._data.path().join("audit"))
.unwrap()
.flatten()
.map(|e| std::fs::read_to_string(e.path()).unwrap_or_default())
.collect::<String>();
assert!(
audit.contains("\"invalidated\"") && audit.contains(&h[0].sha256),
"the trail carries the invalidated step"
);
}
#[test]
fn an_invalidated_crate_drops_from_the_sparse_index_and_is_refused() {
let up = upstream();
let r = start(&crates_sources(&up, false));
let chain = r.chain();
get(&r.http, "/production/crates-io/cf/g-/cfg-if", &[]);
let id = crates::id("crates-io", "cfg-if", "1.0.5");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
approve(&chain, &id);
assert_eq!(
get(&r.http, "/production/crates-io/cf/g-/cfg-if", &[]).status,
200
);
invalidate(&chain, &id);
let index = get_settled(&r.http, "/production/crates-io/cf/g-/cfg-if");
assert!(
!index.text().contains("\"yanked\":false") && !index.text().contains("\"vers\":\"1.0.5\""),
"the sparse index lists no AVAILABLE version and never the invalidated one: {}",
index.text()
);
assert!(
!index.text().contains("\"vers\":\"1.0.5\""),
"{}",
index.text()
);
let c = get_settled(&r.http, "/production/crates-io/cfg-if/cfg-if-1.0.5.crate");
assert_eq!(c.status, 403, "{}", c.text());
assert!(c.text().contains("invalidated"), "{}", c.text());
}
#[test]
fn an_invalidated_npm_version_drops_from_the_packument_and_is_refused() {
let up = upstream();
let pt = format!(
r#"sources: [ (name: "bare", kind: npm, url: "{}/bare/") ], rules: (require_signed: false), retry_after_secs: Some(1),"#,
up.base
);
let r = start(&pt);
let chain = r.chain();
get(
&r.http,
"/production/bare/sigstore",
&[("Accept", "application/json")],
);
let id = npm::id("bare", "sigstore", "5.0.0");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
approve(&chain, &id);
let p = get_settled(&r.http, "/production/bare/sigstore");
assert_eq!(p.status, 200, "{}", p.text());
assert!(p.json()["versions"]["5.0.0"].is_object());
invalidate(&chain, &id);
let p = get_settled(&r.http, "/production/bare/sigstore");
assert_ne!(p.status, 200, "{}", p.text());
assert!(
!p.text().contains("\"5.0.0\":{"),
"the packument drops the version: {}",
p.text()
);
let t = get_settled(&r.http, "/production/bare/sigstore/-/sigstore-5.0.0.tgz");
assert_eq!(t.status, 403, "{}", t.text());
}
// ── real clients against the real registries (`--ignored`; online) ─────────────
//
// `cargo test --features jobs,online,supply-chain --test pull_through_npm_maven_crates -- --ignored`
// (T9's `.asc` test needs `maven-pgp` too: `--features jobs,online,supply-chain,maven-pgp`; without it,
// it checks the boot's refusal by name)
// in a container that has `npm`, `mvn` and `cargo` on its PATH and reaches
// registry.npmjs.org, repo1.maven.org and index.crates.io. A person (bob) approves
// every pulled coordinate as it reaches POLICY_CHECKED, as a reviewer would.
/// Approve every pull-through coordinate at POLICY_CHECKED until dropped.
struct Approver {
stop: Arc<std::sync::atomic::AtomicBool>,
approved: Arc<Mutex<Vec<String>>>,
join: Option<std::thread::JoinHandle<()>>,
}
impl Approver {
fn start(chain: Arc<IntakeChain>) -> Self {
let stop = Arc::new(std::sync::atomic::AtomicBool::new(false));
let approved: Arc<Mutex<Vec<String>>> = Arc::default();
let (s2, a2) = (Arc::clone(&stop), Arc::clone(&approved));
let join = std::thread::spawn(move || {
while !s2.load(Ordering::SeqCst) {
for (id, st) in chain.all() {
if st.state == ArtifactState::PolicyChecked
&& chain
.advance(
TransitionRequest::new(id.clone(), ArtifactState::Approved, "bob")
.with_reason("reviewed"),
)
.is_ok()
{
a2.lock().unwrap().push(id.name.clone());
}
}
std::thread::sleep(Duration::from_millis(50));
}
});
Self {
stop,
approved,
join: Some(join),
}
}
fn approved(&self) -> Vec<String> {
self.approved.lock().unwrap().clone()
}
}
impl Drop for Approver {
fn drop(&mut self) {
self.stop.store(true, Ordering::SeqCst);
if let Some(j) = self.join.take() {
let _ = j.join();
}
}
}
fn run(cmd: &mut std::process::Command) -> std::process::Output {
let out = cmd.output().unwrap_or_else(|e| panic!("{cmd:?}: {e}"));
eprintln!(
"{cmd:?}\n--- stdout\n{}\n--- stderr\n{}",
String::from_utf8_lossy(&out.stdout),
String::from_utf8_lossy(&out.stderr)
);
out
}
#[test]
#[ignore = "a real npm against registry.npmjs.org through Holger"]
fn real_client_npm_install_through_holger() {
let pt = format!(
r#"sources: [ (name: "npmjs", kind: npm, url: "https://registry.npmjs.org/") ],
rules: (require_signed: true, keys: [
(id: "{NPM_KEY_ID}", alg: ecdsa_p256, public_key: "{NPM_KEY}"),
(id: "{NPM_OLD_KEY_ID}", alg: ecdsa_p256, public_key: "{NPM_OLD_KEY}"),
]),
retry_after_secs: Some(1),"#
);
let r = start(&pt);
let approver = Approver::start(r.chain());
let dir = tempfile::tempdir().unwrap();
std::fs::write(
dir.path().join("package.json"),
r#"{"name":"pt2-demo","version":"1.0.0","dependencies":{"is-number":"7.0.0"}}"#,
)
.unwrap();
std::fs::write(
dir.path().join(".npmrc"),
format!("registry=http://{}/production/npmjs/\n", r.http),
)
.unwrap();
let out = run(std::process::Command::new("npm")
.args([
"install",
"--no-audit",
"--no-fund",
"--ignore-scripts",
"--fetch-retries=6",
"--fetch-retry-mintimeout=1000",
"--fetch-retry-maxtimeout=3000",
"--cache",
])
.arg(dir.path().join("cache"))
.current_dir(dir.path()));
assert!(out.status.success(), "npm install failed");
let installed = std::fs::read(dir.path().join("node_modules/is-number/package.json")).unwrap();
assert!(String::from_utf8_lossy(&installed).contains("\"version\": \"7.0.0\""));
assert!(
approver
.approved()
.iter()
.any(|n| n == "npmjs/is-number/-/is-number-7.0.0.tgz"),
"{:?}",
approver.approved()
);
// It came through the chain: the registry's signature verified at SCANNED.
let id = npm::id("npmjs", "is-number", "7.0.0");
let h = r.chain().history(&id);
let prov = h
.iter()
.flat_map(|e| e.evidence.iter())
.find(|e| e.kind == EvidenceKind::Provenance)
.cloned()
.expect("a provenance slot");
assert_eq!(prov.verdict, EvidenceVerdict::Pass, "{prov:?}");
}
#[test]
#[ignore = "a real mvn against Maven Central through Holger"]
fn real_client_mvn_resolves_through_holger() {
let pt = r#"sources: [ (name: "central", kind: maven, url: "https://repo1.maven.org/maven2/") ],
rules: (require_signed: false), retry_after_secs: Some(1),"#;
let r = start(pt);
let approver = Approver::start(r.chain());
let dir = tempfile::tempdir().unwrap();
let holger = format!("http://{}/production/central/", r.http);
std::fs::write(
dir.path().join("pom.xml"),
format!(
r#"<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion>
<groupId>pt2</groupId><artifactId>demo</artifactId><version>1.0</version>
<repositories><repository><id>central</id><url>{holger}</url></repository></repositories>
<pluginRepositories><pluginRepository><id>central</id><url>https://repo1.maven.org/maven2/</url></pluginRepository></pluginRepositories>
<dependencies><dependency><groupId>org.hamcrest</groupId><artifactId>hamcrest-core</artifactId><version>1.3</version></dependency></dependencies>
</project>"#
),
)
.unwrap();
let repo = dir.path().join("m2");
// Maven before 3.9.5 does not retry a 503 (3.9.5 and later retry it three times, honouring
// Retry-After; measured, HOL-DOCFIND i); Holger holds a Maven request (bounded) until its
// pull has walked as far as policy lets it, and a version that waits for a person
// is a 404 Maven caches. So each level of the tree (the POM, its parent) takes one
// run after the approval, as it would for a developer: `mvn -U` again.
let mut ok = false;
for _ in 0..6 {
let out = run(std::process::Command::new("mvn")
.args(["-B", "-q", "-U", "dependency:resolve"])
.arg(format!("-Dmaven.repo.local={}", repo.display()))
.current_dir(dir.path()));
if out.status.success() {
ok = true;
break;
}
std::thread::sleep(Duration::from_millis(500));
}
assert!(ok, "mvn never resolved through Holger");
let jar = repo.join("org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar");
assert_eq!(
std::fs::read(&jar).unwrap(),
fixture(&format!("maven/{HAMCREST}/hamcrest-core-1.3.jar"))
);
let names = approver.approved();
for want in [
"central/org/hamcrest/hamcrest-core/1.3/hamcrest-core-1.3.jar",
"central/org/hamcrest/hamcrest-parent/1.3/hamcrest-parent-1.3.pom",
] {
assert!(
names.iter().any(|n| n == want),
"{want} not pulled through the chain: {names:?}"
);
}
}
#[test]
#[ignore = "a real cargo against index.crates.io through Holger"]
fn real_client_cargo_fetch_through_holger() {
let pt = r#"sources: [ (name: "crates-io", kind: crates, url: "sparse+https://index.crates.io/") ],
rules: (require_signed: false), retry_after_secs: Some(1),"#;
let r = start(pt);
let approver = Approver::start(r.chain());
let dir = tempfile::tempdir().unwrap();
let home = dir.path().join("cargo-home");
std::fs::create_dir_all(home.join("..").join("demo/src")).unwrap();
let demo = dir.path().join("demo");
std::fs::write(demo.join("Cargo.toml"), "[package]\nname = \"demo\"\nversion = \"0.1.0\"\nedition = \"2021\"\n\n[dependencies]\ncfg-if = \"1\"\n").unwrap();
std::fs::write(demo.join("src/lib.rs"), "").unwrap();
std::fs::create_dir_all(&home).unwrap();
std::fs::write(
home.join("config.toml"),
format!(
"[source.crates-io]\nreplace-with = \"holger\"\n[source.holger]\nregistry = \"sparse+http://{}/production/crates-io/\"\n[net]\nretry = 10\n",
r.http
),
)
.unwrap();
let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into());
let out = run(std::process::Command::new(cargo)
.args(["fetch"])
.env("CARGO_HOME", &home)
.current_dir(&demo));
assert!(out.status.success(), "cargo fetch failed");
let names = approver.approved();
assert!(
names
.iter()
.any(|n| n.starts_with("crates-io/cfg-if/cfg-if-1.")),
"{names:?}"
);
let lock = std::fs::read_to_string(demo.join("Cargo.lock")).unwrap();
assert!(lock.contains("name = \"cfg-if\""), "{lock}");
}
// ── Go modules from a GOPROXY (Holger v2.0 H6) ──────────────────────────────
const GO_MOD: &str = "module example.com/hello\n\ngo 1.21\n";
fn go_module_zip(module: &str, version: &str, extra: Option<&str>) -> Vec<u8> {
let prefix = format!("{module}@{version}");
let go_mod = format!("module {module}\n\ngo 1.21\n");
let (m, src) = (format!("{prefix}/go.mod"), format!("{prefix}/hello.go"));
let mut entries: Vec<(&str, &[u8])> = vec![
(&m, go_mod.as_bytes()),
(
&src,
b"package hello\n\nfunc Hello() string { return \"hi\" }\n",
),
];
if let Some(e) = extra {
entries.push((e, b"package other\n"));
}
zip_entry::fixture::zip(&entries)
}
/// The fake GOPROXY: `hello` is honest, `evil`'s zip carries a file outside its module,
/// `liar`'s `.mod` is not its zip's `go.mod`.
fn go_route(p: &str) -> Option<Reply> {
let (module, file) = p.split_once("/@v/")?;
let name = module.strip_prefix("example.com/")?;
let (version, ext) = file.rsplit_once('.')?;
if version != "v1.0.0" || !["hello", "evil", "liar"].contains(&name) {
return None;
}
let extra = (name == "evil").then_some("example.com/other@v1.0.0/x.go");
Some(match ext {
"info" => reply(
200,
"application/json",
br#"{"Version":"v1.0.0","Time":"2026-10-01T00:00:00Z"}"#.to_vec(),
),
"mod" if name == "liar" => reply(
200,
"text/plain",
b"module example.com/liar\n\ngo 1.21\n\nrequire example.com/x v9.9.9\n".to_vec(),
),
"mod" => reply(
200,
"text/plain",
format!("module {module}\n\ngo 1.21\n").into_bytes(),
),
"zip" => reply(
200,
"application/zip",
go_module_zip(module, version, extra),
),
_ => return None,
})
}
fn go_sources(up: &Upstream) -> String {
format!(
r#"sources: [ (name: "go", kind: go, url: "{}/goproxy/") ], rules: (require_signed: false), retry_after_secs: Some(1),"#,
up.base
)
}
fn go_id(module: &str) -> traits::ArtifactId {
traits::ArtifactId {
namespace: None,
name: format!("go/{module}/@v/v1.0.0.zip"),
version: String::new(),
}
}
#[test]
fn a_go_module_is_pulled_from_a_goproxy_held_for_a_person_and_served_from_the_sealed_store() {
let up = upstream();
let r = start(&go_sources(&up));
let chain = r.chain();
let zip = "/production/go/example.com/hello/@v/v1.0.0.zip";
// The first ask is "not yet": the module version is pulled into the chain.
let first = get(&r.http, zip, &[]);
assert_eq!(first.status, 503, "{}", first.text());
assert!(first.headers.contains_key("retry-after"));
let id = go_id("example.com/hello");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
assert_walked_as(&chain, &id, "go");
let bytes = go_module_zip("example.com/hello", "v1.0.0", None);
assert_eq!(
chain.history(&id)[0].sha256,
sha256_hex(&bytes),
"the zip as the proxy served it"
);
assert_eq!(
get_settled(&r.http, zip).status,
404,
"held: it waits for a person"
);
assert_eq!(
get(&r.http, "/production/go/example.com/hello/@v/list", &[]).text(),
"",
"nothing AVAILABLE to list"
);
// After the approval every GOPROXY read is answered from the sealed zip.
approve(&chain, &id);
let z = get(&r.http, zip, &[]);
assert_eq!((z.status, z.body == bytes), (200, true), "{}", z.text());
assert_eq!(
get(
&r.http,
"/production/go/example.com/hello/@v/v1.0.0.mod",
&[]
)
.text(),
GO_MOD
);
assert_eq!(
get(
&r.http,
"/production/go/example.com/hello/@v/v1.0.0.info",
&[]
)
.json()["Version"],
"v1.0.0"
);
assert_eq!(
get(&r.http, "/production/go/example.com/hello/@v/list", &[]).text(),
"v1.0.0\n"
);
assert_eq!(
get(&r.http, "/production/go/example.com/hello/@latest", &[]).json()["Version"],
"v1.0.0"
);
assert_eq!(
(
up.hits("/goproxy/example.com/hello/@v/v1.0.0.zip"),
up.hits("/goproxy/example.com/hello/@v/v1.0.0.mod")
),
(1, 1),
"pulled once"
);
}
/// H4 + H6: an invalidation from the upstream reaches the Go index too. The version drops
/// from `@v/list` and `@latest`, and its `.zip`, `.mod` and `.info` are refused with the
/// reason; the sealed bytes stay.
#[test]
fn an_invalidated_go_module_version_drops_from_list_and_latest_and_is_refused() {
let up = upstream();
let r = start(&go_sources(&up));
let chain = r.chain();
let base = "/production/go/example.com/hello/@v";
assert_eq!(get(&r.http, &format!("{base}/v1.0.0.zip"), &[]).status, 503);
let id = go_id("example.com/hello");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
approve(&chain, &id);
assert_eq!(get(&r.http, &format!("{base}/v1.0.0.zip"), &[]).status, 200);
assert_eq!(
get(&r.http, &format!("{base}/list"), &[]).text(),
"v1.0.0\n"
);
invalidate(&chain, &id);
let list = get_settled(&r.http, &format!("{base}/list"));
assert!(
!list.text().contains("v1.0.0"),
"@v/list drops it: {} {}",
list.status,
list.text()
);
let latest = get_settled(&r.http, "/production/go/example.com/hello/@latest");
assert_ne!(
latest.status,
200,
"@latest names no invalidated version: {}",
latest.text()
);
for f in ["v1.0.0.zip", "v1.0.0.mod", "v1.0.0.info"] {
let a = get_settled(&r.http, &format!("{base}/{f}"));
assert_eq!(a.status, 403, "{f}: {}", a.text());
assert!(
a.text().contains("invalidated by upstream 'dmz'"),
"{f}: the refusal names the reason: {}",
a.text()
);
}
assert_eq!(
chain.history(&id).last().unwrap().reason,
INVALIDATED,
"quarantined, not deleted"
);
}
#[test]
fn a_go_module_zip_with_a_file_outside_its_module_is_refused_and_never_stored() {
let up = upstream();
let r = start(&go_sources(&up));
let path = "/production/go/example.com/evil/@v/v1.0.0.zip";
assert_eq!(get(&r.http, path, &[]).status, 503);
let denied = get_settled(&r.http, path);
assert_eq!(denied.status, 403, "{}", denied.text());
assert!(
denied.text().contains("example.com/other@v1.0.0/x.go")
&& denied.text().contains("outside"),
"{}",
denied.text()
);
assert!(
r.chain().status(&go_id("example.com/evil")).is_none(),
"never ingested"
);
}
#[test]
fn a_go_module_whose_mod_is_not_its_zips_go_mod_is_refused() {
let up = upstream();
let r = start(&go_sources(&up));
let path = "/production/go/example.com/liar/@v/v1.0.0.mod";
assert_eq!(get(&r.http, path, &[]).status, 503);
let denied = get_settled(&r.http, path);
assert_eq!(denied.status, 403, "{}", denied.text());
assert!(denied.text().contains("go.mod"), "{}", denied.text());
assert!(
r.chain().status(&go_id("example.com/liar")).is_none(),
"never ingested"
);
}
/// **A pinned npm version that is not `latest` pulls through** (lane HOL-FIELD-TEST, npm half of
/// P2). In the field `npm install lodash@4.17.15` ended in "No matching version": the packument
/// pull took only `latest`, and the packument lists only AVAILABLE versions, so npm never asked
/// for the version it was told to install. Now every version the registry lists that is not
/// AVAILABLE is in the packument DEPRECATED with the reason (npm prefers a non-deprecated version
/// for a range, so an unpinned install keeps to what is approved), its tarball on Holger's door;
/// an exact pin asks for that tarball, which pulls exactly that version into the chain.
#[test]
fn a_pinned_npm_version_that_is_not_latest_is_listed_deprecated_and_pulled_when_asked() {
let up = upstream();
let r = start(&format!(
r#"sources: [ (name: "npmjs", kind: npm, url: "{}/npm/") ], rules: (require_signed: false), retry_after_secs: Some(1),"#,
up.base
));
let chain = r.chain();
let accept = [("Accept", "application/json")];
// 1. The first ask pulls `latest`; then the packument lists every version.
assert_eq!(
get(&r.http, "/production/npmjs/leftpad-field", &accept).status,
503
);
let started = Instant::now();
let doc = loop {
let g = get(&r.http, "/production/npmjs/leftpad-field", &accept);
if g.status == 200 {
break g.json();
}
assert!(
started.elapsed() < Duration::from_secs(30),
"never listed: {}",
g.text()
);
std::thread::sleep(Duration::from_millis(100));
};
let old = &doc["versions"]["1.0.0"];
assert!(
old["deprecated"]
.as_str()
.is_some_and(|d| d.contains("not in Holger yet")),
"a version Holger has not pulled is deprecated, with the reason: {doc}"
);
assert_eq!(
old["dist"]["tarball"],
format!(
"http://{}/production/npmjs/leftpad-field/-/leftpad-field-1.0.0.tgz",
r.http
),
"{doc}"
);
// 2. Asking for its tarball pulls exactly that version; it waits for a person.
let tgz = "/production/npmjs/leftpad-field/-/leftpad-field-1.0.0.tgz";
assert_eq!(get(&r.http, tgz, &[]).status, 503);
let id = npm::id("npmjs", "leftpad-field", "1.0.0");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
approve(&chain, &id);
let got = get_settled(&r.http, tgz);
assert_eq!(got.status, 200, "{}", got.text());
assert_eq!(got.body, leftpad_tgz("1.0.0"));
// 3. AVAILABLE now: listed, no longer deprecated.
let doc = get(&r.http, "/production/npmjs/leftpad-field", &accept).json();
assert!(
doc["versions"]["1.0.0"].get("deprecated").is_none(),
"{doc}"
);
}
/// **A plugin group's metadata names its prefixes** (lane HOL-FIELD-TEST P13). In the field
/// `mvn dependency:get` through Holger said "No plugin found for prefix 'dependency'": Maven
/// resolves a prefix from the GROUP's `org/apache/maven/plugins/maven-metadata.xml`
/// (`<plugins><plugin><prefix>`), and Holger read that path as the metadata of an artifact
/// `plugins` and answered nothing. The group's metadata is names only (prefix to artifactId):
/// Holger serves the plugin list the source publishes, and every plugin's bytes still come
/// through the chain. The fixture is the real document of Maven Central.
#[test]
fn a_plugin_groups_metadata_names_its_prefixes_for_maven() {
let up = upstream();
let r = start_with_token(&maven_sources(&up));
let got = get_settled(
&r.http,
"/production/central/org/apache/maven/plugins/maven-metadata.xml",
);
assert_eq!(got.status, 200, "{}", got.text());
let t = got.text();
assert!(
t.contains("<prefix>dependency</prefix>")
&& t.contains("<artifactId>maven-dependency-plugin</artifactId>"),
"{t}"
);
assert!(
!t.contains("<versions>"),
"a group's metadata names no versions: {t}"
);
}
/// **A crate version that is not the newest pulls through from a lock file** (lane
/// HOL-FIELD-TEST, the cargo half of P2). In the field `cfg-if = "=1.0.0"` through Holger said
/// "candidate versions found which didn't match: 1.0.5": the index pull took only the newest,
/// and the index lists only AVAILABLE versions. Now every version the registry lists that is
/// not AVAILABLE is on Holger's index line YANKED: cargo resolves no new requirement onto it,
/// a `Cargo.lock` (or `cargo update --precise`) that names it takes it, and asking for its
/// `.crate` pulls exactly that version into the chain. `cfg-if-1.0.0.crate` is the real one.
#[test]
fn a_locked_crate_version_that_is_not_newest_is_listed_yanked_and_pulled_when_its_crate_is_asked() {
let up = upstream();
let r = start(&crates_sources(&up, false));
let chain = r.chain();
let index = "/production/crates-io/cf/g-/cfg-if";
let started = Instant::now();
let text = loop {
let g = get(&r.http, index, &[]);
if g.status == 200 {
break g.text();
}
assert!(
started.elapsed() < Duration::from_secs(30),
"never listed: {}",
g.text()
);
std::thread::sleep(Duration::from_millis(100));
};
let line = |v: &str, text: &str| -> serde_json::Value {
text.lines()
.map(|l| serde_json::from_str::<serde_json::Value>(l).unwrap())
.find(|l| l["vers"] == v)
.unwrap_or_else(|| panic!("{v} is on the index: {text}"))
};
assert_eq!(line("1.0.0", &text)["yanked"], true, "{text}");
let crate_path = "/production/crates-io/cfg-if/cfg-if-1.0.0.crate";
assert_eq!(get(&r.http, crate_path, &[]).status, 503);
let id = crates::id("crates-io", "cfg-if", "1.0.0");
wait_state(&chain, &id, ArtifactState::PolicyChecked);
approve(&chain, &id);
let got = get_settled(&r.http, crate_path);
assert_eq!(got.status, 200, "{}", got.text());
assert_eq!(got.body, fixture("crates/cfg-if-1.0.0.crate"));
let text = get(&r.http, index, &[]).text();
assert_eq!(
line("1.0.0", &text)["yanked"],
false,
"AVAILABLE now: {text}"
);
}