#[cfg(test)]
mod tests {
use crate::ssh::ssh_config::parser::parse;
use crate::ssh::ssh_config::resolver::find_host_config;
#[test]
fn test_certificate_file_merging_across_host_blocks() {
let content = r#"
Host *
CertificateFile ~/.ssh/global-cert.pub
Host example.com
CertificateFile ~/.ssh/example-cert.pub
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.certificate_files.len(), 2);
assert!(config.certificate_files[0]
.to_string_lossy()
.contains("global-cert.pub"));
assert!(config.certificate_files[1]
.to_string_lossy()
.contains("example-cert.pub"));
}
#[test]
fn test_certificate_file_deduplication() {
let content = r#"
Host *
CertificateFile ~/.ssh/shared-cert.pub
Host example.com
CertificateFile ~/.ssh/shared-cert.pub
CertificateFile ~/.ssh/example-cert.pub
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.certificate_files.len(), 2);
assert!(config.certificate_files[0]
.to_string_lossy()
.contains("shared-cert.pub"));
assert!(config.certificate_files[1]
.to_string_lossy()
.contains("example-cert.pub"));
}
#[test]
fn test_certificate_file_limit_during_merge() {
let mut config_lines = vec!["Host example.com".to_string()];
for i in 0..110 {
config_lines.push(format!(" CertificateFile ~/.ssh/cert-{i}.pub"));
}
let content = config_lines.join("\n");
let hosts = parse(&content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.certificate_files.len(), 100);
}
#[test]
fn test_permit_remote_open_merging_across_host_blocks() {
let content = r#"
Host *
PermitRemoteOpen localhost:8080
Host example.com
PermitRemoteOpen db.internal:5432
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.permit_remote_open.len(), 2);
assert_eq!(config.permit_remote_open[0], "localhost:8080");
assert_eq!(config.permit_remote_open[1], "db.internal:5432");
}
#[test]
fn test_permit_remote_open_deduplication() {
let content = r#"
Host *
PermitRemoteOpen localhost:8080
Host example.com
PermitRemoteOpen localhost:8080
PermitRemoteOpen db.internal:5432
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.permit_remote_open.len(), 2);
assert_eq!(config.permit_remote_open[0], "localhost:8080");
assert_eq!(config.permit_remote_open[1], "db.internal:5432");
}
#[test]
fn test_ca_signature_algorithms_override() {
let content = r#"
Host *
CASignatureAlgorithms ssh-rsa,ssh-dss
Host example.com
CASignatureAlgorithms ssh-ed25519,rsa-sha2-512
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.ca_signature_algorithms.len(), 2);
assert_eq!(config.ca_signature_algorithms[0], "ssh-ed25519");
assert_eq!(config.ca_signature_algorithms[1], "rsa-sha2-512");
}
#[test]
fn test_hostbased_accepted_algorithms_override() {
let content = r#"
Host *
HostbasedAcceptedAlgorithms ssh-rsa,ssh-dss
Host example.com
HostbasedAcceptedAlgorithms ssh-ed25519,ecdsa-sha2-nistp256
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.hostbased_accepted_algorithms.len(), 2);
assert_eq!(config.hostbased_accepted_algorithms[0], "ssh-ed25519");
assert_eq!(
config.hostbased_accepted_algorithms[1],
"ecdsa-sha2-nistp256"
);
}
#[test]
fn test_gateway_ports_override() {
let content = r#"
Host *
GatewayPorts no
Host example.com
GatewayPorts yes
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.gateway_ports, Some("yes".to_string()));
}
#[test]
fn test_exit_on_forward_failure_override() {
let content = r#"
Host *
ExitOnForwardFailure no
Host example.com
ExitOnForwardFailure yes
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.exit_on_forward_failure, Some(true));
}
#[test]
fn test_hostbased_authentication_override() {
let content = r#"
Host *
HostbasedAuthentication no
Host example.com
HostbasedAuthentication yes
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.hostbased_authentication, Some(true));
}
#[test]
fn test_multiple_host_blocks_with_priority() {
let content = r#"
Host example.com
GatewayPorts yes
CertificateFile ~/.ssh/first-cert.pub
Host *.com
GatewayPorts no
CertificateFile ~/.ssh/second-cert.pub
Host *
ExitOnForwardFailure yes
CertificateFile ~/.ssh/third-cert.pub
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.gateway_ports, Some("no".to_string()));
assert_eq!(config.exit_on_forward_failure, Some(true));
assert_eq!(config.certificate_files.len(), 3);
assert!(config.certificate_files[0]
.to_string_lossy()
.contains("first-cert.pub"));
assert!(config.certificate_files[1]
.to_string_lossy()
.contains("second-cert.pub"));
assert!(config.certificate_files[2]
.to_string_lossy()
.contains("third-cert.pub"));
}
#[test]
fn test_all_new_options_together() {
let content = r#"
Host secure.example.com
CertificateFile ~/.ssh/user-cert.pub
CertificateFile ~/.ssh/host-cert.pub
CASignatureAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
GatewayPorts clientspecified
ExitOnForwardFailure yes
PermitRemoteOpen localhost:8080
PermitRemoteOpen db.internal:5432
HostbasedAuthentication yes
HostbasedAcceptedAlgorithms ssh-ed25519,rsa-sha2-512
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "secure.example.com");
assert_eq!(config.certificate_files.len(), 2);
assert_eq!(config.ca_signature_algorithms.len(), 3);
assert_eq!(config.gateway_ports, Some("clientspecified".to_string()));
assert_eq!(config.exit_on_forward_failure, Some(true));
assert_eq!(config.permit_remote_open.len(), 2);
assert_eq!(config.permit_remote_open[0], "localhost:8080");
assert_eq!(config.permit_remote_open[1], "db.internal:5432");
assert_eq!(config.hostbased_authentication, Some(true));
assert_eq!(config.hostbased_accepted_algorithms.len(), 2);
}
#[test]
fn test_empty_vs_unset_options() {
let content = r#"
Host example.com
User testuser
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.certificate_files.len(), 0);
assert_eq!(config.ca_signature_algorithms.len(), 0);
assert_eq!(config.gateway_ports, None);
assert_eq!(config.exit_on_forward_failure, None);
assert_eq!(config.permit_remote_open.len(), 0);
assert_eq!(config.hostbased_authentication, None);
assert_eq!(config.hostbased_accepted_algorithms.len(), 0);
}
#[test]
fn test_partial_option_set() {
let content = r#"
Host example.com
CertificateFile ~/.ssh/cert.pub
GatewayPorts yes
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.certificate_files.len(), 1);
assert_eq!(config.gateway_ports, Some("yes".to_string()));
assert_eq!(config.ca_signature_algorithms.len(), 0);
assert_eq!(config.exit_on_forward_failure, None);
assert_eq!(config.permit_remote_open.len(), 0);
assert_eq!(config.hostbased_authentication, None);
assert_eq!(config.hostbased_accepted_algorithms.len(), 0);
}
#[test]
fn test_proxy_use_fdpass_merging() {
let content = r#"
Host *
ProxyCommand ssh -W %h:%p bastion1.example.com
ProxyUseFdpass no
Host example.com
ProxyCommand ssh -W %h:%p bastion2.example.com
ProxyUseFdpass yes
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(
config.proxy_command,
Some("ssh -W %h:%p bastion2.example.com".to_string())
);
assert_eq!(config.proxy_use_fdpass, Some(true));
}
#[test]
fn test_proxy_use_fdpass_inherits_from_global() {
let content = r#"
Host *
ProxyUseFdpass yes
Host example.com
ProxyCommand ssh -W %h:%p bastion.example.com
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(
config.proxy_command,
Some("ssh -W %h:%p bastion.example.com".to_string())
);
assert_eq!(config.proxy_use_fdpass, Some(true));
}
#[test]
#[cfg(target_os = "macos")]
fn test_use_keychain_override() {
let content = r#"
Host *
UseKeychain no
Host example.com
UseKeychain yes
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.use_keychain, Some(true));
}
#[test]
#[cfg(target_os = "macos")]
fn test_use_keychain_inherits_from_global() {
let content = r#"
Host *
UseKeychain yes
Host example.com
AddKeysToAgent yes
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.add_keys_to_agent, Some("yes".to_string()));
assert_eq!(config.use_keychain, Some(true));
}
#[test]
#[cfg(target_os = "macos")]
fn test_use_keychain_last_match_wins() {
let content = r#"
Host example.com
UseKeychain yes
Host example.com
UseKeychain no
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.use_keychain, Some(false));
}
#[test]
#[cfg(target_os = "macos")]
fn test_use_keychain_with_identity_files() {
let content = r#"
Host *
IdentityFile ~/.ssh/id_rsa
UseKeychain yes
Host example.com
IdentityFile ~/.ssh/id_ed25519
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.identity_files.len(), 2);
assert!(config.identity_files[0]
.to_string_lossy()
.contains("id_rsa"));
assert!(config.identity_files[1]
.to_string_lossy()
.contains("id_ed25519"));
assert_eq!(config.use_keychain, Some(true));
}
#[test]
#[cfg(target_os = "macos")]
fn test_use_keychain_default_none() {
let content = r#"
Host example.com
User testuser
"#;
let hosts = parse(content).unwrap();
let config = find_host_config(&hosts, "example.com");
assert_eq!(config.use_keychain, None);
}
}