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 skill_zip() -> Vec<u8> {
let mut buf = Vec::new();
{
let mut writer = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
let options =
SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
writer.add_directory("tdd/", options).unwrap();
writer.start_file("tdd/SKILL.md", options).unwrap();
writer
.write_all(
b"---\nname: tdd\ndescription: Test-driven development workflow\n---\n# TDD\n\nWrite the test first.\n",
)
.unwrap();
writer.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 delete_commands_are_primary_and_old_names_remain_aliases() {
let td = tempfile::tempdir().unwrap();
let root = td.path();
let subkey = "Software\\voli-test-cli\\delete-aliases";
let help = voli(root, subkey, &["--help"]);
let stdout = String::from_utf8_lossy(&help.stdout);
assert!(stdout.contains("\n delete "));
assert!(stdout.contains("\n self-delete "));
assert!(!stdout.contains("\n uninstall "));
assert!(!stdout.contains("\n self-uninstall "));
for command in ["delete", "uninstall", "self-delete", "self-uninstall"] {
let output = voli(root, subkey, &[command, "--help"]);
assert!(output.status.success(), "{command} should parse");
}
}
#[test]
fn local_skill_install_list_and_targeted_delete() {
let temp = tempfile::tempdir().unwrap();
let root = temp.path().join("voli");
let home = temp.path().join("home");
let archive_bytes = skill_zip();
let archive = temp.path().join("tdd.zip");
fs::write(&archive, &archive_bytes).unwrap();
let manifest = temp.path().join("tdd.toml");
fs::write(
&manifest,
format!(
r#"name = "tdd"
version = "1.0.0"
kind = "skill"
[source.any]
url = "https://example.com/tdd.zip"
sha256 = "{}"
"#,
sha256_hex(&archive_bytes)
),
)
.unwrap();
let run = |args: &[&str]| {
Command::new(env!("CARGO_BIN_EXE_voli"))
.args(args)
.env("VOLI_ROOT", &root)
.env("USERPROFILE", &home)
.stdin(Stdio::null())
.output()
.unwrap()
};
let install = run(&[
"--json",
"install",
manifest.to_str().unwrap(),
"--archive",
archive.to_str().unwrap(),
"--for",
"codex",
]);
assert!(
install.status.success(),
"{}",
String::from_utf8_lossy(&install.stderr)
);
let install_json: serde_json::Value = serde_json::from_slice(&install.stdout).unwrap();
assert_eq!(install_json["installed"][0]["kind"], "skill");
assert_eq!(install_json["installed"][0]["target"], "codex");
assert!(home.join(".agents/skills/tdd/SKILL.md").is_file());
let list = run(&["list"]);
assert!(list.status.success());
assert!(String::from_utf8_lossy(&list.stdout).contains("skill/tdd 1.0.0 [codex]"));
let delete = run(&["delete", "skill/tdd", "--for", "codex"]);
assert!(
delete.status.success(),
"{}",
String::from_utf8_lossy(&delete.stderr)
);
assert!(!home.join(".agents/skills/tdd").exists());
}
#[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/app"]);
assert!(env_out.status.success());
assert!(String::from_utf8_lossy(&env_out.stdout).contains("JAVA_HOME"));
assert!(voli(root, sk, &["pin", "app/app"]).status.success());
assert!(voli(root, sk, &["unpin", "app/app"]).status.success());
let un = voli(root, sk, &["delete", "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")
);
}