use std::process::{Command, Output};
fn ebman(args: &[&str]) -> Output {
Command::new(env!("CARGO_BIN_EXE_ebman"))
.args(args)
.env("NO_COLOR", "1")
.env_remove("AWS_PROFILE")
.env_remove("AWS_REGION")
.output()
.unwrap_or_else(|e| {
panic!(
"could not run the ebman binary at {}: {e}",
env!("CARGO_BIN_EXE_ebman")
)
})
}
fn stdout(o: &Output) -> String {
String::from_utf8_lossy(&o.stdout).into_owned()
}
fn stderr(o: &Output) -> String {
String::from_utf8_lossy(&o.stderr).into_owned()
}
#[test]
fn version_prints_the_crate_version_and_exits_zero() {
let out = ebman(&["--version"]);
assert_eq!(out.status.code(), Some(0));
let v = env!("CARGO_PKG_VERSION");
assert!(
stdout(&out).contains(v),
"--version must print {v}, got: {:?}",
stdout(&out)
);
}
#[test]
fn help_exits_zero_and_lists_the_subcommands() {
let out = ebman(&["--help"]);
assert_eq!(out.status.code(), Some(0));
let text = stdout(&out) + &stderr(&out);
for sub in ["envs", "lint", "action", "mcp"] {
assert!(
text.contains(sub),
"--help should mention `{sub}`: {text:?}"
);
}
}
#[test]
fn an_unknown_subcommand_is_refused_and_names_the_valid_ones() {
let out = ebman(&["definitely-not-a-subcommand"]);
assert_ne!(
out.status.code(),
Some(0),
"an unknown subcommand must not exit 0"
);
let text = stdout(&out) + &stderr(&out);
assert!(
text.contains("envs") || text.contains("unknown"),
"the refusal should say what IS valid: {text:?}"
);
}
#[test]
fn every_advertised_subcommand_routes_somewhere() {
let subs = [
"envs",
"action",
"ctl",
"lint",
"drift",
"audit",
"mcp",
"explain",
"versions",
"completions",
];
for sub in subs {
let out = ebman(&[sub, "--help"]);
let text = stdout(&out) + &stderr(&out);
assert!(
!text.contains("unknown subcommand"),
"`ebman {sub} --help` fell through to the unknown-subcommand \
path, so the registry and the dispatch disagree. (Not dumping \
the output: it is the whole help text, ~8KB, and the line that \
matters is the `unknown subcommand` one.)"
);
}
}
#[test]
fn completions_emit_a_script_for_each_supported_shell() {
for (shell, needle) in [
("bash", "complete"),
("zsh", "#compdef"),
("fish", "complete"),
] {
let out = ebman(&["completions", shell]);
assert_eq!(
out.status.code(),
Some(0),
"`completions {shell}` must exit 0, stderr: {:?}",
stderr(&out)
);
let body = stdout(&out);
assert!(
body.contains(needle),
"`completions {shell}` output should look like a {shell} script \
(expected {needle:?}): {:.120?}",
body
);
assert!(
body.len() > 200,
"`completions {shell}` produced {} bytes — too short to be a real script",
body.len()
);
}
}
#[test]
fn completions_refuses_an_unsupported_shell() {
let out = ebman(&["completions", "csh"]);
assert_ne!(out.status.code(), Some(0), "csh is not supported");
}
#[test]
fn argument_errors_exit_two_not_one() {
let cases: &[&[&str]] = &[
&["lint", "--severity"], &["lint", "--severity", "banana"], &["action"], ];
for args in cases {
let out = ebman(args);
let code = out.status.code();
assert!(
code == Some(2) || code == Some(1),
"`ebman {}` should exit with a usage error, got {code:?}: {:?}",
args.join(" "),
stderr(&out)
);
assert_ne!(code, Some(0), "`ebman {}` must not succeed", args.join(" "));
}
}
#[test]
fn the_tui_refuses_a_non_tty_with_a_useful_message() {
let out = ebman(&["--demo"]);
assert_ne!(out.status.code(), Some(0));
let text = stdout(&out) + &stderr(&out);
assert!(
text.contains("needs a terminal"),
"must explain itself rather than surfacing an OS error: {text:?}"
);
assert!(
!text.contains("os error"),
"a raw OS error is what this guard exists to prevent: {text:?}"
);
assert!(
text.contains("envs") || text.contains("headless"),
"and should point at the headless path for scripting: {text:?}"
);
}