mod options {
mod ssh_command {
use crate::client::ssh::{connect::Options, ProgramKind};
#[test]
fn no_field_means_ssh() {
assert_eq!(Options::default().ssh_command(), "ssh");
}
#[test]
fn command_field_determines_ssh_command() {
assert_eq!(
Options {
command: Some("field-value".into()),
..Default::default()
}
.ssh_command(),
"field-value"
);
assert_eq!(
Options {
command: Some("field-value".into()),
kind: Some(ProgramKind::TortoisePlink),
..Default::default()
}
.ssh_command(),
"field-value"
);
}
#[test]
fn kind_serves_as_fallback() {
assert_eq!(
Options {
kind: Some(ProgramKind::TortoisePlink),
..Default::default()
}
.ssh_command(),
"tortoiseplink.exe"
);
}
}
}
mod program_kind {
mod from_os_str {
use std::ffi::OsStr;
use crate::client::ssh::ProgramKind;
#[test]
fn known_variants_are_derived_from_basename() {
for name_or_path in [
"ssh",
"ssh.exe",
"SSH",
"SSH.exe",
"/bin/ssh",
"/bin/SSH",
#[cfg(windows)]
"c:\\bin\\ssh.exe",
] {
assert_eq!(
ProgramKind::from(OsStr::new(name_or_path)),
ProgramKind::Ssh,
"{name_or_path:?} could not be identified correctly"
);
}
assert_eq!(
ProgramKind::from(OsStr::new("TortoisePlink.exe")),
ProgramKind::TortoisePlink
);
assert_eq!(ProgramKind::from(OsStr::new("putty")), ProgramKind::Putty);
assert_eq!(
ProgramKind::from(OsStr::new("../relative/Plink.exe")),
ProgramKind::Plink
);
}
#[test]
fn unknown_variants_fallback_to_simple() {
assert_eq!(
ProgramKind::from(OsStr::new("something-unknown-that-does-not-exist-for-sure-foobar")),
ProgramKind::Simple,
"in theory, we could fail right here but we don't and leave non-existing programs to fail during handshake"
);
}
#[test]
fn ssh_disguised_within_a_script_cannot_be_detected_due_to_invocation_with_dash_g() {
assert_eq!(
ProgramKind::from(OsStr::new("ssh -VVV")),
ProgramKind::Simple,
"we don't execute the command here but assume simple, even though we could determine it's ssh if we would do what git does here"
);
}
}
mod prepare_invocation {
use std::ffi::OsStr;
use crate::{
client::{ssh, ssh::ProgramKind},
Protocol,
};
#[test]
fn ssh() {
for (url, protocol, expected) in [
("ssh://user@host:42/p", Protocol::V1, &["ssh", "-p42", "user@host"][..]),
("ssh://user@host/p", Protocol::V1, &["ssh", "user@host"][..]),
("ssh://host/p", Protocol::V1, &["ssh", "host"][..]),
(
"ssh://user@host:42/p",
Protocol::V2,
&["ssh", "-o", "SendEnv=GIT_PROTOCOL", "-p42", "user@host"][..],
),
(
"ssh://user@host/p",
Protocol::V2,
&["ssh", "-o", "SendEnv=GIT_PROTOCOL", "user@host"][..],
),
(
"ssh://host/p",
Protocol::V2,
&["ssh", "-o", "SendEnv=GIT_PROTOCOL", "host"][..],
),
] {
assert_eq!(call_args(ProgramKind::Ssh, url, protocol), quoted(expected));
}
}
#[test]
fn tortoise_plink_has_batch_command() {
assert_eq!(
call_args(ProgramKind::TortoisePlink, "ssh://user@host:42/p", Protocol::V2),
quoted(&["tortoiseplink.exe", "-batch", "-P", "42", "user@host"])
);
}
#[test]
fn port_for_all() {
for kind in [ProgramKind::TortoisePlink, ProgramKind::Plink, ProgramKind::Putty] {
assert!(call_args(kind, "ssh://user@host:43/p", Protocol::V2).ends_with(r#""-P" "43" "user@host""#));
}
}
#[test]
fn simple_cannot_handle_any_arguments() {
match try_call(ProgramKind::Simple, "ssh://user@host:42/p", Protocol::V2) {
Err(ssh::invocation::Error { .. }) => {}
_ => panic!("BUG: unexpected outcome"),
}
assert_eq!(
call_args(ProgramKind::Simple, "ssh://user@host/p", Protocol::V2),
quoted(&["simple", "user@host"]),
"simple can only do simple invocations"
);
}
#[test]
fn ssh_env_v2() {
let prepare = call(ProgramKind::Ssh, "ssh://host/p", Protocol::V2);
assert_eq!(
prepare.env,
&[
("GIT_PROTOCOL".into(), "version=2".into()),
("LANG".into(), "C".into()),
("LC_ALL".into(), "C".into())
]
);
assert!(!prepare.use_shell);
}
#[test]
fn disallow_shell_is_honored() -> Result {
let url = git_url::parse("ssh://host/path".into()).expect("valid url");
let disallow_shell = false;
let prepare =
ProgramKind::Ssh.prepare_invocation(OsStr::new("echo hi"), &url, Protocol::V1, disallow_shell)?;
assert!(prepare.use_shell, "shells are used when needed");
let disallow_shell = true;
let prepare =
ProgramKind::Ssh.prepare_invocation(OsStr::new("echo hi"), &url, Protocol::V1, disallow_shell)?;
assert!(
!prepare.use_shell,
"but we can enforce it not to be used as well for historical reasons"
);
Ok(())
}
fn quoted(input: &[&str]) -> String {
input.iter().map(|s| format!("\"{s}\"")).collect::<Vec<_>>().join(" ")
}
fn try_call(
kind: ProgramKind,
url: &str,
version: Protocol,
) -> std::result::Result<git_command::Prepare, ssh::invocation::Error> {
let ssh_cmd = kind.exe().unwrap_or_else(|| OsStr::new("simple"));
let url = git_url::parse(url.into()).expect("valid url");
kind.prepare_invocation(ssh_cmd, &url, version, false)
}
fn call(kind: ProgramKind, url: &str, version: Protocol) -> git_command::Prepare {
try_call(kind, url, version).expect("no error")
}
fn call_args(kind: ProgramKind, url: &str, version: Protocol) -> String {
format!("{:?}", std::process::Command::from(call(kind, url, version)))
}
type Result = std::result::Result<(), ssh::invocation::Error>;
}
mod line_to_err {
use std::io::ErrorKind;
use crate::client::ssh::ProgramKind;
#[test]
fn all() {
for (kind, line, expected) in [
(
ProgramKind::Ssh,
"byron@github.com: Permission denied (publickey).",
ErrorKind::PermissionDenied,
),
(
ProgramKind::Ssh,
"ssh: Could not resolve hostname hostfoobar: nodename nor servname provided, or not known",
ErrorKind::ConnectionRefused,
),
(
ProgramKind::Ssh,
"ssh: connect to host example.org port 22: No route to host",
ErrorKind::NotFound,
),
(
ProgramKind::Ssh,
"banner exchange: Connection to 127.0.0.1 port 61024: Software caused connection abort",
ErrorKind::NotFound,
),
(
ProgramKind::Ssh,
"Connection closed by 127.0.0.1 port 8888", ErrorKind::NotFound,
),
(
ProgramKind::Simple,
"something permission denied something",
ErrorKind::PermissionDenied,
),
(
ProgramKind::Simple,
"something resolve hostname hostfoobar: nodename nor servname something",
ErrorKind::ConnectionRefused,
),
(
ProgramKind::Simple,
"something connect to host something",
ErrorKind::NotFound,
),
] {
assert_eq!(kind.line_to_err(line.into()).map(|err| err.kind()), Ok(expected));
}
}
#[test]
fn tortoiseplink_putty_plink() {
for kind in [ProgramKind::TortoisePlink, ProgramKind::Plink, ProgramKind::Putty] {
assert_eq!(
kind
.line_to_err("publickey".into())
.map(|err| err.kind()),
Ok(std::io::ErrorKind::PermissionDenied),
"this program pops up error messages in a window, no way to extract information from it. Maybe there is other ways to use it, 'publickey' they mention all"
);
}
}
}
}