use std::collections::HashMap; use rpki::audit::{DiscoveredFrom, PublicationPointAudit}; use rpki::report::Warning; use rpki::storage::{FetchCachePpPack, PackTime}; use rpki::validation::manifest::PublicationPointSource; use rpki::validation::objects::{ObjectsOutput, ObjectsStats}; use rpki::validation::tree::{ CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner, TreeRunConfig, run_tree_serial, }; fn empty_pack(manifest_uri: &str, pp_uri: &str) -> FetchCachePpPack { FetchCachePpPack { format_version: FetchCachePpPack::FORMAT_VERSION_V1, publication_point_rsync_uri: pp_uri.to_string(), manifest_rsync_uri: manifest_uri.to_string(), manifest_number_be: vec![1], 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, } } fn discovered_child( parent_manifest_uri: &str, child_manifest_uri: &str, ) -> DiscoveredChildCaInstance { let name = child_manifest_uri .rsplit('/') .next() .unwrap_or("child.mft") .trim_end_matches(".mft"); DiscoveredChildCaInstance { handle: ca_handle(child_manifest_uri), discovered_from: DiscoveredFrom { parent_manifest_rsync_uri: parent_manifest_uri.to_string(), child_ca_certificate_rsync_uri: format!("rsync://example.test/repo/{name}.cer"), child_ca_certificate_sha256_hex: "00".repeat(32), }, } } #[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![ discovered_child(root_manifest, bad_child_manifest), discovered_child(root_manifest, 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" ); }