use crate::common::{
mock_commands::{MockConfig, MockResponse},
wt_command,
};
use std::path::{Path, PathBuf};
use std::process::Command;
use tempfile::TempDir;
fn prepend_path(cmd: &mut Command, dir: &Path) {
let (path_var, current) = std::env::vars_os()
.find(|(k, _)| k.eq_ignore_ascii_case("PATH"))
.map(|(k, v)| (k.to_string_lossy().into_owned(), Some(v)))
.unwrap_or(("PATH".to_string(), None));
let mut paths: Vec<PathBuf> = current
.as_deref()
.map(|p| std::env::split_paths(p).collect())
.unwrap_or_default();
paths.insert(0, dir.to_path_buf());
let new_path = std::env::join_paths(&paths).unwrap();
cmd.env(path_var, new_path);
}
fn mock_bin_dir(name: &str, response: MockResponse) -> TempDir {
let dir = TempDir::new().unwrap();
MockConfig::new(name)
.command("_default", response)
.write(dir.path());
dir
}
#[test]
fn external_subcommand_runs_wt_prefixed_binary_on_path() {
let dir = mock_bin_dir(
"wt-wt-test-extcmd-ok",
MockResponse::output("external ran\n"),
);
let mut cmd = wt_command();
prepend_path(&mut cmd, dir.path());
cmd.env("MOCK_CONFIG_DIR", dir.path());
cmd.args(["wt-test-extcmd-ok", "arg1", "arg2"]);
let output = cmd.output().expect("failed to run wt");
assert!(
output.status.success(),
"expected success, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim(),
"external ran"
);
}
#[test]
fn external_subcommand_not_found_prints_clap_error() {
let mut cmd = wt_command();
let empty = TempDir::new().unwrap();
cmd.env("PATH", empty.path());
cmd.arg("definitely-not-a-wt-subcommand");
let output = cmd.output().expect("failed to run wt");
assert!(!output.status.success(), "expected failure");
assert_eq!(output.status.code(), Some(2));
let stderr = strip_ansi(&String::from_utf8_lossy(&output.stderr));
assert!(
stderr.contains("unrecognized subcommand 'definitely-not-a-wt-subcommand'"),
"stderr should use clap's native error format: {stderr}"
);
assert!(
stderr.contains("Usage:") && stderr.contains("try '--help'"),
"stderr should include Usage block and --help suggestion: {stderr}"
);
}
#[test]
fn external_subcommand_typo_suggests_closest_builtin() {
let mut cmd = wt_command();
let empty = TempDir::new().unwrap();
cmd.env("PATH", empty.path());
cmd.arg("siwtch");
let output = cmd.output().expect("failed to run wt");
assert!(!output.status.success());
let stderr = strip_ansi(&String::from_utf8_lossy(&output.stderr));
assert!(
stderr.contains("tip:") && stderr.contains("similar subcommand"),
"stderr missing clap's similar-subcommand tip: {stderr}"
);
assert!(
stderr.contains("'switch'"),
"stderr should suggest 'switch': {stderr}"
);
}
#[test]
fn external_subcommand_nested_suggestion_wins_over_path_lookup() {
let mut cmd = wt_command();
let empty = TempDir::new().unwrap();
cmd.env("PATH", empty.path());
cmd.arg("squash");
let output = cmd.output().expect("failed to run wt");
assert!(!output.status.success());
assert_eq!(output.status.code(), Some(2));
let stderr = strip_ansi(&String::from_utf8_lossy(&output.stderr));
assert!(
stderr.contains("wt step squash"),
"stderr should suggest 'wt step squash': {stderr}"
);
}
fn strip_ansi(s: &str) -> String {
let mut out = String::with_capacity(s.len());
let mut chars = s.chars();
while let Some(c) = chars.next() {
if c == '\u{1b}' {
for next in chars.by_ref() {
if next.is_ascii_alphabetic() {
break;
}
}
} else {
out.push(c);
}
}
out
}
#[test]
fn external_subcommand_propagates_exit_code() {
let dir = mock_bin_dir(
"wt-wt-test-extcmd-fail",
MockResponse::exit(0).with_exit_code(42),
);
let mut cmd = wt_command();
prepend_path(&mut cmd, dir.path());
cmd.env("MOCK_CONFIG_DIR", dir.path());
cmd.arg("wt-test-extcmd-fail");
let output = cmd.output().expect("failed to run wt");
assert_eq!(
output.status.code(),
Some(42),
"expected exit code 42, stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("exited with status"),
"wt should not decorate child failures: {stderr}"
);
}
#[test]
fn external_subcommand_respects_global_dash_c_flag() {
let target_dir = TempDir::new().unwrap();
let dir = mock_bin_dir("wt-wt-test-extcmd-cwd", MockResponse::output("ok\n"));
let mut cmd = wt_command();
prepend_path(&mut cmd, dir.path());
cmd.env("MOCK_CONFIG_DIR", dir.path());
cmd.current_dir(std::env::temp_dir());
cmd.args([
"-C",
target_dir.path().to_str().unwrap(),
"wt-test-extcmd-cwd",
]);
let output = cmd.output().expect("failed to run wt");
assert!(
output.status.success(),
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn external_subcommand_passes_help_flag_through() {
let dir = mock_bin_dir(
"wt-wt-test-extcmd-help",
MockResponse::output("child got help\n"),
);
let mut cmd = wt_command();
prepend_path(&mut cmd, dir.path());
cmd.env("MOCK_CONFIG_DIR", dir.path());
cmd.args(["wt-test-extcmd-help", "--help"]);
let output = cmd.output().expect("failed to run wt");
assert!(output.status.success());
assert_eq!(
String::from_utf8_lossy(&output.stdout).trim(),
"child got help",
"stderr: {}",
String::from_utf8_lossy(&output.stderr)
);
}