mod common;
use common::TestTempDir;
use std::fs;
use std::process::Command;
fn run(bin: &str, args: &[&str], vault_dir: &std::path::Path) -> std::process::Output {
Command::new(bin)
.args(args)
.env("LOCKBOX_VAULT_DIR", vault_dir)
.env_remove("LOCKBOX_VAULT_PASSWORD")
.env_remove("COMPLETE")
.output()
.unwrap()
}
#[test]
fn completion_generation_supports_shell_override_and_install_uninstall() {
let bin = env!("CARGO_BIN_EXE_lockbox");
let temp = TestTempDir::new("completion-install");
let vault_dir = temp.path().join("vault");
let output_path = temp.path().join("completion").join("lockbox.bash");
let generated = run(
bin,
&[
"completion",
"generate",
"--shell",
"bash",
"--output",
output_path.to_str().unwrap(),
],
&vault_dir,
);
assert!(generated.status.success(), "{generated:?}");
let script = fs::read_to_string(&output_path).unwrap();
assert!(script.contains("_clap_complete_lockbox"));
assert!(script.contains("COMPLETE=\"bash\""));
let installed = temp.path().join("custom").join("lockbox.fish");
let output = run(
bin,
&[
"completion",
"install",
"--shell",
"fish",
"--path",
installed.to_str().unwrap(),
],
&vault_dir,
);
assert!(output.status.success(), "{output:?}");
assert!(installed.exists());
let output = run(
bin,
&[
"completion",
"uninstall",
"--shell",
"fish",
"--path",
installed.to_str().unwrap(),
],
&vault_dir,
);
assert!(output.status.success(), "{output:?}");
assert!(!installed.exists());
}
#[test]
fn locked_vault_completion_falls_back_without_prompt_or_diagnostics() {
let bin = env!("CARGO_BIN_EXE_lockbox");
let temp = TestTempDir::new("completion-locked");
let vault_dir = temp.path().join("vault");
let output = Command::new(bin)
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "4")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", "vault", "profile", "create", ""])
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
assert!(output.stderr.is_empty(), "{:?}", output.stderr);
}
#[test]
fn dynamic_completion_completes_target_lockboxes_and_add_sources() {
let bin = env!("CARGO_BIN_EXE_lockbox");
let temp = TestTempDir::new("completion-paths");
let vault_dir = temp.path().join("vault");
fs::write(temp.path().join("secrets.lbox"), b"test").unwrap();
fs::write(temp.path().join("README.md"), b"test").unwrap();
fs::write(temp.path().join("source.txt"), b"test").unwrap();
fs::create_dir(temp.path().join("source-dir")).unwrap();
fs::create_dir(temp.path().join("unrelated-dir")).unwrap();
let target = Command::new(bin)
.current_dir(temp.path())
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "1")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", ""])
.output()
.unwrap();
assert!(target.status.success(), "{target:?}");
assert!(
String::from_utf8_lossy(&target.stdout).contains("secrets.lbox"),
"{target:?}"
);
let target_stdout = String::from_utf8_lossy(&target.stdout);
assert!(target_stdout.contains("create"), "{target:?}");
assert!(target_stdout.contains("--help"), "{target:?}");
assert!(!target_stdout.contains("README.md"), "{target:?}");
assert!(!target_stdout.contains("source.txt"), "{target:?}");
assert!(!target_stdout.contains("source-dir"), "{target:?}");
assert!(!target_stdout.contains("unrelated-dir"), "{target:?}");
let after_lockbox = Command::new(bin)
.current_dir(temp.path())
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "2")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", "secrets.lbox", ""])
.output()
.unwrap();
assert!(after_lockbox.status.success(), "{after_lockbox:?}");
let after_lockbox_stdout = String::from_utf8_lossy(&after_lockbox.stdout);
assert!(after_lockbox_stdout.contains("add"), "{after_lockbox:?}");
assert!(after_lockbox_stdout.contains("--help"), "{after_lockbox:?}");
assert!(
!after_lockbox_stdout.contains("README.md"),
"{after_lockbox:?}"
);
assert!(
!after_lockbox_stdout.contains("source.txt"),
"{after_lockbox:?}"
);
let source = Command::new(bin)
.current_dir(temp.path())
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "3")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", "secrets.lbox", "add", "sou"])
.output()
.unwrap();
assert!(source.status.success(), "{source:?}");
assert!(
String::from_utf8_lossy(&source.stdout).contains("source.txt"),
"{source:?}"
);
let mirror_action = Command::new(bin)
.current_dir(temp.path())
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "3")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", "secrets.lbox", "mirror", ""])
.output()
.unwrap();
assert!(mirror_action.status.success(), "{mirror_action:?}");
assert!(
String::from_utf8_lossy(&mirror_action.stdout).contains("create"),
"{mirror_action:?}"
);
}
#[test]
fn dynamic_completion_navigates_open_lockbox_paths() {
let bin = env!("CARGO_BIN_EXE_lockbox");
let temp = TestTempDir::new("completion-open-paths");
let vault_dir = temp.path().join("vault");
let nested = temp.path().join("nested").join("deeper");
fs::create_dir_all(&nested).unwrap();
fs::write(nested.join("secrets.lbox"), b"test").unwrap();
let directory = Command::new(bin)
.current_dir(temp.path())
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "1")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", "nested/d"])
.output()
.unwrap();
assert!(directory.status.success(), "{directory:?}");
assert!(
String::from_utf8_lossy(&directory.stdout).contains("nested/deeper/"),
"{directory:?}"
);
let lockbox = Command::new(bin)
.current_dir(temp.path())
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "1")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", "nested/deeper/sec"])
.output()
.unwrap();
assert!(lockbox.status.success(), "{lockbox:?}");
assert!(
String::from_utf8_lossy(&lockbox.stdout).contains("nested/deeper/secrets.lbox"),
"{lockbox:?}"
);
}
#[test]
fn dynamic_completion_reads_vault_names_without_exposing_signing_material() {
let bin = env!("CARGO_BIN_EXE_lockbox");
let temp = TestTempDir::new("completion-dynamic");
let vault_dir = temp.path().join("vault");
let agent_dir = temp.path().join("agent");
let agent_log = temp.path().join("agent.log");
let password = "completion-test-vault-password";
let init = Command::new(bin)
.args(["vault", "init"])
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("LOCKBOX_VAULT_PASSWORD", password)
.env("LOCKBOX_SESSION_AGENT_DIR", &agent_dir)
.env("LOCKBOX_SESSION_AGENT_LOG", &agent_log)
.output()
.unwrap();
assert!(init.status.success(), "{init:?}");
let create = Command::new(bin)
.args(["vault", "profile", "create", "alice"])
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("LOCKBOX_VAULT_PASSWORD", password)
.env("LOCKBOX_SESSION_AGENT_DIR", &agent_dir)
.env("LOCKBOX_SESSION_AGENT_LOG", &agent_log)
.output()
.unwrap();
assert!(create.status.success(), "{create:?}");
let log_len_before_completion = fs::metadata(&agent_log).map(|m| m.len()).unwrap_or(0);
let output = Command::new(bin)
.env("LOCKBOX_VAULT_DIR", &vault_dir)
.env("LOCKBOX_VAULT_PASSWORD", password)
.env("LOCKBOX_SESSION_AGENT_DIR", &agent_dir)
.env("LOCKBOX_SESSION_AGENT_LOG", &agent_log)
.env("COMPLETE", "bash")
.env("_CLAP_COMPLETE_INDEX", "4")
.env("_CLAP_COMPLETE_COMP_TYPE", "9")
.env("_CLAP_COMPLETE_SPACE", "true")
.args(["--", "lockbox", "vault", "profile", "history", "al"])
.output()
.unwrap();
assert!(output.status.success(), "{output:?}");
assert!(String::from_utf8_lossy(&output.stdout).contains("alice"));
assert!(!String::from_utf8_lossy(&output.stdout).contains("LBX1SPRV"));
assert!(output.stderr.is_empty(), "{:?}", output.stderr);
let agent_log_bytes = fs::read(&agent_log).unwrap_or_default();
let completion_log = &agent_log_bytes[log_len_before_completion as usize..];
assert!(
!String::from_utf8_lossy(completion_log).contains("owner-signing:"),
"completion must not request an owner-signing key from the agent"
);
}