Files
spotifast/tests/update_launch.rs
T
Carmine Paolino cd0bf03308 Update through fastframe-update
Checking for, downloading, verifying and installing an update now go
through fastframe-update, configured for Spotifast: its repository and
names, Fastpotify's legacy names and Windows location, the macOS
bundle ids and renamed executables, and no publisher key until releases
are signed (checksums.txt alone, as before). The crate's reqwest
transport runs on Spotifast's blocking client with its proxy.
fastframe_update::intercept runs first in main: `--apply-update <job>`
makes the process the helper, which exits before anything else, and
`--update-receipt` and `--update-error` are taken out before clap parses
the rest, so the hidden flags leave Cli. The handoff and receipt formats
are unchanged (the crate's compat tests read Spotifast 0.10.1's), the
update interface and its tests stay, and a new test checks that both
commands accept the update flags and that the helper runs before the
app. The updater tests of the deleted modules move to the crate.

Behaviour changes:

- The macOS helper runs from a copy of the whole bundle in the staging
  folder, not from the installed bundle, so moving and restoring the
  installed bundle never touches the helper's own code.
- A Nix install says "Update this installation with Nix." and a Homebrew
  one "Update this installation with Homebrew.", where both said "with
  Nix or Homebrew".
- The old portable layout of 0.8.0 and 0.9.0 is no longer recognised in a
  downloaded archive; no release older than 0.10 can be offered.
- Before the Windows setup program left a marker, its current default
  location Programs/Spotifast/spotifast.exe is recognised as well as
  Programs/Fastpotify/fastpotify.exe.
- The release check runs on the blocking client like the download, with
  the updater's own headers and redirect rules.
2026-09-25 15:56:09 +02:00

70 lines
2.8 KiB
Rust

//! What the previous release's updater relies on from a new binary: the
//! update flags are taken out before the argument parser sees them, and
//! `--apply-update` makes the process the helper before anything else runs.
use std::process::Command;
/// An update relaunch passes `--update-receipt` (or `--update-error`)
/// alongside the app's own arguments. Both commands must accept them, which
/// they only do if the flags are intercepted before clap parses the rest;
/// `--version` also answers `<command> <version>`, as old helpers check.
#[test]
fn update_flags_are_intercepted_before_the_arguments_are_parsed() {
for (program, name) in [
(env!("CARGO_BIN_EXE_spotifast"), "spotifast"),
(env!("CARGO_BIN_EXE_fastpotify"), "fastpotify"),
] {
for flags in [
[
"--update-receipt",
"/missing/.spotifast-update-0000000000000000/handoff.json",
],
[
"--update-error",
"The update could not start. The previous version has been restored.",
],
] {
let output = Command::new(program)
.args(flags)
.arg("--version")
.output()
.unwrap();
assert!(output.status.success(), "{name} {flags:?}: {output:?}");
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim(),
format!("{name} {}", env!("CARGO_PKG_VERSION"))
);
}
}
}
/// `--apply-update <job>` runs the helper and exits before the app starts:
/// a missing job fails with a message on standard error and exit code 1,
/// and no log file or profile is touched (the environment points them at a
/// scratch folder that must stay empty).
#[test]
fn apply_update_runs_the_helper_before_the_app() {
let scratch = std::env::temp_dir().join(format!(
"spotifast-apply-update-{:016x}",
rand::random::<u64>()
));
std::fs::create_dir(&scratch).unwrap();
let output = Command::new(env!("CARGO_BIN_EXE_spotifast"))
.arg("--apply-update")
.arg(scratch.join("missing-job.json"))
.env("HOME", &scratch)
.env("XDG_CONFIG_HOME", scratch.join("config"))
.env("XDG_STATE_HOME", scratch.join("state"))
.env("XDG_CACHE_HOME", scratch.join("cache"))
.env("XDG_DATA_HOME", scratch.join("data"))
.env("APPDATA", scratch.join("appdata"))
.env("LOCALAPPDATA", scratch.join("localappdata"))
.output()
.unwrap();
let left = std::fs::read_dir(&scratch).unwrap().count();
std::fs::remove_dir_all(&scratch).unwrap();
assert_eq!(output.status.code(), Some(1), "{output:?}");
assert!(!output.stderr.is_empty(), "the helper explains its failure");
assert_eq!(left, 0, "the helper must not start the app");
}