137 lines
6.5 KiB
Rust
137 lines
6.5 KiB
Rust
//! 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<_>>(), 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", "<script>", &"x".repeat(65)] {
|
|
assert!(!valid_job(bad), "{bad}");
|
|
}
|
|
}
|