use std::collections::HashMap; use rpki::report::Warning; use rpki::storage::{PackTime, VerifiedPublicationPointPack}; use rpki::validation::manifest::PublicationPointSource; use rpki::validation::objects::{ObjectsOutput, ObjectsStats}; use rpki::validation::tree::{ CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner, TreeRunConfig, run_tree_serial, }; use rpki::audit::PublicationPointAudit; fn empty_pack(manifest_uri: &str, pp_uri: &str) -> VerifiedPublicationPointPack { VerifiedPublicationPointPack { format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1, publication_point_rsync_uri: pp_uri.to_string(), manifest_rsync_uri: manifest_uri.to_string(), this_update: PackTime { rfc3339_utc: "2026-01-01T00:00:00Z".to_string(), }, next_update: PackTime { rfc3339_utc: "2026-12-31T00:00:00Z".to_string(), }, verified_at: PackTime { rfc3339_utc: "2026-02-06T00:00:00Z".to_string(), }, manifest_bytes: vec![1, 2, 3], files: Vec::new(), } } fn ca_handle(manifest_uri: &str) -> CaInstanceHandle { CaInstanceHandle { depth: 0, ca_certificate_der: Vec::new(), ca_certificate_rsync_uri: None, effective_ip_resources: None, effective_as_resources: None, rsync_base_uri: "rsync://example.test/repo/".to_string(), manifest_rsync_uri: manifest_uri.to_string(), publication_point_rsync_uri: "rsync://example.test/repo/".to_string(), rrdp_notification_uri: None, } } #[derive(Default)] struct ResultRunner { by_manifest: HashMap>, } impl ResultRunner { fn with_ok(mut self, manifest: &str, res: PublicationPointRunResult) -> Self { self.by_manifest.insert(manifest.to_string(), Ok(res)); self } fn with_err(mut self, manifest: &str, err: &str) -> Self { self.by_manifest .insert(manifest.to_string(), Err(err.to_string())); self } } impl PublicationPointRunner for ResultRunner { fn run_publication_point( &self, ca: &CaInstanceHandle, ) -> Result { self.by_manifest .get(&ca.manifest_rsync_uri) .cloned() .unwrap_or_else(|| Err(format!("no mock for {}", ca.manifest_rsync_uri))) } } #[test] fn tree_continues_when_a_publication_point_fails() { let root_manifest = "rsync://example.test/repo/root.mft"; let bad_child_manifest = "rsync://example.test/repo/bad-child.mft"; let ok_child_manifest = "rsync://example.test/repo/ok-child.mft"; let runner = ResultRunner::default() .with_ok( root_manifest, PublicationPointRunResult { source: PublicationPointSource::Fresh, pack: empty_pack(root_manifest, "rsync://example.test/repo/"), warnings: Vec::new(), objects: ObjectsOutput { vrps: Vec::new(), aspas: Vec::new(), warnings: Vec::new(), stats: ObjectsStats::default(), audit: Vec::new(), }, audit: PublicationPointAudit::default(), discovered_children: vec![ca_handle(bad_child_manifest), ca_handle(ok_child_manifest)], }, ) .with_err(bad_child_manifest, "synthetic failure") .with_ok( ok_child_manifest, PublicationPointRunResult { source: PublicationPointSource::Fresh, pack: empty_pack(ok_child_manifest, "rsync://example.test/repo/ok-child/"), warnings: vec![Warning::new("ok child warning")], objects: ObjectsOutput { vrps: Vec::new(), aspas: Vec::new(), warnings: Vec::new(), stats: ObjectsStats::default(), audit: Vec::new(), }, audit: PublicationPointAudit::default(), discovered_children: Vec::new(), }, ); let out = run_tree_serial(ca_handle(root_manifest), &runner, &TreeRunConfig::default()) .expect("run tree"); assert_eq!(out.instances_processed, 2); assert_eq!(out.instances_failed, 1); assert!( out.warnings .iter() .any(|w| w.message.contains("publication point failed")), "expected failure warning" ); assert!( out.warnings .iter() .any(|w| w.message.contains("ok child warning")), "expected ok child warning propagated" ); }