diff --git a/crates/flowproof-agent/src/recorder.rs b/crates/flowproof-agent/src/recorder.rs index 434ca12..d97757e 100644 --- a/crates/flowproof-agent/src/recorder.rs +++ b/crates/flowproof-agent/src/recorder.rs @@ -1801,18 +1801,11 @@ fn mint_baseline( /// /// `downloads_dir` is the one field here that resolves `${VAR}` (a runner's /// temp/download path is exactly the kind of thing that differs per -/// environment) — every sibling field is a literal by design (geometry, -/// a pinned clock/seed, raw Chrome flags), so this is the one seam that -/// needs it. +/// environment) — see `secret::resolve_downloads_dir`. fn web_browser_from_setup( setup: &flowproof_trace::format::BrowserSetup, ) -> Result { - let downloads_dir = setup - .downloads_dir - .as_deref() - .map(flowproof_trace::secret::resolve_refs) - .transpose()? - .map(std::path::PathBuf::from); + let downloads_dir = flowproof_trace::secret::resolve_downloads_dir(&setup.downloads_dir)?; Ok(flowproof_driver::WebBrowserConfig::from_setup_parts( setup .viewport diff --git a/crates/flowproof-cli/src/lib.rs b/crates/flowproof-cli/src/lib.rs index d721d4e..2f3122b 100644 --- a/crates/flowproof-cli/src/lib.rs +++ b/crates/flowproof-cli/src/lib.rs @@ -705,16 +705,9 @@ fn stage_surface_browser( return Ok(()); }; // `downloads_dir` is the one field here resolved from `${VAR}` — see - // `web_browser_from_setup` in `flowproof-agent/src/recorder.rs`, the - // record-path sibling of this replay-path function; every other field - // is a literal by design. - let downloads_dir = browser - .downloads_dir - .as_deref() - .map(flowproof_trace::secret::resolve_refs) - .transpose() - .map_err(|e| flowproof_driver::DriverError::Uia(e.to_string()))? - .map(std::path::PathBuf::from); + // `secret::resolve_downloads_dir`. + let downloads_dir = flowproof_trace::secret::resolve_downloads_dir(&browser.downloads_dir) + .map_err(|e| flowproof_driver::DriverError::Uia(e.to_string()))?; driver.stage_browser(flowproof_driver::WebBrowserConfig::from_setup_parts( browser .viewport diff --git a/crates/flowproof-replay/src/lib.rs b/crates/flowproof-replay/src/lib.rs index bddaa1b..c486ac8 100644 --- a/crates/flowproof-replay/src/lib.rs +++ b/crates/flowproof-replay/src/lib.rs @@ -2457,15 +2457,10 @@ pub fn run_trace_with_exports( // emulated phone viewport must not replay on a desktop one. if let Some(browser) = &header.browser { if !browser.is_empty() { - // `downloads_dir` is the one field here resolved from `${VAR}`; - // every sibling is a literal by design (geometry, a pinned - // clock/seed, raw Chrome flags). - let downloads_dir = browser - .downloads_dir - .as_deref() - .map(flowproof_trace::secret::resolve_refs) - .transpose()? - .map(std::path::PathBuf::from); + // `downloads_dir` is the one field here resolved from `${VAR}` — + // see `secret::resolve_downloads_dir`. + let downloads_dir = + flowproof_trace::secret::resolve_downloads_dir(&browser.downloads_dir)?; driver.stage_browser(flowproof_driver::WebBrowserConfig::from_setup_parts( browser .viewport diff --git a/crates/flowproof-trace/src/secret.rs b/crates/flowproof-trace/src/secret.rs index 5512e3f..9325d3d 100644 --- a/crates/flowproof-trace/src/secret.rs +++ b/crates/flowproof-trace/src/secret.rs @@ -92,10 +92,55 @@ pub fn resolve_refs_in_json(value: &serde_json::Value) -> Result, +) -> Result, MissingSecret> { + raw.as_deref() + .map(resolve_refs) + .transpose() + .map(|opt| opt.map(std::path::PathBuf::from)) +} + #[cfg(test)] mod tests { use super::*; + #[test] + fn downloads_dir_none_resolves_to_none() { + assert_eq!(resolve_downloads_dir(&None).expect("resolves"), None); + } + + #[test] + fn downloads_dir_literal_path_resolves_unchanged() { + let raw = Some("/tmp/flowproof-downloads".to_string()); + assert_eq!( + resolve_downloads_dir(&raw).expect("resolves"), + Some(std::path::PathBuf::from("/tmp/flowproof-downloads")) + ); + } + + #[test] + fn downloads_dir_ref_resolves_from_the_environment() { + std::env::set_var("FLOWPROOF_TEST_DOWNLOADS_DIR", "/runner/downloads"); + let raw = Some("${FLOWPROOF_TEST_DOWNLOADS_DIR}".to_string()); + assert_eq!( + resolve_downloads_dir(&raw).expect("resolves"), + Some(std::path::PathBuf::from("/runner/downloads")) + ); + } + + #[test] + fn downloads_dir_missing_ref_is_a_hard_error() { + let raw = Some("${FLOWPROOF_TEST_DOWNLOADS_DIR_UNSET_XYZ}".to_string()); + let err = resolve_downloads_dir(&raw).expect_err("must fail"); + assert_eq!(err.var, "FLOWPROOF_TEST_DOWNLOADS_DIR_UNSET_XYZ"); + } + #[test] fn plain_text_passes_through_untouched() { assert!(!has_refs("hello from flowproof"));