use holger_server_lib::retention::{RetentionPolicy, RetentionReason};
use holger_server_lib::{read_ron_config, wire_holger};
use traits::ArtifactId;
fn from_ron(ron: &str) -> holger_server_lib::Holger {
use std::io::Write;
let mut f = tempfile::NamedTempFile::new().unwrap();
f.write_all(ron.as_bytes()).unwrap();
read_ron_config(f.path()).expect("RON must parse")
}
#[test]
fn plan_repo_retention_keeps_last_n_over_a_live_listing() {
let dir = tempfile::tempdir().unwrap();
let d = dir.path().display();
let ron = format!(
r#"(
exposed_endpoints: [ ( ron_name: "dev", ron_url: "127.0.0.1:0", ron_http_url: None ) ],
storage_endpoints: [ ( ron_name: "inert", ron_storage_type: "znippy", ron_path: "{d}/inert/" ) ],
repositories: [
( ron_name: "rust-dev", ron_repo_type: "rust", ron_data_dir: Some("{d}/rust-dev"), ron_base_url: Some("http://x"), ron_upstreams: [], ron_in: None, ron_out: Some(( ron_storage_endpoint: "inert", ron_exposed_endpoint: "dev" )) ),
],
)
"#
);
let mut holger = from_ron(&ron);
holger.instantiate_backends().expect("backend builds");
wire_holger(&mut holger).expect("wires");
for v in ["1.0.0", "1.1.0", "1.2.0", "1.10.0", "2.0.0"] {
let id = ArtifactId { namespace: Some("crates".into()), name: "serde".into(), version: v.into() };
holger.put("rust-dev", &id, format!("serde-{v}").as_bytes()).unwrap();
}
for v in ["0.1.0", "0.2.0"] {
let id = ArtifactId { namespace: Some("crates".into()), name: "tokio".into(), version: v.into() };
holger.put("rust-dev", &id, format!("tokio-{v}").as_bytes()).unwrap();
}
let policy = RetentionPolicy::new().keep_last(2);
let plan = holger
.plan_repo_retention("rust-dev", &policy, 1_700_000_000)
.expect("plan builds");
assert_eq!(plan.name_count, 2, "serde + tokio");
assert_eq!(plan.delete_count, 3, "3 old serde versions reclaimed");
assert_eq!(plan.kept_count, 4, "2 serde + 2 tokio kept");
let mut reclaimed: Vec<String> = plan
.to_delete()
.filter(|r| r.id.name == "serde")
.map(|r| r.id.version.clone())
.collect();
reclaimed.sort();
assert_eq!(reclaimed, vec!["1.0.0", "1.1.0", "1.2.0"]);
assert!(plan
.decisions
.iter()
.filter(|dcn| dcn.delete)
.all(|dcn| dcn.reasons.contains(&RetentionReason::SupersededByNewer)));
}
#[test]
fn plan_repo_retention_property_pin_survives_sweep() {
let dir = tempfile::tempdir().unwrap();
let d = dir.path().display();
let ron = format!(
r#"(
exposed_endpoints: [ ( ron_name: "dev", ron_url: "127.0.0.1:0", ron_http_url: None ) ],
storage_endpoints: [ ( ron_name: "inert", ron_storage_type: "znippy", ron_path: "{d}/inert/" ) ],
repositories: [
( ron_name: "rust-dev", ron_repo_type: "rust", ron_data_dir: Some("{d}/rust-dev"), ron_base_url: Some("http://x"), ron_upstreams: [], ron_in: None, ron_out: Some(( ron_storage_endpoint: "inert", ron_exposed_endpoint: "dev" )) ),
],
audit: ( dir: "{d}/data/audit" ),
)
"#
);
let mut holger = from_ron(&ron);
holger.instantiate_backends().unwrap();
wire_holger(&mut holger).unwrap();
for v in ["1.0.0", "2.0.0", "3.0.0"] {
let id = ArtifactId { namespace: Some("crates".into()), name: "serde".into(), version: v.into() };
holger.put("rust-dev", &id, format!("serde-{v}").as_bytes()).unwrap();
}
let pinned = ArtifactId { namespace: Some("crates".into()), name: "serde".into(), version: "1.0.0".into() };
holger.set_artifact_property("rust-dev", &pinned, "keep", vec!["true".into()]).unwrap();
let policy = RetentionPolicy::new()
.keep_last(1)
.protect_latest(false)
.protect_property_kv("keep", "true");
let plan = holger.plan_repo_retention("rust-dev", &policy, 1_700_000_000).expect("plan builds");
let v1 = plan.decisions.iter().find(|d| d.record.id.version == "1.0.0").unwrap();
assert!(!v1.delete, "the keep=true-pinned 1.0.0 must survive the sweep");
assert!(v1.reasons.contains(&RetentionReason::ProtectedByProperty), "kept for the pin");
let v2 = plan.decisions.iter().find(|d| d.record.id.version == "2.0.0").unwrap();
assert!(v2.delete, "the untagged 2.0.0 IS reclaimed (control)");
}
#[test]
fn plan_repo_retention_noop_policy_keeps_all() {
let dir = tempfile::tempdir().unwrap();
let d = dir.path().display();
let ron = format!(
r#"(
exposed_endpoints: [ ( ron_name: "dev", ron_url: "127.0.0.1:0", ron_http_url: None ) ],
storage_endpoints: [ ( ron_name: "inert", ron_storage_type: "znippy", ron_path: "{d}/inert/" ) ],
repositories: [
( ron_name: "rust-dev", ron_repo_type: "rust", ron_data_dir: Some("{d}/rust-dev"), ron_base_url: Some("http://x"), ron_upstreams: [], ron_in: None, ron_out: Some(( ron_storage_endpoint: "inert", ron_exposed_endpoint: "dev" )) ),
],
)
"#
);
let mut holger = from_ron(&ron);
holger.instantiate_backends().unwrap();
wire_holger(&mut holger).unwrap();
for v in ["1.0.0", "2.0.0", "3.0.0"] {
let id = ArtifactId { namespace: Some("crates".into()), name: "serde".into(), version: v.into() };
holger.put("rust-dev", &id, b"x").unwrap();
}
let plan = holger
.plan_repo_retention("rust-dev", &RetentionPolicy::new(), 1_700_000_000)
.unwrap();
assert_eq!(plan.delete_count, 0);
assert_eq!(plan.kept_count, 3);
}