//! Desktop D2: offline cache, tool download manifest and rules, job ids. use std::io::Write; use serde_json::json; use storebound_desktop::cache::{key, Cache}; use storebound_desktop::downloads::{self, chrome_download, download_allowed, inside, manifest, unpack}; use storebound_desktop::sidecar::valid_job; use url::Url; #[test] fn cache_keeps_the_last_read_per_project_method_and_args() { let c = Cache::in_memory().unwrap(); assert_eq!(c.get("p1", "health", &json!({})).unwrap(), None); c.put("p1", "health", &json!({}), &json!({ "ok": true, "n": 1 }), "2026-10-10T10:00:00Z").unwrap(); c.put("p1", "health", &json!({}), &json!({ "ok": true, "n": 2 }), "2026-10-10T11:00:00Z").unwrap(); c.put("p1", "reviews.list", &json!({ "days": 14, "store": "play" }), &json!([1]), "2026-10-10T09:00:00Z").unwrap(); c.put("p2", "health", &json!({}), &json!({ "n": 9 }), "2026-10-10T08:00:00Z").unwrap(); let e = c.get("p1", "health", &json!({})).unwrap().unwrap(); assert_eq!((e.value["n"].clone(), e.at.as_str()), (json!(2), "2026-10-10T11:00:00Z")); // argument order does not matter (canonical JSON), values do assert!(c.get("p1", "reviews.list", &json!({ "store": "play", "days": 14 })).unwrap().is_some()); assert!(c.get("p1", "reviews.list", &json!({ "store": "apple", "days": 14 })).unwrap().is_none()); assert_eq!(key("a", "m", &json!({ "b": 1, "a": 2 })), key("a", "m", &json!({ "a": 2, "b": 1 }))); assert_eq!(c.stamps("p1").unwrap().len(), 2); c.forget_project("p1").unwrap(); assert!(c.get("p1", "health", &json!({})).unwrap().is_none()); assert!(c.get("p2", "health", &json!({})).unwrap().is_some()); c.clear().unwrap(); assert!(c.get("p2", "health", &json!({})).unwrap().is_none()); } #[test] fn cache_survives_a_reopen() { let dir = tempfile::tempdir().unwrap(); Cache::open(dir.path()).unwrap().put("p", "m", &json!({}), &json!({ "x": 1 }), "t").unwrap(); assert_eq!(Cache::open(dir.path()).unwrap().get("p", "m", &json!({})).unwrap().unwrap().value, json!({ "x": 1 })); } #[test] fn manifest_pins_every_desktop_target_with_hashes() { let m = manifest().unwrap(); for t in ["aarch64-apple-darwin", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "aarch64-unknown-linux-gnu", "x86_64-pc-windows-msvc"] { let list = m.ffmpeg.targets.get(t).unwrap_or_else(|| panic!("no ffmpeg for {t}")); let mut outs = vec![]; for a in list { let u = Url::parse(&a.url).unwrap(); assert!(download_allowed(&u), "{}", a.url); assert!(!a.url.contains("/latest/"), "pinned URLs only: {}", a.url); assert_eq!(a.sha256.len(), 64); assert!(a.sha256.chars().all(|c| c.is_ascii_hexdigit())); assert!(a.size > 1_000_000); assert!(a.kind == "zip" || a.kind == "tar.xz"); for f in &a.files { inside(std::path::Path::new("/x"), &f.from).unwrap(); outs.push(f.to.clone()); } } outs.sort(); let exe = if t.contains("windows") { ".exe" } else { "" }; assert_eq!(outs, vec![format!("ffmpeg{exe}"), format!("ffprobe{exe}")], "{t}"); } assert!(download_allowed(&Url::parse(&m.chrome.endpoint).unwrap())); assert_eq!(m.chrome.mac_team_id, "EQHXZ8M8AV"); assert!(m.ffmpeg.license.contains("GPL")); } #[test] fn downloads_only_over_https_from_the_listed_hosts() { for ok in ["https://ffmpeg.martin-riedl.de/download/x.zip", "https://github.com/BtbN/FFmpeg-Builds/releases/download/a/b.zip", "https://storage.googleapis.com/chrome-for-testing-public/1/mac-arm64/chrome-mac-arm64.zip"] { assert!(download_allowed(&Url::parse(ok).unwrap()), "{ok}"); } for bad in ["http://ffmpeg.martin-riedl.de/x.zip", "https://evil.test/x.zip", "https://github.com.evil.test/x", "https://storage.googleapis.com:8443/x", "file:///tmp/x.zip"] { assert!(!download_allowed(&Url::parse(bad).unwrap()), "{bad}"); } assert!(!download_allowed(&Url::parse(&format!("https://{}@github.com/x", "u")).unwrap())); } #[test] fn chrome_for_testing_endpoint_answer() { let j = json!({ "channels": { "Stable": { "version": "141.0.7390.54", "downloads": { "chrome": [ { "platform": "linux64", "url": "https://storage.googleapis.com/chrome-for-testing-public/141.0.7390.54/linux64/chrome-linux64.zip" }, { "platform": "mac-arm64", "url": "https://storage.googleapis.com/chrome-for-testing-public/141.0.7390.54/mac-arm64/chrome-mac-arm64.zip" } ] } } } }); let (v, u) = chrome_download(&j, "Stable", "mac-arm64").unwrap(); assert_eq!(v, "141.0.7390.54"); assert!(u.as_str().ends_with("chrome-mac-arm64.zip")); assert!(chrome_download(&j, "Stable", "win64").is_err()); let evil = json!({ "channels": { "Stable": { "version": "1", "downloads": { "chrome": [{ "platform": "win64", "url": "https://evil.test/chrome.zip" }] } } } }); assert!(chrome_download(&evil, "Stable", "win64").is_err()); } #[test] fn archive_paths_stay_inside() { let base = std::path::Path::new("/data/tools/.staging"); assert!(inside(base, "ffmpeg-n9/bin/ffmpeg").is_ok()); for bad in ["../ffmpeg", "/usr/bin/ffmpeg", "a/../../b", "./x"] { assert!(inside(base, bad).is_err(), "{bad}"); } } #[test] fn zip_unpack_refuses_entries_outside_the_folder() { if cfg!(target_os = "macos") { return; // macOS unpacks with ditto, which never writes outside the target folder } let dir = tempfile::tempdir().unwrap(); let zip_path = dir.path().join("evil.zip"); { let f = std::fs::File::create(&zip_path).unwrap(); let mut z = zip::ZipWriter::new(f); z.start_file("../escape.txt", zip::write::SimpleFileOptions::default()).unwrap(); z.write_all(b"x").unwrap(); z.finish().unwrap(); } let out = dir.path().join("out"); let _ = unpack(&zip_path, "zip", &out); assert!(!dir.path().join("escape.txt").exists()); } #[test] fn download_info_lists_both_tools() { let dir = tempfile::tempdir().unwrap(); let info = downloads::info(dir.path()).unwrap(); assert_eq!(info.iter().map(|i| i.tool).collect::>(), vec!["ffmpeg", "chrome"]); assert!(info.iter().all(|i| !i.installed)); assert!(info[0].available && info[0].size.unwrap_or(0) > 0); } #[test] fn job_ids_are_short_and_plain() { for ok in ["portfolio-1", "dl:ffmpeg:3", "a.b_c"] { assert!(valid_job(ok), "{ok}"); } for bad in ["", "a b", "x\"y", "