#![forbid(unsafe_code)]
use std::path::PathBuf;
use std::process::Command;
fn bin() -> PathBuf {
PathBuf::from(env!("CARGO_BIN_EXE_ssh-cli"))
}
fn help_for(args: &[&str]) -> String {
let out = Command::new(bin())
.args(args)
.arg("--help")
.output()
.expect("spawn ssh-cli");
let mut text = String::from_utf8_lossy(&out.stdout).into_owned();
text.push_str(&String::from_utf8_lossy(&out.stderr));
text
}
#[test]
fn every_persisting_command_accepts_the_key_passphrase_on_stdin() {
let surfaces: &[&[&str]] = &[
&["vps", "add"],
&["vps", "edit"],
&["exec"],
&["scp", "upload"],
&["scp", "download"],
&["health-check"],
&["tunnel"],
];
for args in surfaces {
let help = help_for(args);
assert!(
help.contains("--key-passphrase-stdin"),
"{args:?} must accept --key-passphrase-stdin; argv is not a secret channel"
);
}
}
#[test]
fn the_stdin_variants_exist_for_every_secret_on_vps_add() {
let help = help_for(&["vps", "add"]);
for flag in [
"--password-stdin",
"--key-passphrase-stdin",
"--sudo-password-stdin",
"--su-password-stdin",
] {
assert!(help.contains(flag), "vps add must accept {flag}");
}
}
#[test]
fn two_stdin_secrets_are_refused_rather_than_silently_emptied() {
let combos: &[(&str, &str)] = &[
("--password-stdin", "--sudo-password-stdin"),
("--password-stdin", "--key-passphrase-stdin"),
("--sudo-password-stdin", "--su-password-stdin"),
("--key-passphrase-stdin", "--su-password-stdin"),
];
let tmp = std::env::temp_dir().join(format!("ssh-cli-d13-{}", std::process::id()));
for (a, b) in combos {
let out = Command::new(bin())
.args(["--config-dir", &tmp.to_string_lossy()])
.args(["vps", "add"])
.args(["--name", "d13", "--host", "127.0.0.1", "--user", "u"])
.arg(a)
.arg(b)
.output()
.expect("spawn ssh-cli");
assert!(
!out.status.success(),
"{a} + {b} must be refused; a second stdin read yields an empty secret"
);
}
let _ = std::fs::remove_dir_all(&tmp);
}
#[test]
fn transfer_commands_reject_a_flag_between_positionals() {
let tmp = std::env::temp_dir().join(format!("ssh-cli-d14-{}", std::process::id()));
let interleaved = Command::new(bin())
.args(["--config-dir", &tmp.to_string_lossy()])
.args([
"scp",
"upload",
"somehost",
"--timeout",
"1000",
"/tmp/a",
"/tmp/b",
])
.output()
.expect("spawn ssh-cli");
assert_eq!(
interleaved.status.code(),
Some(2),
"a flag between positionals must fail loudly as a clap parse error, not be silently accepted"
);
let leading = Command::new(bin())
.args(["--config-dir", &tmp.to_string_lossy()])
.args([
"scp",
"upload",
"--timeout",
"1000",
"somehost",
"/tmp/a",
"/tmp/b",
])
.output()
.expect("spawn ssh-cli");
assert_ne!(
leading.status.code(),
Some(2),
"options before the positional run must parse; got a clap parse error instead"
);
let _ = std::fs::remove_dir_all(&tmp);
}