use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
use std::sync::Once;
use sha2::{Digest, Sha256};
use voli_core::{Manifest, State, env, install_local};
use zip::write::SimpleFileOptions;
static STUB: Once = Once::new();
fn ensure_stub() {
STUB.call_once(|| {
let stub = std::env::current_exe()
.ok()
.and_then(|p| {
p.parent()
.and_then(|d| d.parent())
.map(|d| d.join("voli-shim.exe"))
})
.filter(|p| p.exists())
.unwrap_or_else(|| {
let p = std::env::temp_dir().join("voli-test-shim-stub.exe");
fs::write(&p, b"dummy shim stub").unwrap();
p
});
unsafe { std::env::set_var("VOLI_SHIM_STUB", stub) };
});
}
fn sha256_hex(bytes: &[u8]) -> String {
let d = Sha256::digest(bytes);
d.iter().map(|b| format!("{b:02x}")).collect()
}
fn app_zip() -> Vec<u8> {
let mut buf = Vec::new();
{
let mut w = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
let opts = SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
w.add_directory("app-1.0.0/", opts).unwrap();
w.start_file("app-1.0.0/app.exe", opts).unwrap();
w.write_all(b"fake app").unwrap();
w.add_directory("app-1.0.0/bin/", opts).unwrap();
w.finish().unwrap();
}
buf
}
fn write_env_manifest(dir: &Path, sha: &str) -> std::path::PathBuf {
let toml = format!(
r#"
name = "app"
version = "1.0.0"
kind = "app"
extract_dir = "app-1.0.0"
bin = ["app.exe"]
[source.x64]
url = "https://example.com/app.zip"
sha256 = "{sha}"
[env]
JAVA_HOME = "{{dir}}"
"#
);
let p = dir.join("app.toml");
fs::write(&p, toml).unwrap();
p
}
fn voli(root: &Path, subkey: &str, args: &[&str]) -> std::process::Output {
Command::new(env!("CARGO_BIN_EXE_voli"))
.args(args)
.env("VOLI_ROOT", root)
.env("VOLI_ENV_SUBKEY", subkey)
.stdin(Stdio::null()) .output()
.unwrap()
}
#[test]
fn non_tty_install_auto_applies_env_and_uninstall_restores() {
let td = tempfile::tempdir().unwrap();
let root = td.path();
let sk = "Software\\voli-test-cli\\autoapply";
let _ = env::delete_subkey(sk);
let zip = app_zip();
let archive = root.join("app.zip");
fs::write(&archive, &zip).unwrap();
let manifest = write_env_manifest(root, &sha256_hex(&zip));
let out = voli(
root,
sk,
&[
"install",
manifest.to_str().unwrap(),
"--archive",
archive.to_str().unwrap(),
],
);
assert!(
out.status.success(),
"install failed: {}",
String::from_utf8_lossy(&out.stderr)
);
let expect = root
.join("apps")
.join("app")
.join("current")
.to_string_lossy()
.into_owned();
assert_eq!(
env::get(sk, "JAVA_HOME").unwrap().as_deref(),
Some(expect.as_str())
);
let env_out = voli(root, sk, &["env", "app"]);
assert!(env_out.status.success());
assert!(String::from_utf8_lossy(&env_out.stdout).contains("JAVA_HOME"));
let un = voli(root, sk, &["uninstall", "app"]);
assert!(un.status.success());
assert_eq!(
env::get(sk, "JAVA_HOME").unwrap(),
None,
"zero-trace: key deleted"
);
let _ = env::delete_subkey(sk);
}
#[test]
fn no_env_flag_skips_application() {
let td = tempfile::tempdir().unwrap();
let root = td.path();
let sk = "Software\\voli-test-cli\\noenv";
let _ = env::delete_subkey(sk);
let zip = app_zip();
let archive = root.join("app.zip");
fs::write(&archive, &zip).unwrap();
let manifest = write_env_manifest(root, &sha256_hex(&zip));
let out = voli(
root,
sk,
&[
"install",
manifest.to_str().unwrap(),
"--archive",
archive.to_str().unwrap(),
"--no-env",
],
);
assert!(out.status.success());
assert_eq!(
env::get(sk, "JAVA_HOME").unwrap(),
None,
"--no-env applies nothing"
);
let _ = env::delete_subkey(sk);
}
#[test]
fn doctor_warns_on_env_drift() {
let td = tempfile::tempdir().unwrap();
let root = td.path();
let sk = "Software\\voli-test-cli\\drift";
let _ = env::delete_subkey(sk);
let zip = app_zip();
let archive = root.join("app.zip");
fs::write(&archive, &zip).unwrap();
let manifest = write_env_manifest(root, &sha256_hex(&zip));
voli(
root,
sk,
&[
"install",
manifest.to_str().unwrap(),
"--archive",
archive.to_str().unwrap(),
],
);
env::set(sk, "JAVA_HOME", "C:\\hand\\edited").unwrap();
let out = voli(root, sk, &["doctor", "--json"]);
let text = String::from_utf8_lossy(&out.stdout);
let json: serde_json::Value = serde_json::from_str(&text).unwrap();
let drift = json["checks"]
.as_array()
.unwrap()
.iter()
.find(|c| c["check"] == "env drift")
.expect("doctor should report an env-drift check");
assert_eq!(drift["status"], "WARN", "drift must WARN, not FAIL");
assert_eq!(
env::get(sk, "JAVA_HOME").unwrap().as_deref(),
Some("C:\\hand\\edited")
);
let _ = env::delete_subkey(sk);
}
#[test]
fn upgrade_all_skips_pinned() {
ensure_stub();
let td = tempfile::tempdir().unwrap();
let root = td.path();
let sk = "Software\\voli-test-cli\\pin";
let zip = app_zip();
let archive = root.join("app.zip");
fs::write(&archive, &zip).unwrap();
let manifest = write_env_manifest(root, &sha256_hex(&zip));
install_local(&manifest, &archive, root).unwrap();
let m1 = Manifest::from_toml_str(
r#"name="app"
version="1.0.0"
kind="app"
bin=["app.exe"]
[source.x64]
url="https://example.com/app-1.0.0.zip"
sha256="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa""#,
)
.unwrap();
let m2 = Manifest::from_toml_str(
r#"name="app"
version="2.0.0"
kind="app"
bin=["app.exe"]
[source.x64]
url="https://example.com/app-2.0.0.zip"
sha256="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb""#,
)
.unwrap();
voli_core::index::build(&[m1, m2], &voli_core::index::index_db_path(root)).unwrap();
{
let mut state = State::open(&voli_core::Paths::at(root).state_db()).unwrap();
assert!(state.set_pinned("app", true).unwrap());
}
let out = voli(root, sk, &["upgrade", "--all"]);
assert!(out.status.success());
let text = String::from_utf8_lossy(&out.stdout);
assert!(
text.contains("pinned"),
"expected a pinned-skip note:\n{text}"
);
let state = State::open(&voli_core::Paths::at(root).state_db()).unwrap();
assert_eq!(
state.installed_version("app").unwrap().as_deref(),
Some("1.0.0")
);
}