1934 lines
63 KiB
Rust
1934 lines
63 KiB
Rust
use super::*;
|
|
use crate::memory_telemetry::{
|
|
MemoryTelemetryCheckpoint, MemoryTelemetrySummary, ProcessMemorySnapshot,
|
|
};
|
|
use crate::storage::{
|
|
RocksDbMemorySnapshot, RocksDbMemoryTotals, VcirCcrProjectionSizeBreakdown,
|
|
VcirChildResourceSizeBreakdown, VcirCoreFieldSizeBreakdown, VcirFieldSizeBreakdown,
|
|
VcirStorageEntrySummary, VcirStorageSummary,
|
|
};
|
|
|
|
#[test]
|
|
fn parse_help_returns_usage() {
|
|
let argv = vec!["rpki".to_string(), "--help".to_string()];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("Usage:"), "{err}");
|
|
assert!(err.contains("--db"), "{err}");
|
|
assert!(err.contains("--rsync-mirror-root"), "{err}");
|
|
assert!(err.contains("--rsync-scope"), "{err}");
|
|
assert!(err.contains("--parallel-phase2-object-workers"), "{err}");
|
|
assert!(err.contains("--memory-trim-after-validation"), "{err}");
|
|
assert!(err.contains("--enable-roa-validation-cache"), "{err}");
|
|
assert!(!err.contains("--parallel-phase1"), "{err}");
|
|
assert!(!err.contains("--parallel-phase2 "), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_unknown_argument() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--nope".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("unknown argument"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_both_tal_url_and_tal_path() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(
|
|
err.contains("one-or-more --tal-url or one-or-more --tal-path/--ta-path pairs"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_invalid_max_depth() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--max-depth".to_string(),
|
|
"nope".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("invalid --max-depth"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_ccr_out_path() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--rsync-local-dir".to_string(),
|
|
"repo".to_string(),
|
|
"--ccr-out".to_string(),
|
|
"out/example.ccr".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(
|
|
args.ccr_out_path.as_deref(),
|
|
Some(std::path::Path::new("out/example.ccr"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_memory_trim_after_validation() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--memory-trim-after-validation".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.memory_trim_after_validation);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_disables_memory_trim_after_validation_by_default() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(!args.memory_trim_after_validation);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_enable_roa_validation_cache() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--enable-roa-validation-cache".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.enable_roa_validation_cache);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_enable_transport_request_prefetch() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--enable-transport-request-prefetch".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.enable_transport_request_prefetch);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_disables_roa_validation_cache_by_default() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(!args.enable_roa_validation_cache);
|
|
assert!(!args.enable_transport_request_prefetch);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_analysis_out_and_implies_analyze() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--analysis-out".to_string(),
|
|
"run/analyze".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.analyze);
|
|
assert_eq!(
|
|
args.analysis_out_path.as_deref(),
|
|
Some(std::path::Path::new("run/analyze"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_report_json_compact_when_report_json_is_set() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--report-json".to_string(),
|
|
"out/report.json".to_string(),
|
|
"--report-json-compact".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(
|
|
args.report_json_path.as_deref(),
|
|
Some(std::path::Path::new("out/report.json"))
|
|
);
|
|
assert!(args.report_json_compact);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_rsync_scope_policy() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--rsync-scope".to_string(),
|
|
"module-root".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(args.rsync_scope_policy, RsyncScopePolicy::ModuleRoot);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_host_rsync_scope_policy() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--rsync-scope".to_string(),
|
|
"host".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(args.rsync_scope_policy, RsyncScopePolicy::Host);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_invalid_rsync_scope_policy() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--rsync-scope".to_string(),
|
|
"wide".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("invalid rsync scope should fail");
|
|
assert!(err.contains("invalid --rsync-scope"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_strict_policy_list() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--strict".to_string(),
|
|
"name,cms-der".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(
|
|
args.strict_policy,
|
|
Some(StrictPolicy {
|
|
name: true,
|
|
cms_der: true,
|
|
signed_attrs: false,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_strict_without_value_as_all() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--strict".to_string(),
|
|
"--report-json".to_string(),
|
|
"out/report.json".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(args.strict_policy, Some(StrictPolicy::all()));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_unknown_strict_policy() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--strict=unknown".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("unknown strict policy should fail");
|
|
assert!(err.contains("unknown strict policy"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn effective_cir_tal_uris_filters_skipped_multi_tal_inputs() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"afrinic.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"afrinic.cer".to_string(),
|
|
"--tal-path".to_string(),
|
|
"apnic.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"apnic.cer".to_string(),
|
|
"--tal-path".to_string(),
|
|
"arin.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"arin.cer".to_string(),
|
|
"--cir-enable".to_string(),
|
|
"--cir-out".to_string(),
|
|
"out.cir".to_string(),
|
|
"--cir-tal-uri".to_string(),
|
|
"https://example.test/afrinic.cer".to_string(),
|
|
"--cir-tal-uri".to_string(),
|
|
"https://example.test/apnic.cer".to_string(),
|
|
"--cir-tal-uri".to_string(),
|
|
"https://example.test/arin.cer".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
let mut shared = synthetic_post_validation_shared();
|
|
shared.discoveries = vec![shared.discovery.clone(), shared.discovery.clone()].into();
|
|
shared.successful_tal_inputs =
|
|
vec![args.tal_inputs[0].clone(), args.tal_inputs[2].clone()].into();
|
|
|
|
let effective = effective_cir_tal_uris_for_discoveries(
|
|
&args,
|
|
&shared,
|
|
resolve_cir_export_tal_uris(&args).expect("resolve cir tal uris"),
|
|
)
|
|
.expect("map effective cir tal uris");
|
|
|
|
assert_eq!(
|
|
effective,
|
|
vec![
|
|
"https://example.test/afrinic.cer".to_string(),
|
|
"https://example.test/arin.cer".to_string(),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_report_json_compact_without_report_json() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--report-json-compact".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("compact flag without report path should fail");
|
|
assert!(
|
|
err.contains("--report-json-compact requires --report-json"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_skip_report_build_without_report_json() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--ccr-out".to_string(),
|
|
"out/result.ccr".to_string(),
|
|
"--skip-report-build".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.skip_report_build);
|
|
assert_eq!(
|
|
args.ccr_out_path.as_deref(),
|
|
Some(std::path::Path::new("out/result.ccr"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_skip_vcir_persist() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--skip-vcir-persist".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.skip_vcir_persist);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_skip_report_build_with_report_json() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--report-json".to_string(),
|
|
"out/report.json".to_string(),
|
|
"--skip-report-build".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("skip report build with report path should fail");
|
|
assert!(
|
|
err.contains("--skip-report-build cannot be combined with --report-json"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_direct_compare_view_csv_outputs() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--vrps-csv-out".to_string(),
|
|
"out/vrps.csv".to_string(),
|
|
"--vaps-csv-out".to_string(),
|
|
"out/vaps.csv".to_string(),
|
|
"--compare-view-trust-anchor".to_string(),
|
|
"unknown".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(
|
|
args.vrps_csv_out_path.as_deref(),
|
|
Some(std::path::Path::new("out/vrps.csv"))
|
|
);
|
|
assert_eq!(
|
|
args.vaps_csv_out_path.as_deref(),
|
|
Some(std::path::Path::new("out/vaps.csv"))
|
|
);
|
|
assert_eq!(args.compare_view_trust_anchor.as_deref(), Some("unknown"));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_partial_direct_compare_view_csv_outputs() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--vrps-csv-out".to_string(),
|
|
"out/vrps.csv".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("partial direct compare view output should fail");
|
|
assert!(
|
|
err.contains("--vrps-csv-out and --vaps-csv-out must be provided together"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_external_raw_store_db() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--raw-store-db".to_string(),
|
|
"raw-store.db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(
|
|
args.raw_store_db.as_deref(),
|
|
Some(std::path::Path::new("raw-store.db"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_external_repo_bytes_db() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--repo-bytes-db".to_string(),
|
|
"repo-bytes.db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(
|
|
args.repo_bytes_db.as_deref(),
|
|
Some(std::path::Path::new("repo-bytes.db"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_cir_enable_with_raw_store_backend() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--raw-store-db".to_string(),
|
|
"raw-store.db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--rsync-local-dir".to_string(),
|
|
"repo".to_string(),
|
|
"--cir-enable".to_string(),
|
|
"--cir-out".to_string(),
|
|
"out/example.cir".to_string(),
|
|
"--cir-tal-uri".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.cir_enabled);
|
|
assert_eq!(
|
|
args.raw_store_db.as_deref(),
|
|
Some(std::path::Path::new("raw-store.db"))
|
|
);
|
|
assert_eq!(args.cir_static_root, None);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_cir_enable_with_required_paths_and_tal_override() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--rsync-local-dir".to_string(),
|
|
"repo".to_string(),
|
|
"--cir-enable".to_string(),
|
|
"--cir-out".to_string(),
|
|
"out/example.cir".to_string(),
|
|
"--cir-tal-uri".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert!(args.cir_enabled);
|
|
assert_eq!(
|
|
args.cir_out_path.as_deref(),
|
|
Some(std::path::Path::new("out/example.cir"))
|
|
);
|
|
assert_eq!(
|
|
args.cir_tal_uri.as_deref(),
|
|
Some("https://example.test/root.tal")
|
|
);
|
|
assert_eq!(
|
|
args.cir_tal_uris,
|
|
vec!["https://example.test/root.tal".to_string()]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_deprecated_cir_static_root() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--cir-enable".to_string(),
|
|
"--cir-out".to_string(),
|
|
"out/example.cir".to_string(),
|
|
"--cir-static-root".to_string(),
|
|
"out/static".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("cir-static-root should be rejected");
|
|
assert!(err.contains("no longer supported"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_default_parallel_config_and_phase2_overrides() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2-object-workers".to_string(),
|
|
"3".to_string(),
|
|
"--parallel-phase2-worker-queue-capacity".to_string(),
|
|
"17".to_string(),
|
|
"--parallel-phase2-ready-batch-size".to_string(),
|
|
"31".to_string(),
|
|
"--parallel-phase2-ready-batch-wall-time-budget-ms".to_string(),
|
|
"43".to_string(),
|
|
"--parallel-phase2-result-drain-batch-size".to_string(),
|
|
"37".to_string(),
|
|
"--parallel-phase2-finalize-batch-size".to_string(),
|
|
"41".to_string(),
|
|
"--parallel-phase2-finalize-batch-wall-time-budget-ms".to_string(),
|
|
"47".to_string(),
|
|
"--parallel-phase2-finalize-queue-capacity".to_string(),
|
|
"8192".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(args.parallel_phase2_config.object_workers, 3);
|
|
assert_eq!(args.parallel_phase2_config.worker_queue_capacity, 17);
|
|
assert_eq!(args.parallel_phase2_config.ready_batch_size, 31);
|
|
assert_eq!(
|
|
args.parallel_phase2_config.ready_batch_wall_time_budget_ms,
|
|
43
|
|
);
|
|
assert_eq!(
|
|
args.parallel_phase2_config.object_result_drain_batch_size,
|
|
37
|
|
);
|
|
assert_eq!(
|
|
args.parallel_phase2_config
|
|
.publication_point_finalize_batch_size,
|
|
41
|
|
);
|
|
assert_eq!(
|
|
args.parallel_phase2_config
|
|
.publication_point_finalize_wall_time_budget_ms,
|
|
47
|
|
);
|
|
assert_eq!(
|
|
args.parallel_phase2_config
|
|
.publication_point_finalize_queue_capacity,
|
|
8192
|
|
);
|
|
assert_eq!(args.parallel_phase1_config, ParallelPhase1Config::default());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_zero_phase2_ready_batch_size() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2-ready-batch-size".to_string(),
|
|
"0".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("zero ready batch must fail");
|
|
assert!(err.contains("--parallel-phase2-ready-batch-size"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_zero_phase2_result_drain_batch_size() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2-result-drain-batch-size".to_string(),
|
|
"0".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("zero result drain batch must fail");
|
|
assert!(
|
|
err.contains("--parallel-phase2-result-drain-batch-size"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_zero_phase2_ready_batch_wall_time_budget_ms() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2-ready-batch-wall-time-budget-ms".to_string(),
|
|
"0".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("zero ready time budget must fail");
|
|
assert!(
|
|
err.contains("--parallel-phase2-ready-batch-wall-time-budget-ms"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_zero_phase2_finalize_batch_size() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2-finalize-batch-size".to_string(),
|
|
"0".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("zero finalize batch must fail");
|
|
assert!(
|
|
err.contains("--parallel-phase2-finalize-batch-size"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_zero_phase2_finalize_batch_wall_time_budget_ms() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2-finalize-batch-wall-time-budget-ms".to_string(),
|
|
"0".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("zero finalize time budget must fail");
|
|
assert!(
|
|
err.contains("--parallel-phase2-finalize-batch-wall-time-budget-ms"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_zero_phase2_finalize_queue_capacity() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2-finalize-queue-capacity".to_string(),
|
|
"0".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("zero finalize queue capacity must fail");
|
|
assert!(
|
|
err.contains("--parallel-phase2-finalize-queue-capacity"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_removed_parallel_enable_flags() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase1".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("removed phase flag should fail");
|
|
assert!(err.contains("unknown argument: --parallel-phase1"), "{err}");
|
|
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--parallel-phase2".to_string(),
|
|
];
|
|
let err = parse_args(&argv).expect_err("removed phase flag should fail");
|
|
assert!(err.contains("unknown argument: --parallel-phase2"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_multi_tal_cir_overrides_in_file_mode() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"apnic.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"apnic.cer".to_string(),
|
|
"--tal-path".to_string(),
|
|
"arin.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"arin.cer".to_string(),
|
|
"--rsync-local-dir".to_string(),
|
|
"repo".to_string(),
|
|
"--cir-enable".to_string(),
|
|
"--cir-out".to_string(),
|
|
"out/example.cir".to_string(),
|
|
"--cir-tal-uri".to_string(),
|
|
"https://example.test/apnic.tal".to_string(),
|
|
"--cir-tal-uri".to_string(),
|
|
"https://example.test/arin.tal".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse args");
|
|
assert_eq!(
|
|
args.cir_tal_uris,
|
|
vec![
|
|
"https://example.test/apnic.tal".to_string(),
|
|
"https://example.test/arin.tal".to_string()
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_incomplete_or_invalid_cir_flags() {
|
|
let argv_missing = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--cir-enable".to_string(),
|
|
];
|
|
let err = parse_args(&argv_missing).unwrap_err();
|
|
assert!(err.contains("--cir-enable requires --cir-out"), "{err}");
|
|
|
|
let argv_needs_enable = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/root.tal".to_string(),
|
|
"--cir-out".to_string(),
|
|
"out/example.cir".to_string(),
|
|
];
|
|
let err = parse_args(&argv_needs_enable).unwrap_err();
|
|
assert!(err.contains("require --cir-enable"), "{err}");
|
|
|
|
let argv_offline_missing_uri = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"x.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"x.cer".to_string(),
|
|
"--rsync-local-dir".to_string(),
|
|
"repo".to_string(),
|
|
"--cir-enable".to_string(),
|
|
"--cir-out".to_string(),
|
|
"out/example.cir".to_string(),
|
|
];
|
|
let err = parse_args(&argv_offline_missing_uri).unwrap_err();
|
|
assert!(err.contains("requires --cir-tal-uri"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_invalid_validation_time() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--validation-time".to_string(),
|
|
"not-a-time".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("invalid --validation-time"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_invalid_max_instances() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--max-instances".to_string(),
|
|
"nope".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("invalid --max-instances"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_missing_value_for_db() {
|
|
let argv = vec!["rpki".to_string(), "--db".to_string()];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("--db requires a value"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_missing_value_for_tal_url() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("--tal-url requires a value"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_missing_db() {
|
|
let argv = vec!["rpki".to_string(), "--tal-url".to_string(), "x".to_string()];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("--db is required"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_missing_tal_mode() {
|
|
let argv = vec!["rpki".to_string(), "--db".to_string(), "db".to_string()];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(
|
|
err.contains("--tal-url") || err.contains("--tal-path"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_tal_url_mode() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert_eq!(args.tal_url.as_deref(), Some("https://example.test/x.tal"));
|
|
assert_eq!(
|
|
args.tal_urls,
|
|
vec!["https://example.test/x.tal".to_string()]
|
|
);
|
|
assert!(args.tal_path.is_none());
|
|
assert!(args.ta_path.is_none());
|
|
assert_eq!(args.tal_inputs.len(), 1);
|
|
assert_eq!(args.tal_inputs[0].tal_id, "x");
|
|
assert_eq!(args.parallel_phase1_config, ParallelPhase1Config::default());
|
|
assert_eq!(args.parallel_phase2_config, ParallelPhase2Config::default());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_multi_tal_without_parallel_flags() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/arin.tal".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/apnic.tal".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/ripe.tal".to_string(),
|
|
"--parallel-max-repo-sync-workers-global".to_string(),
|
|
"8".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert_eq!(args.tal_urls.len(), 3);
|
|
assert_eq!(args.tal_inputs.len(), 3);
|
|
assert_eq!(args.tal_inputs[0].tal_id, "arin");
|
|
assert_eq!(args.tal_inputs[1].tal_id, "apnic");
|
|
assert_eq!(args.tal_inputs[2].tal_id, "ripe");
|
|
assert_eq!(args.parallel_phase1_config.max_repo_sync_workers_global, 8);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_multi_tal_urls_by_default() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/arin.tal".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/apnic.tal".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert_eq!(args.tal_urls.len(), 2);
|
|
assert_eq!(args.tal_inputs.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_offline_mode_requires_ta() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--max-depth".to_string(),
|
|
"0".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert_eq!(args.tal_paths, vec![PathBuf::from("a.tal")]);
|
|
assert_eq!(args.ta_paths, vec![PathBuf::from("ta.cer")]);
|
|
assert_eq!(args.tal_path.as_deref(), Some(Path::new("a.tal")));
|
|
assert_eq!(args.ta_path.as_deref(), Some(Path::new("ta.cer")));
|
|
assert_eq!(args.max_depth, Some(0));
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_multiple_tal_path_pairs_by_default() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"apnic.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"apnic-ta.cer".to_string(),
|
|
"--tal-path".to_string(),
|
|
"arin.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"arin-ta.cer".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert_eq!(args.tal_paths.len(), 2);
|
|
assert_eq!(args.ta_paths.len(), 2);
|
|
assert_eq!(args.tal_inputs.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_mixed_tal_url_and_tal_path_modes() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/arin.tal".to_string(),
|
|
"--tal-path".to_string(),
|
|
"apnic.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"apnic-ta.cer".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(
|
|
err.contains(
|
|
"must specify either one-or-more --tal-url or one-or-more --tal-path/--ta-path pairs"
|
|
),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_mismatched_tal_path_and_ta_path_counts() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"apnic.tal".to_string(),
|
|
"--tal-path".to_string(),
|
|
"arin.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"apnic-ta.cer".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(
|
|
err.contains("--tal-path and --ta-path counts must match"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_tal_path_without_ta_when_disable_rrdp_is_set() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--disable-rrdp".to_string(),
|
|
"--rsync-command".to_string(),
|
|
"/tmp/fake-rsync".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert_eq!(args.tal_path.as_deref(), Some(Path::new("a.tal")));
|
|
assert!(args.ta_path.is_none());
|
|
assert!(args.disable_rrdp);
|
|
assert_eq!(
|
|
args.rsync_command.as_deref(),
|
|
Some(Path::new("/tmp/fake-rsync"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_multiple_tal_paths_without_ta_when_disable_rrdp() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--tal-path".to_string(),
|
|
"b.tal".to_string(),
|
|
"--disable-rrdp".to_string(),
|
|
"--rsync-command".to_string(),
|
|
"/tmp/fake-rsync".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert_eq!(
|
|
args.tal_paths,
|
|
vec![PathBuf::from("a.tal"), PathBuf::from("b.tal")]
|
|
);
|
|
assert!(args.ta_paths.is_empty());
|
|
assert_eq!(args.tal_inputs.len(), 2);
|
|
assert!(args.disable_rrdp);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_payload_delta_replay_mode_with_offline_tal_and_ta() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--payload-base-archive".to_string(),
|
|
"base-archive".to_string(),
|
|
"--payload-base-locks".to_string(),
|
|
"base-locks.json".to_string(),
|
|
"--payload-delta-archive".to_string(),
|
|
"delta-archive".to_string(),
|
|
"--payload-delta-locks".to_string(),
|
|
"delta-locks.json".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse delta replay mode");
|
|
assert_eq!(
|
|
args.payload_base_archive.as_deref(),
|
|
Some(Path::new("base-archive"))
|
|
);
|
|
assert_eq!(
|
|
args.payload_base_locks.as_deref(),
|
|
Some(Path::new("base-locks.json"))
|
|
);
|
|
assert_eq!(
|
|
args.payload_delta_archive.as_deref(),
|
|
Some(Path::new("delta-archive"))
|
|
);
|
|
assert_eq!(
|
|
args.payload_delta_locks.as_deref(),
|
|
Some(Path::new("delta-locks.json"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_partial_payload_delta_arguments_and_mutual_exclusion() {
|
|
let argv_partial = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--payload-base-archive".to_string(),
|
|
"base-archive".to_string(),
|
|
];
|
|
let err = parse_args(&argv_partial).unwrap_err();
|
|
assert!(err.contains("must be provided together"), "{err}");
|
|
|
|
let argv_both = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--payload-replay-archive".to_string(),
|
|
"archive".to_string(),
|
|
"--payload-replay-locks".to_string(),
|
|
"locks.json".to_string(),
|
|
"--payload-base-archive".to_string(),
|
|
"base-archive".to_string(),
|
|
"--payload-base-locks".to_string(),
|
|
"base-locks.json".to_string(),
|
|
"--payload-delta-archive".to_string(),
|
|
"delta-archive".to_string(),
|
|
"--payload-delta-locks".to_string(),
|
|
"delta-locks.json".to_string(),
|
|
];
|
|
let err = parse_args(&argv_both).unwrap_err();
|
|
assert!(err.contains("mutually exclusive"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_payload_delta_with_tal_url_or_rsync_local_dir() {
|
|
let argv_url = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--payload-base-archive".to_string(),
|
|
"base-archive".to_string(),
|
|
"--payload-base-locks".to_string(),
|
|
"base-locks.json".to_string(),
|
|
"--payload-delta-archive".to_string(),
|
|
"delta-archive".to_string(),
|
|
"--payload-delta-locks".to_string(),
|
|
"delta-locks.json".to_string(),
|
|
];
|
|
let err = parse_args(&argv_url).unwrap_err();
|
|
assert!(err.contains("--tal-url is not supported"), "{err}");
|
|
|
|
let argv_rsync = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--payload-base-archive".to_string(),
|
|
"base-archive".to_string(),
|
|
"--payload-base-locks".to_string(),
|
|
"base-locks.json".to_string(),
|
|
"--payload-delta-archive".to_string(),
|
|
"delta-archive".to_string(),
|
|
"--payload-delta-locks".to_string(),
|
|
"delta-locks.json".to_string(),
|
|
"--rsync-local-dir".to_string(),
|
|
"repo".to_string(),
|
|
];
|
|
let err = parse_args(&argv_rsync).unwrap_err();
|
|
assert!(
|
|
err.contains("payload delta replay mode cannot be combined with --rsync-local-dir"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_payload_replay_mode_with_offline_tal_and_ta() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--payload-replay-archive".to_string(),
|
|
"archive".to_string(),
|
|
"--payload-replay-locks".to_string(),
|
|
"locks.json".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse replay mode");
|
|
assert_eq!(
|
|
args.payload_replay_archive.as_deref(),
|
|
Some(Path::new("archive"))
|
|
);
|
|
assert_eq!(
|
|
args.payload_replay_locks.as_deref(),
|
|
Some(Path::new("locks.json"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_partial_payload_replay_arguments() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--payload-replay-archive".to_string(),
|
|
"archive".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("must be provided together"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_payload_replay_with_tal_url_or_rsync_local_dir() {
|
|
let argv_url = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--payload-replay-archive".to_string(),
|
|
"archive".to_string(),
|
|
"--payload-replay-locks".to_string(),
|
|
"locks.json".to_string(),
|
|
];
|
|
let err = parse_args(&argv_url).unwrap_err();
|
|
assert!(err.contains("--tal-url is not supported"), "{err}");
|
|
|
|
let argv_rsync = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-path".to_string(),
|
|
"a.tal".to_string(),
|
|
"--ta-path".to_string(),
|
|
"ta.cer".to_string(),
|
|
"--payload-replay-archive".to_string(),
|
|
"archive".to_string(),
|
|
"--payload-replay-locks".to_string(),
|
|
"locks.json".to_string(),
|
|
"--rsync-local-dir".to_string(),
|
|
"repo".to_string(),
|
|
];
|
|
let err = parse_args(&argv_rsync).unwrap_err();
|
|
assert!(
|
|
err.contains("cannot be combined with --rsync-local-dir"),
|
|
"{err}"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parse_accepts_validation_time_rfc3339() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--validation-time".to_string(),
|
|
"2026-01-01T00:00:00Z".to_string(),
|
|
];
|
|
let args = parse_args(&argv).expect("parse");
|
|
assert!(args.validation_time.is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn parse_rejects_removed_revalidate_only_flag() {
|
|
let argv = vec![
|
|
"rpki".to_string(),
|
|
"--db".to_string(),
|
|
"db".to_string(),
|
|
"--tal-url".to_string(),
|
|
"https://example.test/x.tal".to_string(),
|
|
"--revalidate-only".to_string(),
|
|
];
|
|
let err = parse_args(&argv).unwrap_err();
|
|
assert!(err.contains("unknown argument: --revalidate-only"), "{err}");
|
|
}
|
|
|
|
#[test]
|
|
fn read_policy_accepts_valid_toml() {
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let p = dir.path().join("policy.toml");
|
|
std::fs::write(
|
|
&p,
|
|
"signed_object_failure_policy = \"drop_publication_point\"\n",
|
|
)
|
|
.expect("write policy");
|
|
|
|
let policy = read_policy(Some(&p)).expect("parse policy");
|
|
assert_eq!(
|
|
policy.signed_object_failure_policy,
|
|
crate::policy::SignedObjectFailurePolicy::DropPublicationPoint
|
|
);
|
|
assert_eq!(policy.strict, StrictPolicy::default());
|
|
}
|
|
|
|
#[test]
|
|
fn read_policy_accepts_strict_table() {
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let p = dir.path().join("policy.toml");
|
|
std::fs::write(
|
|
&p,
|
|
r#"
|
|
[strict]
|
|
name = true
|
|
cms_der = true
|
|
"#,
|
|
)
|
|
.expect("write policy");
|
|
|
|
let policy = read_policy(Some(&p)).expect("parse policy");
|
|
assert_eq!(
|
|
policy.strict,
|
|
StrictPolicy {
|
|
name: true,
|
|
cms_der: true,
|
|
signed_attrs: false,
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn read_policy_reports_missing_file() {
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let p = dir.path().join("missing.toml");
|
|
let err = read_policy(Some(&p)).unwrap_err();
|
|
assert!(err.contains("read policy file failed"), "{err}");
|
|
}
|
|
|
|
fn synthetic_post_validation_shared() -> PostValidationShared {
|
|
let tal_bytes = std::fs::read(
|
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests/fixtures/tal/apnic-rfc7730-https.tal"),
|
|
)
|
|
.expect("read tal fixture");
|
|
let ta_der = std::fs::read(
|
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer"),
|
|
)
|
|
.expect("read ta fixture");
|
|
|
|
let discovery = crate::validation::from_tal::discover_root_ca_instance_from_tal_and_ta_der(
|
|
&tal_bytes, &ta_der, None,
|
|
)
|
|
.expect("discover root");
|
|
|
|
let tree = crate::validation::tree::TreeRunOutput {
|
|
instances_processed: 1,
|
|
instances_failed: 0,
|
|
warnings: vec![
|
|
crate::report::Warning::new("synthetic warning")
|
|
.with_rfc_refs(&[crate::report::RfcRef("RFC 6487 §4.8.8.1")])
|
|
.with_context("rsync://example.test/repo/pp/"),
|
|
],
|
|
vrps: vec![
|
|
crate::validation::objects::Vrp {
|
|
asn: 64496,
|
|
prefix: crate::data_model::roa::IpPrefix {
|
|
afi: crate::data_model::roa::RoaAfi::Ipv4,
|
|
prefix_len: 24,
|
|
addr: [192, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
},
|
|
max_length: 24,
|
|
},
|
|
crate::validation::objects::Vrp {
|
|
asn: 64497,
|
|
prefix: crate::data_model::roa::IpPrefix {
|
|
afi: crate::data_model::roa::RoaAfi::Ipv6,
|
|
prefix_len: 48,
|
|
addr: [0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
},
|
|
max_length: 64,
|
|
},
|
|
],
|
|
aspas: vec![crate::validation::objects::AspaAttestation {
|
|
customer_as_id: 64496,
|
|
provider_as_ids: vec![64497, 64498],
|
|
}],
|
|
router_keys: Vec::new(),
|
|
};
|
|
|
|
let mut pp1 = crate::audit::PublicationPointAudit::default();
|
|
pp1.source = "fresh".to_string();
|
|
pp1.rrdp_notification_uri = Some("https://example.test/n1.xml".to_string());
|
|
pp1.manifest_rsync_uri = "rsync://example.test/repo/pp1/manifest.mft".to_string();
|
|
pp1.objects.push(crate::audit::ObjectAuditEntry {
|
|
rsync_uri: "rsync://example.test/repo/pp1/a.roa".to_string(),
|
|
sha256_hex: "11".repeat(32),
|
|
kind: crate::audit::AuditObjectKind::Roa,
|
|
result: crate::audit::AuditObjectResult::Ok,
|
|
detail: None,
|
|
});
|
|
let mut pp2 = crate::audit::PublicationPointAudit::default();
|
|
pp2.source = "fresh".to_string();
|
|
pp2.rrdp_notification_uri = Some("https://example.test/n1.xml".to_string());
|
|
let mut pp3 = crate::audit::PublicationPointAudit::default();
|
|
pp3.source = "fresh".to_string();
|
|
pp3.rrdp_notification_uri = Some("https://example.test/n2.xml".to_string());
|
|
|
|
let out = crate::validation::run_tree_from_tal::RunTreeFromTalAuditOutput {
|
|
discovery: discovery.clone(),
|
|
discoveries: vec![discovery],
|
|
successful_tal_inputs: Vec::new(),
|
|
tree,
|
|
publication_points: vec![pp1, pp2, pp3],
|
|
roa_cache_stats: crate::validation::objects::RoaValidationCacheStats::default(),
|
|
downloads: Vec::new(),
|
|
download_stats: crate::audit::AuditDownloadStats::default(),
|
|
current_repo_objects: Vec::new(),
|
|
ccr_accumulator: None,
|
|
cir_input: crate::cir::CirInputSnapshot::default(),
|
|
};
|
|
PostValidationShared::from_run_output(out)
|
|
}
|
|
|
|
fn sample_cli_ccr_accumulator() -> CcrAccumulator {
|
|
let tal_bytes = std::fs::read(
|
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.join("tests/fixtures/tal/apnic-rfc7730-https.tal"),
|
|
)
|
|
.expect("read tal fixture");
|
|
let ta_der = std::fs::read(
|
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer"),
|
|
)
|
|
.expect("read ta fixture");
|
|
let discovery = crate::validation::from_tal::discover_root_ca_instance_from_tal_and_ta_der(
|
|
&tal_bytes, &ta_der, None,
|
|
)
|
|
.expect("discover root");
|
|
let mut accumulator = CcrAccumulator::new(vec![discovery.trust_anchor.clone()]);
|
|
let projection = crate::storage::VcirCcrManifestProjection {
|
|
manifest_rsync_uri: "rsync://example.test/repo/current.mft".to_string(),
|
|
manifest_sha256: vec![0x44; 32],
|
|
manifest_size: 2048,
|
|
manifest_ee_aki: vec![0x55; 20],
|
|
manifest_number_be: vec![1],
|
|
manifest_this_update: crate::storage::PackTime::from_utc_offset_datetime(
|
|
time::OffsetDateTime::now_utc(),
|
|
),
|
|
manifest_sia_locations_der: vec![vec![
|
|
0x30, 0x11, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x05, 0x86, 0x05,
|
|
b'r', b's', b'y', b'n', b'c',
|
|
]],
|
|
subordinate_skis: vec![vec![0x33; 20]],
|
|
};
|
|
accumulator
|
|
.append_manifest_projection(&projection)
|
|
.expect("append manifest projection");
|
|
accumulator
|
|
}
|
|
|
|
#[test]
|
|
fn build_report_and_helpers_work_on_synthetic_output() {
|
|
let shared = synthetic_post_validation_shared();
|
|
let policy = Policy::default();
|
|
let validation_time = time::OffsetDateTime::now_utc();
|
|
let report = build_report(&policy, validation_time, &shared);
|
|
|
|
assert_eq!(unique_rrdp_repos(&report), 2);
|
|
assert_eq!(report.vrps.len(), 2);
|
|
assert_eq!(report.aspas.len(), 1);
|
|
|
|
print_summary(&report);
|
|
}
|
|
|
|
#[test]
|
|
fn run_report_task_and_stage_timing_work() {
|
|
let shared = synthetic_post_validation_shared();
|
|
let policy = Policy::default();
|
|
let validation_time = time::OffsetDateTime::now_utc();
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let report_path = dir.path().join("report.json");
|
|
let report_output = run_report_task(
|
|
&policy,
|
|
validation_time,
|
|
&shared,
|
|
Some(&report_path),
|
|
ReportJsonFormat::Compact,
|
|
)
|
|
.expect("run report task");
|
|
|
|
assert!(report_output.report_write_ms.is_some());
|
|
|
|
let report_json = std::fs::read_to_string(&report_path).expect("read report json");
|
|
assert!(!report_json.contains('\n'), "{report_json}");
|
|
let report: serde_json::Value =
|
|
serde_json::from_str(&report_json).expect("parse compact report json");
|
|
assert_eq!(report["vrps"].as_array().unwrap().len(), 2);
|
|
assert_eq!(report["aspas"].as_array().unwrap().len(), 1);
|
|
assert_eq!(report["queryAudit"]["status"].as_str(), Some("complete"));
|
|
assert!(report["queryAudit"]["eventsCount"].as_u64().unwrap() > 0);
|
|
let events_path = dir.path().join(
|
|
report["queryAudit"]["eventsPath"]
|
|
.as_str()
|
|
.expect("events path"),
|
|
);
|
|
let events = std::fs::read_to_string(events_path).expect("read validation events");
|
|
assert!(
|
|
events
|
|
.lines()
|
|
.any(|line| line.contains("\"eventType\":\"object\""))
|
|
);
|
|
|
|
let stage_timing = RunStageTiming {
|
|
validation_ms: 1,
|
|
enable_roa_validation_cache: false,
|
|
enable_transport_request_prefetch: false,
|
|
report_build_ms: report_output.report_build_ms,
|
|
report_write_ms: report_output.report_write_ms,
|
|
ccr_build_ms: Some(2),
|
|
ccr_build_breakdown: None,
|
|
ccr_write_ms: Some(3),
|
|
compare_view_build_ms: Some(4),
|
|
compare_view_write_ms: Some(5),
|
|
cir_build_cir_ms: Some(6),
|
|
cir_write_cir_ms: Some(7),
|
|
cir_total_ms: Some(8),
|
|
total_ms: 9,
|
|
publication_points: shared.publication_points.len(),
|
|
repo_sync_ms_total: 10,
|
|
publication_point_repo_sync_ms_total: 11,
|
|
download_event_count: 12,
|
|
rrdp_download_ms_total: 13,
|
|
rsync_download_ms_total: 14,
|
|
download_bytes_total: 15,
|
|
roa_validation_cache: crate::validation::objects::RoaValidationCacheStats::default(),
|
|
analysis_counts: std::collections::HashMap::new(),
|
|
vcir_storage_summary_ms: Some(16),
|
|
vcir_storage: Some(VcirStorageSummary {
|
|
entry_count: 2,
|
|
vcir_value_bytes: 100,
|
|
vcir_value_bytes_max: 60,
|
|
vcir_value_bytes_max_manifest_rsync_uri: Some(
|
|
"rsync://example.test/repo/max.mft".to_string(),
|
|
),
|
|
core_fields: VcirCoreFieldSizeBreakdown {
|
|
manifest_rsync_uri_bytes: 10,
|
|
..VcirCoreFieldSizeBreakdown::default()
|
|
},
|
|
ccr_projection: VcirCcrProjectionSizeBreakdown {
|
|
manifest_sha256_bytes: 32,
|
|
..VcirCcrProjectionSizeBreakdown::default()
|
|
},
|
|
child_resources: VcirChildResourceSizeBreakdown {
|
|
effective_ip_resource_cbor_bytes: 12,
|
|
effective_as_resource_cbor_bytes: 6,
|
|
},
|
|
field_sizes: VcirFieldSizeBreakdown {
|
|
local_output_count: 1,
|
|
local_output_payload_json_bytes: 70,
|
|
local_output_payload_typed_body_bytes: 20,
|
|
..VcirFieldSizeBreakdown::default()
|
|
},
|
|
local_output_old_projection_bytes: 80,
|
|
local_output_typed_projection_bytes: 30,
|
|
local_output_projection_saved_bytes: 50,
|
|
top_entries_by_vcir_value_bytes: vec![VcirStorageEntrySummary {
|
|
manifest_rsync_uri: "rsync://example.test/repo/max.mft".to_string(),
|
|
vcir_value_bytes: 60,
|
|
local_vrp_count: 1,
|
|
local_aspa_count: 0,
|
|
local_router_key_count: 0,
|
|
accepted_object_count: 1,
|
|
rejected_object_count: 0,
|
|
child_count: 0,
|
|
core_fields: VcirCoreFieldSizeBreakdown::default(),
|
|
ccr_projection: VcirCcrProjectionSizeBreakdown::default(),
|
|
child_resources: VcirChildResourceSizeBreakdown::default(),
|
|
field_sizes: VcirFieldSizeBreakdown::default(),
|
|
local_output_old_projection_bytes: 1,
|
|
local_output_typed_projection_bytes: 1,
|
|
local_output_projection_saved_bytes: 0,
|
|
}],
|
|
}),
|
|
memory_telemetry: None,
|
|
};
|
|
write_stage_timing(Some(&report_path), &stage_timing).expect("write stage timing");
|
|
let stage_timing_json =
|
|
std::fs::read_to_string(dir.path().join("stage-timing.json")).expect("read timing");
|
|
assert!(stage_timing_json.contains("\"validation_ms\""));
|
|
assert!(stage_timing_json.contains("\"ccr_build_ms\""));
|
|
assert!(stage_timing_json.contains("\"vcir_storage\""));
|
|
assert!(stage_timing_json.contains("\"local_output_projection_saved_bytes\""));
|
|
|
|
let ccr_path = dir.path().join("result.ccr");
|
|
write_stage_timing(Some(&ccr_path), &stage_timing).expect("write stage timing via ccr path");
|
|
assert!(
|
|
dir.path().join("stage-timing.json").exists(),
|
|
"stage timing should use parent directory of the anchor path"
|
|
);
|
|
|
|
let skipped = ReportTaskOutput::skipped();
|
|
assert_eq!(skipped.report_build_ms, 0);
|
|
assert!(skipped.report_write_ms.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn stage_timing_serializes_memory_telemetry() {
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let report_path = dir.path().join("report.json");
|
|
let stage_timing = RunStageTiming {
|
|
validation_ms: 1,
|
|
enable_roa_validation_cache: true,
|
|
enable_transport_request_prefetch: true,
|
|
report_build_ms: 2,
|
|
report_write_ms: None,
|
|
ccr_build_ms: None,
|
|
ccr_build_breakdown: None,
|
|
ccr_write_ms: None,
|
|
compare_view_build_ms: None,
|
|
compare_view_write_ms: None,
|
|
cir_build_cir_ms: None,
|
|
cir_write_cir_ms: None,
|
|
cir_total_ms: None,
|
|
total_ms: 3,
|
|
publication_points: 4,
|
|
repo_sync_ms_total: 5,
|
|
publication_point_repo_sync_ms_total: 6,
|
|
download_event_count: 7,
|
|
rrdp_download_ms_total: 8,
|
|
rsync_download_ms_total: 9,
|
|
download_bytes_total: 10,
|
|
roa_validation_cache: crate::validation::objects::RoaValidationCacheStats {
|
|
hit_roas: 2,
|
|
..crate::validation::objects::RoaValidationCacheStats::default()
|
|
},
|
|
analysis_counts: std::collections::HashMap::from([(
|
|
"roa_validation_cache_hit_roas".to_string(),
|
|
2,
|
|
)]),
|
|
vcir_storage_summary_ms: None,
|
|
vcir_storage: None,
|
|
memory_telemetry: Some(MemoryTelemetrySummary {
|
|
checkpoints: vec![MemoryTelemetryCheckpoint {
|
|
label: "after_validation".to_string(),
|
|
elapsed_ms: 11,
|
|
process: ProcessMemorySnapshot {
|
|
label: "after_validation".to_string(),
|
|
vm_rss_kb: Some(12),
|
|
vm_size_kb: None,
|
|
vm_data_kb: None,
|
|
vm_swap_kb: None,
|
|
rss_anon_kb: Some(13),
|
|
rss_file_kb: None,
|
|
rss_shmem_kb: None,
|
|
threads: Some(14),
|
|
fd_count: Some(15),
|
|
smaps_rollup: None,
|
|
smaps_mapping_summary: None,
|
|
errors: Vec::new(),
|
|
},
|
|
rocksdb: RocksDbMemorySnapshot {
|
|
databases: Vec::new(),
|
|
totals: RocksDbMemoryTotals {
|
|
cur_size_all_mem_tables: 16,
|
|
size_all_mem_tables: 17,
|
|
estimate_table_readers_mem: 18,
|
|
block_cache_capacity: 19,
|
|
block_cache_usage: 20,
|
|
block_cache_pinned_usage: 21,
|
|
},
|
|
},
|
|
}],
|
|
object_graph: None,
|
|
malloc_trim_probes: Vec::new(),
|
|
}),
|
|
};
|
|
|
|
write_stage_timing(Some(&report_path), &stage_timing).expect("write stage timing");
|
|
let value: serde_json::Value = serde_json::from_str(
|
|
&std::fs::read_to_string(dir.path().join("stage-timing.json")).unwrap(),
|
|
)
|
|
.expect("parse stage timing json");
|
|
let checkpoint = &value["memory_telemetry"]["checkpoints"][0];
|
|
assert_eq!(checkpoint["label"], "after_validation");
|
|
assert_eq!(checkpoint["process"]["vm_rss_kb"], 12);
|
|
assert_eq!(
|
|
checkpoint["rocksdb"]["totals"]["cur_size_all_mem_tables"],
|
|
16
|
|
);
|
|
assert_eq!(value["analysis_counts"]["roa_validation_cache_hit_roas"], 2);
|
|
assert_eq!(value["roa_validation_cache"]["hit_roas"], 2);
|
|
assert!(
|
|
value["memory_telemetry"]
|
|
.as_object()
|
|
.expect("memory telemetry object")
|
|
.get("malloc_trim_probes")
|
|
.is_none()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn shared_object_graph_estimate_counts_audit_and_outputs() {
|
|
let mut shared = synthetic_post_validation_shared();
|
|
let mut publication_points = shared
|
|
.publication_points
|
|
.iter()
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
publication_points[0].rsync_base_uri = "rsync://example.test/repo/".to_string();
|
|
publication_points[0].manifest_rsync_uri = "rsync://example.test/repo/a.mft".to_string();
|
|
publication_points[0].publication_point_rsync_uri = "rsync://example.test/repo/".to_string();
|
|
publication_points[0].objects = vec![crate::audit::ObjectAuditEntry {
|
|
rsync_uri: "rsync://example.test/repo/a.roa".to_string(),
|
|
sha256_hex: "11".repeat(32),
|
|
kind: crate::audit::AuditObjectKind::Roa,
|
|
result: crate::audit::AuditObjectResult::Ok,
|
|
detail: None,
|
|
}];
|
|
shared.publication_points = publication_points.into();
|
|
|
|
let graph = estimate_shared_object_graph(&shared);
|
|
let publication_points_section = graph
|
|
.sections
|
|
.iter()
|
|
.find(|section| section.name == "publication_points")
|
|
.expect("publication points section");
|
|
let object_count = publication_points_section
|
|
.details
|
|
.iter()
|
|
.find(|metric| metric.name == "object_audit_entry_count")
|
|
.expect("object count metric");
|
|
assert_eq!(object_count.value, 1);
|
|
assert!(publication_points_section.estimated_bytes > 0);
|
|
|
|
let vrps_section = graph
|
|
.sections
|
|
.iter()
|
|
.find(|section| section.name == "vrps")
|
|
.expect("vrps section");
|
|
assert_eq!(vrps_section.item_count, 2);
|
|
assert!(graph.total_estimated_bytes >= publication_points_section.estimated_bytes);
|
|
}
|
|
|
|
#[test]
|
|
fn run_compare_view_task_writes_csv_from_shared_output() {
|
|
let shared = synthetic_post_validation_shared();
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let vrps_path = dir.path().join("vrps.csv");
|
|
let vaps_path = dir.path().join("vaps.csv");
|
|
|
|
let output = run_compare_view_task(&shared, Some(&vrps_path), Some(&vaps_path), "unknown")
|
|
.expect("write direct compare views");
|
|
|
|
assert!(output.build_ms.is_some());
|
|
assert!(output.write_ms.is_some());
|
|
let vrps_csv = std::fs::read_to_string(vrps_path).expect("read vrps csv");
|
|
let vaps_csv = std::fs::read_to_string(vaps_path).expect("read vaps csv");
|
|
assert!(vrps_csv.contains("ASN,IP Prefix,Max Length,Trust Anchor"));
|
|
assert!(vrps_csv.contains("AS64496,192.0.2.0/24,24,unknown"));
|
|
assert!(vrps_csv.contains("AS64497,2001:db8::/48,64,unknown"));
|
|
assert!(vaps_csv.contains("Customer ASN,Providers,Trust Anchor"));
|
|
assert!(vaps_csv.contains("AS64496,AS64497;AS64498,unknown"));
|
|
}
|
|
|
|
#[test]
|
|
fn run_ccr_task_uses_accumulator_when_phase2_output_contains_reuse_sources() {
|
|
let mut shared = synthetic_post_validation_shared();
|
|
shared.ccr_accumulator = Some(sample_cli_ccr_accumulator());
|
|
let mut publication_points = shared
|
|
.publication_points
|
|
.iter()
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
publication_points[1].source = "vcir_current_instance".to_string();
|
|
publication_points[2].source = "failed_no_cache".to_string();
|
|
shared.publication_points = publication_points.into();
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let ccr_path = dir.path().join("result.ccr");
|
|
let store = RocksStore::open(&dir.path().join("db")).expect("open empty store");
|
|
|
|
let output = run_ccr_task(
|
|
&store,
|
|
&shared,
|
|
Some(&ccr_path),
|
|
time::OffsetDateTime::now_utc(),
|
|
)
|
|
.expect("run ccr task");
|
|
|
|
assert!(output.ccr_build_ms.is_some());
|
|
assert!(output.ccr_build_breakdown.is_none());
|
|
let der = std::fs::read(&ccr_path).expect("read ccr");
|
|
let ci = crate::ccr::decode_content_info(&der).expect("decode ccr");
|
|
assert_eq!(
|
|
ci.content
|
|
.mfts
|
|
.as_ref()
|
|
.map(|manifest_state| manifest_state.mis.len()),
|
|
Some(1)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn write_json_writes_report() {
|
|
let report = AuditReportV2 {
|
|
format_version: 2,
|
|
meta: AuditRunMeta {
|
|
validation_time_rfc3339_utc: "2026-01-01T00:00:00Z".to_string(),
|
|
},
|
|
policy: Policy::default(),
|
|
tree: TreeSummary {
|
|
instances_processed: 0,
|
|
instances_failed: 0,
|
|
warnings: Vec::new(),
|
|
},
|
|
publication_points: Vec::new(),
|
|
vrps: Vec::new(),
|
|
aspas: Vec::new(),
|
|
downloads: Vec::new(),
|
|
download_stats: crate::audit::AuditDownloadStats::default(),
|
|
repo_sync_stats: crate::audit::AuditRepoSyncStats::default(),
|
|
query_audit: None,
|
|
};
|
|
|
|
let dir = tempfile::tempdir().expect("tmpdir");
|
|
let pretty_path = dir.path().join("report-pretty.json");
|
|
write_json(&pretty_path, &report, ReportJsonFormat::Pretty).expect("write pretty json");
|
|
let pretty = std::fs::read_to_string(&pretty_path).expect("read pretty report");
|
|
assert!(pretty.contains("\"format_version\""));
|
|
assert!(pretty.contains("\"policy\""));
|
|
assert!(pretty.contains("\n \"format_version\""), "{pretty}");
|
|
|
|
let compact_path = dir.path().join("report-compact.json");
|
|
write_json(&compact_path, &report, ReportJsonFormat::Compact).expect("write compact json");
|
|
let compact = std::fs::read_to_string(&compact_path).expect("read compact report");
|
|
assert!(compact.contains("\"format_version\""));
|
|
assert!(compact.contains("\"policy\""));
|
|
assert!(!compact.contains('\n'), "{compact}");
|
|
}
|
|
|
|
#[test]
|
|
fn build_repo_sync_stats_aggregates_phase_and_terminal_state() {
|
|
let mut pp1 = crate::audit::PublicationPointAudit::default();
|
|
pp1.repo_sync_phase = Some("rrdp_ok".to_string());
|
|
pp1.repo_sync_duration_ms = Some(10);
|
|
pp1.repo_terminal_state = "fresh".to_string();
|
|
|
|
let mut pp2 = crate::audit::PublicationPointAudit::default();
|
|
pp2.repo_sync_phase = Some("rrdp_failed_rsync_failed".to_string());
|
|
pp2.repo_sync_duration_ms = Some(20);
|
|
pp2.repo_terminal_state = "failed_no_cache".to_string();
|
|
|
|
let mut pp3 = crate::audit::PublicationPointAudit::default();
|
|
pp3.repo_sync_phase = Some("rrdp_failed_rsync_failed".to_string());
|
|
pp3.repo_sync_duration_ms = Some(30);
|
|
pp3.repo_terminal_state = "failed_no_cache".to_string();
|
|
|
|
let stats = build_repo_sync_stats(&[pp1, pp2, pp3]);
|
|
assert_eq!(stats.publication_points_total, 3);
|
|
assert_eq!(stats.by_phase["rrdp_ok"].count, 1);
|
|
assert_eq!(stats.by_phase["rrdp_ok"].duration_ms_total, 10);
|
|
assert_eq!(stats.by_phase["rrdp_failed_rsync_failed"].count, 2);
|
|
assert_eq!(
|
|
stats.by_phase["rrdp_failed_rsync_failed"].duration_ms_total,
|
|
50
|
|
);
|
|
assert_eq!(stats.by_terminal_state["fresh"].count, 1);
|
|
assert_eq!(stats.by_terminal_state["failed_no_cache"].count, 2);
|
|
assert_eq!(
|
|
stats.by_terminal_state["failed_no_cache"].duration_ms_total,
|
|
50
|
|
);
|
|
}
|