server/lib/tests/build_history_repo_wiring.rs
//! **A `znippy-build-thing` repo, configured the way an operator configures
//! one, and answering over the real routing.**
//!
//! The backend crate's own tests drive `BuildThingRepoZnippy` directly. That
//! leaves the seam this file covers: `ron_repo_type: "znippy-build-thing"`
//! reaching that backend at all. A backend that exists and is never selected is
//! the same as no backend, and it is exactly what a missing match arm looks
//! like.
use std::path::Path;
use holger_server_lib::read_ron_config;
const SHA: &str = "1111111111111111111111111111111111111111111111111111111111111111";
/// Seal a minimal but real build-history archive.
fn seal(path: &Path) {
let attestation = format!(
r#"{{"attestation_version":1,"receipt":{{"receipt_version":1,"build_thing":{{"krate":"korp-build-thing","version":"0.3.1","hash":"a91f4c2e"}},"atom":"korp-server","target":"exe","settings":{{}},"inputs":{{}}}},"artifact":{{"sha256":"{SHA}","bytes":4096}}}}"#
);
let index = format!(
r#"{{"kind":"znippy-build-thing","package_version":1,"attestations":[{{"artifact_sha256":"{SHA}","artifact_bytes":4096,"build_thing":"korp-build-thing:0.3.1:a91f4c2e","build_thing_krate":"korp-build-thing","build_thing_version":"0.3.1","atom":"korp-server","target":"exe","path":"attestations/{SHA}.json"}}]}}"#
);
let files = vec![
("index.json".to_string(), index.into_bytes()),
(format!("attestations/{SHA}.json"), attestation.into_bytes()),
(
"by-recipe/korp-build-thing".to_string(),
SHA.as_bytes().to_vec(),
),
(
"by-recipe/korp-build-thing/0.3.1".to_string(),
SHA.as_bytes().to_vec(),
),
("by-atom/korp-server".to_string(), SHA.as_bytes().to_vec()),
];
// Packed the way `graceful_backend_init` packs its fixtures — the stream API
// this repo already uses in tests, rather than a second way to make an
// archive. `compress_stream` appends the extension itself.
let base = path.with_extension("");
let compressor = znippy_compress::compress_stream(&base, true).expect("compressor");
let sender = compressor.sender().clone();
for (relative_path, data) in files {
sender
.send(znippy_compress::ArchiveEntry {
relative_path,
data,
pkg_type: None,
repo: None,
})
.expect("send entry");
}
drop(sender);
compressor.finish().expect("seal");
assert!(path.is_file(), "no archive at {}", path.display());
}
fn config(tmp: &Path, archive: &Path) -> String {
format!(
r#"(
exposed_endpoints: [ ( ron_name: "e", ron_url: "127.0.0.1:0" ) ],
storage_endpoints: [ ( ron_name: "s", ron_storage_type: "file", ron_path: "{store}" ) ],
repositories: [
( ron_name: "history", ron_repo_type: "znippy-build-thing", ron_upstreams: [], ron_in: None,
ron_out: Some(( ron_storage_endpoint: "s", ron_exposed_endpoint: "e" )),
ron_archive_path: Some("{archive}") ),
],
)"#,
store = tmp.display(),
archive = archive.display(),
)
}
/// The repo type selects the build-history backend, and that backend answers
/// the question the package exists for.
#[test]
fn the_repo_type_reaches_the_build_history_backend_and_it_answers() {
let tmp = tempfile::tempdir().unwrap();
let archive = tmp.path().join("history.znippy");
seal(&archive);
let cfg = tmp.path().join("h.ron");
std::fs::write(&cfg, config(tmp.path(), &archive)).unwrap();
let mut holger = read_ron_config(&cfg).unwrap();
holger
.instantiate_backends()
.expect("a znippy-build-thing repo must instantiate");
let repo = holger
.repositories
.iter()
.find(|r| r.ron_name == "history")
.unwrap();
let backend = repo
.backend_repository
.as_ref()
.expect("the repo type must select a backend — an unselected backend is no backend");
// Read-only: a provenance store anyone can append to attests nothing.
assert!(!backend.is_writable());
// What did this recipe build? — over the wired route, not the direct call.
let (code, _headers, body) = backend
.handle_http2_request("GET", "/history/recipe/korp-build-thing", b"")
.expect("answer");
assert_eq!(code, 200, "{}", String::from_utf8_lossy(&body));
assert_eq!(String::from_utf8_lossy(&body).trim(), SHA);
// One attestation, by the hash of what it attests.
let (code, _headers, body) = backend
.handle_http2_request("GET", &format!("/history/artifact/{SHA}"), b"")
.expect("answer");
assert_eq!(code, 200);
assert!(String::from_utf8_lossy(&body).contains("korp-server"));
// THE RED PROBE: the routing is the build-history routing and not a generic
// path passthrough that would answer anything. A rust repo's sparse-index
// route means nothing here and must miss.
let (code, _headers, _) = backend
.handle_http2_request("GET", "/history/index/config.json", b"")
.expect("answer");
assert_eq!(
code, 404,
"a build-history repo answered a cargo sparse-index route, so the repo type \
is not selecting the backend under test"
);
assert_eq!(backend.list(None, 0).expect("list").len(), 1);
}
/// A corrupt build-history archive disables that repo rather than the server —
/// the same contract every other znippy-backed repo has, and the red probe for
/// the test above (which would pass just as happily if instantiation never
/// checked the archive at all).
#[test]
fn a_corrupt_build_history_archive_disables_only_that_repo() {
let tmp = tempfile::tempdir().unwrap();
let good = tmp.path().join("good.znippy");
seal(&good);
let bad = tmp.path().join("bad.znippy");
std::fs::write(&bad, b"this is not a znippy archive").unwrap();
let ron = format!(
r#"(
exposed_endpoints: [ ( ron_name: "e", ron_url: "127.0.0.1:0" ) ],
storage_endpoints: [ ( ron_name: "s", ron_storage_type: "file", ron_path: "{store}" ) ],
repositories: [
( ron_name: "history", ron_repo_type: "znippy-build-thing", ron_upstreams: [], ron_in: None,
ron_out: Some(( ron_storage_endpoint: "s", ron_exposed_endpoint: "e" )),
ron_archive_path: Some("{good}") ),
( ron_name: "rubble", ron_repo_type: "znippy-build-thing", ron_upstreams: [], ron_in: None,
ron_out: Some(( ron_storage_endpoint: "s", ron_exposed_endpoint: "e" )),
ron_archive_path: Some("{bad}") ),
],
)"#,
store = tmp.path().display(),
good = good.display(),
bad = bad.display(),
);
let cfg = tmp.path().join("h.ron");
std::fs::write(&cfg, ron).unwrap();
let mut holger = read_ron_config(&cfg).unwrap();
holger
.instantiate_backends()
.expect("one corrupt archive must not fail the whole server");
let live = holger
.repositories
.iter()
.find(|r| r.ron_name == "history")
.unwrap();
let dead = holger
.repositories
.iter()
.find(|r| r.ron_name == "rubble")
.unwrap();
assert!(live.backend_repository.is_some());
assert!(
dead.backend_repository.is_none(),
"a corrupt archive must disable its repo — otherwise instantiation never \
opened the archive and the healthy case above proves nothing"
);
}