#![forbid(unsafe_code)]
#![allow(clippy::unwrap_used)]
use super::super::path_parse::{parse_exec_target, ExecTargetError};
use super::super::{CliArgs, Command};
use super::{resolve_exec_target, ExecTargetArgs};
use clap::Parser;
use std::path::Path;
fn seed_host(config: &Path, name: &str) {
let path = crate::vps::resolve_config_path(Some(config)).expect("resolve config path");
let mut file = crate::vps::load(&path).unwrap_or_default();
file.hosts.insert(
name.to_string(),
crate::vps::model::VpsRecord::test_new(
name,
"203.0.113.10",
22,
"u",
secrecy::SecretString::from("seed-password-long-enough".to_string()),
None,
None,
None,
None,
None,
None,
None,
false,
),
);
crate::vps::save(&path, &file).expect("write seeded registry");
}
fn seed_active(config: &Path, name: &str) {
let path = crate::vps::resolve_config_path(Some(config)).expect("resolve config path");
let marker = path
.parent()
.expect("config path has a parent")
.join(crate::constants::ACTIVE_VPS_FILE_NAME);
std::fs::write(marker, name).expect("write active marker");
}
fn test_vps(name: &str) -> crate::domain::VpsName {
crate::domain::VpsName::try_new(name).expect("valid test VpsName")
}
#[test]
fn parse_exec_target_rejects_host_only_with_steps() {
let err = parse_exec_target(
false,
None,
None,
false,
vec!["ssh-danilo".into()],
Some("ssh-other".into()),
)
.expect_err("a lone positional must never resolve to the active marker");
assert!(
matches!(err, ExecTargetError::Invalid(_)),
"expected a usage error, got {err:?}"
);
}
#[test]
fn parse_exec_target_use_active_is_explicit() {
let plan = parse_exec_target(
false,
None,
None,
true,
vec!["uptime".into()],
Some("prod".into()),
)
.unwrap();
assert_eq!(
plan.selection,
crate::vps::HostSelection::Single(test_vps("prod"))
);
assert_eq!(plan.command, "uptime");
assert_eq!(plan.source, crate::json_wire::TargetSource::ActiveMarker);
assert!(plan.source.is_ambient());
}
#[test]
fn parse_exec_target_use_active_without_marker_is_typed() {
let err = parse_exec_target(false, None, None, true, vec!["uptime".into()], None)
.expect_err("no marker must fail");
assert_eq!(err, ExecTargetError::NoActiveVps);
}
#[test]
fn parse_exec_target_use_active_rejects_two_positionals() {
let err = parse_exec_target(
false,
None,
None,
true,
vec!["prod".into(), "uptime".into()],
Some("prod".into()),
)
.expect_err("two positionals under --use-active must fail");
assert!(matches!(err, ExecTargetError::Invalid(_)));
}
#[test]
fn parse_exec_target_use_active_conflicts_with_selectors() {
let err = parse_exec_target(
true,
None,
None,
true,
vec!["uptime".into()],
Some("prod".into()),
)
.expect_err("--use-active with --all must fail");
assert!(matches!(err, ExecTargetError::Invalid(_)));
}
#[test]
fn parse_exec_target_tags_selects_and_reports_selector() {
let plan = parse_exec_target(
false,
None,
Some("prod,edge".into()),
false,
vec!["uptime".into()],
None,
)
.unwrap();
assert!(plan.selection.is_batch());
assert_eq!(plan.command, "uptime");
assert_eq!(plan.source, crate::json_wire::TargetSource::Selector);
}
#[test]
fn parse_exec_target_selector_rejects_two_positionals() {
for (all, hosts, tags) in [
(true, None, None),
(false, Some("a".to_string()), None),
(false, None, Some("prod".to_string())),
] {
let err = parse_exec_target(
all,
hosts,
tags,
false,
vec!["prod".into(), "uptime".into()],
None,
)
.expect_err("selector + two positionals must fail");
assert!(matches!(err, ExecTargetError::Invalid(_)));
}
}
#[test]
fn parse_exec_target_usage_message_names_all_forms() {
let err = parse_exec_target(false, None, None, false, vec!["uptime".into()], None)
.expect_err("one positional must fail");
let msg = err.to_string();
assert!(
msg.contains("<VPS> <COMMAND>"),
"missing explicit form: {msg}"
);
assert!(msg.contains("--use-active"), "missing opt-in form: {msg}");
assert!(msg.contains("--all"), "missing selector form: {msg}");
}
#[test]
fn parser_accepts_use_active_on_every_exec_surface() {
for verb in ["exec", "sudo-exec", "su-exec"] {
let args = CliArgs::try_parse_from(["ssh-cli", verb, "--use-active", "uptime"]).unwrap();
let ok = match args.command {
Command::Exec { use_active, .. }
| Command::SudoExec { use_active, .. }
| Command::SuExec { use_active, .. } => use_active,
_ => panic!("{verb} parsed into the wrong variant"),
};
assert!(ok, "{verb} did not set use_active");
}
}
#[test]
fn parser_rejects_use_active_with_selectors() {
for flag in ["--all", "--hosts=a", "--tags=prod"] {
assert!(
CliArgs::try_parse_from(["ssh-cli", "exec", "--use-active", flag, "uptime"]).is_err(),
"clap accepted --use-active with {flag}"
);
}
}
#[test]
fn parse_exec_target_rejects_known_vps_as_command() {
let tmp = tempfile::TempDir::new().expect("tempdir");
let config = tmp.path().to_path_buf();
seed_host(&config, "target-host");
seed_host(&config, "other-host");
seed_active(&config, "other-host");
let err = resolve_exec_target(
ExecTargetArgs {
all: false,
hosts: None,
tags: None,
use_active: true,
target: vec!["target-host".into()],
},
Some(config.as_path()),
)
.expect_err("a registered name must not be accepted as a command");
let msg = err.to_string();
assert!(
msg.contains("target-host"),
"the message must name the token so the caller can see the mistake: {msg}"
);
assert!(
msg.contains("--use-active"),
"the message must teach the fix, not just refuse: {msg}"
);
}
#[test]
fn use_active_still_accepts_a_command_that_is_not_a_host() {
let tmp = tempfile::TempDir::new().expect("tempdir");
let config = tmp.path().to_path_buf();
seed_host(&config, "target-host");
seed_active(&config, "target-host");
let plan = resolve_exec_target(
ExecTargetArgs {
all: false,
hosts: None,
tags: None,
use_active: true,
target: vec!["uptime".into()],
},
Some(config.as_path()),
)
.expect("a plain command under --use-active is the supported form");
assert_eq!(plan.command, "uptime");
assert_eq!(plan.source, crate::json_wire::TargetSource::ActiveMarker);
}