storebound/apps/desktop/src-tauri/tests/d5.rs

154 lines
7.8 KiB
Rust

//! D5 release pipeline: one version everywhere, the updater's key and endpoints, the `storebound://open?path=` deep
//! link and the self-test the release runs on the exact artifact.
use std::fs;
use std::path::PathBuf;
use base64::Engine;
use serde_json::Value;
use storebound_desktop::policy::deep_link_open_path;
use storebound_desktop::{selftest, updates};
use url::Url;
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn conf() -> Value {
serde_json::from_str(&fs::read_to_string(root().join("tauri.conf.json")).unwrap()).unwrap()
}
#[test]
fn one_version_with_the_cli() {
let pkg: Value = serde_json::from_str(&fs::read_to_string(root().join("../../../package.json")).unwrap()).unwrap();
// one product, one number: the desktop app has the npm CLI's version (its bundled sidecar is that CLI)
let want = pkg["version"].as_str().expect("package.json version");
assert_eq!(env!("CARGO_PKG_VERSION"), want, "Cargo.toml (bun tools/desktop-version.ts --set {want})");
assert_eq!(conf()["version"], want, "tauri.conf.json");
let app: Value = serde_json::from_str(&fs::read_to_string(root().join("../package.json")).unwrap()).unwrap();
assert_eq!(app["version"], want, "apps/desktop/package.json");
}
#[test]
fn updater_has_a_real_minisign_key_and_only_storebound_dev() {
let c = conf();
let key = c["plugins"]["updater"]["pubkey"].as_str().unwrap();
let text = String::from_utf8(base64::engine::general_purpose::STANDARD.decode(key).expect("base64 pubkey")).unwrap();
let mut lines = text.lines();
assert!(lines.next().unwrap().starts_with("untrusted comment: minisign public key"));
let raw = base64::engine::general_purpose::STANDARD.decode(lines.next().unwrap()).unwrap();
assert_eq!(raw.len(), 42, "Ed + key id + 32-byte key");
assert_eq!(&raw[..2], b"Ed");
assert_eq!(c["plugins"]["updater"]["endpoints"], serde_json::json!([updates::STABLE]));
// updater bundles are signed by tools/desktop-release.ts on the release Mac, never by a CI runner
assert_eq!(c["bundle"]["createUpdaterArtifacts"], false);
assert_eq!(c["plugins"]["updater"]["windows"]["installMode"], "quiet");
for ch in ["stable", "beta", "dev", "nightly", ""] {
assert!(updates::endpoint_allowed(&Url::parse(updates::endpoint(ch)).unwrap()), "{ch}");
}
assert_eq!(updates::endpoint("beta"), updates::BETA);
assert_eq!(updates::endpoint("dev"), updates::DEV);
assert_eq!(updates::DEV, "https://storebound.dev/desktop/dev/latest.json");
assert_eq!(updates::endpoint("anything"), updates::STABLE);
for bad in ["http://storebound.dev/desktop/latest.json", "https://storebound.dev:8443/desktop/latest.json", "https://evil.test/desktop/latest.json", "https://storebound.dev/latest.json"] {
assert!(!updates::endpoint_allowed(&Url::parse(bad).unwrap()), "{bad}");
}
assert!(updates::error_text("signature verification failed").contains("not installed"));
}
#[test]
fn open_deep_link_takes_one_plain_absolute_path() {
let p = |s: &str| deep_link_open_path(&Url::parse(s).unwrap());
let abs = if cfg!(windows) { "C:%5Cgames%5Cdemo-app" } else { "%2FUsers%2Fyou%2Fgames%2Fdemo-app" };
let want = if cfg!(windows) { "C:\\games\\demo-app" } else { "/Users/you/games/demo-app" };
assert_eq!(p(&format!("storebound://open?path={abs}")), Some(PathBuf::from(want)));
assert_eq!(p(&format!("storebound://open/?path={abs}")), Some(PathBuf::from(want)));
for bad in [
"storebound://open?path=games%2Fdemo-app".to_string(),
"storebound://open?path=".to_string(),
"storebound://open".to_string(),
format!("storebound://open?path={abs}&path={abs}"),
format!("storebound://open?path={abs}&run=1"),
format!("storebound://open?path={abs}%2F..%2F..%2Fetc"),
format!("storebound://open?path={abs}%0A"),
format!("storebound://open/x?path={abs}"),
format!("storebound://open?path={abs}#f"),
format!("storebound://user@open?path={abs}"),
format!("storebound://apps?path={abs}"),
format!("https://open?path={abs}"),
format!("storebound://open?path={}", "a".repeat(5000)),
] {
assert_eq!(p(&bad), None, "{bad}");
}
}
#[test]
fn self_test_looks_for_the_sidecar_next_to_the_executable() {
let p = selftest::sidecar_path().unwrap();
let exe = std::env::current_exe().unwrap();
assert_eq!(p.parent(), exe.parent());
assert_eq!(p.file_name().unwrap().to_string_lossy(), format!("storebound{}", std::env::consts::EXE_SUFFIX));
assert_eq!(selftest::FLAG, "--self-test");
// the flag is handled in main before Tauri starts, so no window opens
let main = fs::read_to_string(root().join("src/main.rs")).unwrap();
let flag = main.find("selftest::FLAG").unwrap();
assert!(flag < main.find("storebound_desktop::run()").unwrap());
}
#[test]
fn deep_links_to_folders_ask_before_adding() {
let cmds = fs::read_to_string(root().join("src/commands.rs")).unwrap();
let at = cmds.find("async fn open_folder(").unwrap();
let body = &cmds[at..];
let confirm = body.find("confirm_dialog_with(").unwrap();
let add = body.find("add_folder(").unwrap();
assert!(confirm < add, "the native dialog comes before add_folder");
let at = cmds.find("pub async fn update_install(").unwrap();
let body = &cmds[at..];
assert!(body.find("confirm_dialog_with(").unwrap() < body.find("download_and_install(").unwrap());
}
#[test]
fn dev_versions_sort_and_never_downgrade() {
// a dev build of the next patch is above the current stable, below that next stable, and later stamps go up
assert!(updates::newer("0.8.48-dev.20261023120000", "0.8.47"));
assert!(updates::newer("0.8.48", "0.8.48-dev.20261023120000"));
assert!(updates::newer("0.8.48-dev.20261023120001", "0.8.48-dev.20261023120000"));
assert!(updates::newer("0.8.48-dev.20261024000000", "0.8.48-dev.9"), "numeric identifiers compare as numbers");
assert!(updates::newer("0.8.49-dev.1", "0.8.48"));
assert!(updates::newer("0.10.0", "0.9.9"));
assert!(!updates::newer("0.8.48", "0.8.48"));
assert!(!updates::newer("nonsense", "0.8.48"));
// switched from dev back to stable: stays on the dev build until a stable release passes it
assert!(!updates::should_install("0.8.48-dev.20261023120000", "0.8.47"));
assert!(updates::should_install("0.8.48-dev.20261023120000", "0.8.48"));
assert!(updates::should_install("0.8.47", "0.8.48-dev.20261023120000"));
assert!(!updates::should_install("0.8.48-dev.20261023120000", "0.8.48-dev.20261023120000"));
}
#[test]
fn background_updater_schedule_and_defer() {
assert!(updates::check_due(true, false, 0, 0), "at start");
assert!(updates::check_due(false, true, 0, 0), "channel switched");
assert!(updates::check_due(false, false, 3600, 5), "resume from sleep");
assert!(updates::check_due(false, false, 5, updates::CHECK_EVERY_SECS));
assert!(!updates::check_due(false, false, 5, 60));
assert_eq!(updates::CHECK_EVERY_SECS, 900);
assert!(updates::defer_install(true, true, 0));
assert!(!updates::defer_install(true, true, updates::MAX_DEFERS), "an open window still updates after a few checks");
assert!(!updates::defer_install(false, true, 0), "a hidden window can report focus");
assert!(!updates::defer_install(true, false, 0));
let _ = updates::take_kick();
updates::kick();
assert!(updates::take_kick());
assert!(!updates::take_kick());
}
#[test]
fn startup_apps_choice_is_read_from_startup_approved() {
use storebound_desktop::startup_approved_off;
assert!(!startup_approved_off(&[0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]));
assert!(!startup_approved_off(&[0x06, 0, 0, 0]));
assert!(startup_approved_off(&[0x03, 0, 0, 0, 0x20, 0xD1, 0xD3, 0xB1, 0xF0, 0xB3, 0xDC, 0x01]));
assert!(startup_approved_off(&[0x07]));
assert!(!startup_approved_off(&[]));
}