use crate::common::Drip;
use std::fs;
use std::path::Path;
use std::process::{Command, Output};
fn run(drip: &Drip, args: &[&str]) -> Output {
Command::new(&drip.bin)
.args(args)
.env("DRIP_DATA_DIR", drip.data_dir.path())
.env("DRIP_SESSION_ID", &drip.session_id)
.output()
.expect("drip spawn")
}
fn run_init(drip: &Drip, project: &Path, home: &Path, shell: &str) -> Output {
Command::new(&drip.bin)
.args(["init", "--global", "--agent", "claude"])
.current_dir(project)
.env("HOME", home)
.env("SHELL", shell)
.env("DRIP_DATA_DIR", drip.data_dir.path())
.env("DRIP_SESSION_ID", &drip.session_id)
.output()
.expect("drip init")
}
fn run_uninstall(drip: &Drip, project: &Path, home: &Path, shell: &str) -> Output {
Command::new(&drip.bin)
.args(["uninstall", "--global", "--agent", "claude"])
.current_dir(project)
.env("HOME", home)
.env("SHELL", shell)
.env("DRIP_DATA_DIR", drip.data_dir.path())
.env("DRIP_SESSION_ID", &drip.session_id)
.output()
.expect("drip uninstall")
}
#[test]
fn completions_bash_nonempty() {
let drip = Drip::new();
let o = run(&drip, &["completions", "bash"]);
assert!(
o.status.success(),
"stderr={}",
String::from_utf8_lossy(&o.stderr)
);
let s = String::from_utf8_lossy(&o.stdout);
assert!(s.len() > 200, "bash script too small ({} bytes)", s.len());
assert!(
s.contains("_drip"),
"bash script missing _drip function: {s}"
);
assert!(s.contains("complete"), "bash script missing complete call");
}
#[test]
fn completions_zsh_nonempty() {
let drip = Drip::new();
let o = run(&drip, &["completions", "zsh"]);
assert!(o.status.success());
let s = String::from_utf8_lossy(&o.stdout);
assert!(
s.starts_with("#compdef drip"),
"zsh script must begin with #compdef drip, got: {}",
s.lines().next().unwrap_or("")
);
}
#[test]
fn completions_fish_nonempty() {
let drip = Drip::new();
let o = run(&drip, &["completions", "fish"]);
assert!(o.status.success());
let s = String::from_utf8_lossy(&o.stdout);
assert!(
s.contains("complete -c drip"),
"fish script missing `complete -c drip`: {s}"
);
}
#[test]
fn completions_unknown_shell_error() {
let drip = Drip::new();
let o = run(&drip, &["completions", "tcsh"]);
assert!(!o.status.success(), "expected failure for unknown shell");
let err = String::from_utf8_lossy(&o.stderr);
assert!(
err.to_lowercase().contains("unsupported") || err.to_lowercase().contains("invalid"),
"stderr should explain: {err}"
);
}
#[test]
fn completions_powershell_nonempty() {
let drip = Drip::new();
let o = run(&drip, &["completions", "powershell"]);
assert!(
o.status.success(),
"stderr={}",
String::from_utf8_lossy(&o.stderr)
);
let s = String::from_utf8_lossy(&o.stdout);
assert!(
s.len() > 200,
"powershell script too small ({} bytes)",
s.len()
);
assert!(s.contains("drip"), "powershell script must reference drip");
let o2 = run(&drip, &["completions", "pwsh"]);
assert!(o2.status.success(), "pwsh alias failed");
}
#[test]
fn completions_elvish_nonempty() {
let drip = Drip::new();
let o = run(&drip, &["completions", "elvish"]);
assert!(
o.status.success(),
"stderr={}",
String::from_utf8_lossy(&o.stderr)
);
let s = String::from_utf8_lossy(&o.stdout);
assert!(s.contains("drip"), "elvish script must reference drip");
}
#[test]
fn init_skips_powershell_auto_install() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let o = run_init(&drip, project.path(), home.path(), "/usr/bin/pwsh");
assert!(
o.status.success(),
"init failed under SHELL=pwsh: {}",
String::from_utf8_lossy(&o.stderr)
);
assert!(!home.path().join(".zsh/completions/_drip").exists());
assert!(!home.path().join(".bash_completion.d/drip.bash").exists());
assert!(!home
.path()
.join(".config/fish/completions/drip.fish")
.exists());
}
#[test]
fn completions_contain_subcommands() {
let drip = Drip::new();
let expected = [
"read",
"init",
"uninstall",
"meter",
"reset",
"refresh",
"sessions",
"cache",
"watch",
"replay",
"doctor",
"completions",
];
for shell in ["bash", "zsh", "fish"] {
let o = run(&drip, &["completions", shell]);
assert!(o.status.success(), "{shell} failed");
let s = String::from_utf8_lossy(&o.stdout);
for sub in expected {
assert!(s.contains(sub), "{shell} script missing subcommand `{sub}`");
}
}
}
#[test]
fn init_installs_zsh_completion_file() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let o = run_init(&drip, project.path(), home.path(), "/bin/zsh");
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
let comp = home.path().join(".zsh/completions/_drip");
assert!(
comp.exists(),
"zsh completion file not installed at {comp:?}"
);
let body = fs::read_to_string(&comp).unwrap();
assert!(body.starts_with("#compdef drip"), "wrong contents: {body}");
let report = String::from_utf8_lossy(&o.stdout);
assert!(
report.contains("zsh") && report.contains("_drip"),
"init didn't report completion install: {report}"
);
}
#[test]
fn init_installs_bash_completion_file() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let o = run_init(&drip, project.path(), home.path(), "/bin/bash");
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
let comp = home.path().join(".bash_completion.d/drip.bash");
assert!(comp.exists(), "bash completion file not installed");
assert!(fs::read_to_string(&comp).unwrap().contains("_drip"));
}
#[test]
fn init_installs_fish_completion_file() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let o = run_init(&drip, project.path(), home.path(), "/usr/local/bin/fish");
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
let comp = home.path().join(".config/fish/completions/drip.fish");
assert!(comp.exists(), "fish completion file not installed");
assert!(fs::read_to_string(&comp)
.unwrap()
.contains("complete -c drip"));
}
#[test]
fn init_unknown_shell_skips_silently() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let o = run_init(&drip, project.path(), home.path(), "/bin/tcsh");
assert!(
o.status.success(),
"init failed on unknown shell: {}",
String::from_utf8_lossy(&o.stderr)
);
assert!(!home.path().join(".zsh/completions/_drip").exists());
assert!(!home.path().join(".bash_completion.d/drip.bash").exists());
}
#[test]
fn uninstall_removes_zsh_completion() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
run_init(&drip, project.path(), home.path(), "/bin/zsh");
let comp = home.path().join(".zsh/completions/_drip");
assert!(comp.exists(), "init didn't install");
let o = run_uninstall(&drip, project.path(), home.path(), "/bin/zsh");
assert!(o.status.success(), "{}", String::from_utf8_lossy(&o.stderr));
assert!(
!comp.exists(),
"completion file still present after uninstall"
);
}
#[test]
fn uninstall_preserves_user_authored_completion() {
let drip = Drip::new();
let project = tempfile::tempdir().unwrap();
let home = tempfile::tempdir().unwrap();
let dir = home.path().join(".zsh/completions");
fs::create_dir_all(&dir).unwrap();
let user_file = dir.join("_drip");
fs::write(&user_file, "# my own custom completion\nfoo bar\n").unwrap();
let o = run_uninstall(&drip, project.path(), home.path(), "/bin/zsh");
assert!(o.status.success());
assert!(user_file.exists(), "user-authored completion was removed");
let body = fs::read_to_string(&user_file).unwrap();
assert!(body.contains("my own custom completion"));
}