use bssh::commands::interactive::connection::is_auth_error_for_password_fallback;
use bssh::ssh::tokio_client::Error as SshError;
#[test]
fn test_all_agent_errors_trigger_password_fallback() {
let agent_errors = vec![
(
SshError::AgentAuthenticationFailed,
"AgentAuthenticationFailed",
),
(SshError::AgentNoIdentities, "AgentNoIdentities"),
(SshError::AgentConnectionFailed, "AgentConnectionFailed"),
(
SshError::AgentRequestIdentitiesFailed,
"AgentRequestIdentitiesFailed",
),
];
for (error, name) in agent_errors {
assert!(
is_auth_error_for_password_fallback(&error),
"{} should trigger password fallback",
name
);
}
}
#[test]
fn test_key_auth_failure_triggers_password_fallback() {
let error = SshError::KeyAuthFailed;
assert!(
is_auth_error_for_password_fallback(&error),
"KeyAuthFailed should trigger password fallback"
);
}
#[test]
fn test_non_auth_errors_do_not_trigger_fallback() {
let non_auth_errors: Vec<(SshError, &str)> = vec![
(SshError::PasswordWrong, "PasswordWrong"),
(SshError::ServerCheckFailed, "ServerCheckFailed"),
(SshError::CommandDidntExit, "CommandDidntExit"),
(
SshError::KeyboardInteractiveAuthFailed,
"KeyboardInteractiveAuthFailed",
),
(
SshError::IoError(std::io::Error::new(
std::io::ErrorKind::ConnectionRefused,
"connection refused",
)),
"IoError",
),
];
for (error, name) in non_auth_errors {
assert!(
!is_auth_error_for_password_fallback(&error),
"{} should NOT trigger password fallback",
name
);
}
}
#[test]
fn test_password_wrong_prevents_infinite_loop() {
let error = SshError::PasswordWrong;
assert!(
!is_auth_error_for_password_fallback(&error),
"PasswordWrong must NOT trigger password fallback to prevent infinite retry loops"
);
}
#[test]
fn test_host_key_verification_not_bypassed() {
let error = SshError::ServerCheckFailed;
assert!(
!is_auth_error_for_password_fallback(&error),
"ServerCheckFailed must NOT trigger password fallback - host key verification is a security feature"
);
}
#[test]
fn test_ssh_disconnect_during_auth_triggers_password_fallback() {
let error = SshError::SshError(russh::Error::Disconnect);
assert!(
is_auth_error_for_password_fallback(&error),
"SshError(Disconnect) should trigger password fallback - \
server may disconnect after key authentication rejection (issue #113)"
);
}
#[test]
fn test_ssh_recv_error_during_auth_triggers_password_fallback() {
let error = SshError::SshError(russh::Error::RecvError);
assert!(
is_auth_error_for_password_fallback(&error),
"SshError(RecvError) should trigger password fallback - \
server may close connection during authentication"
);
}
#[test]
fn test_other_ssh_errors_do_not_trigger_fallback() {
let non_auth_ssh_errors: Vec<(SshError, &str)> = vec![
(
SshError::SshError(russh::Error::HUP),
"HUP - remote closed connection",
),
(
SshError::SshError(russh::Error::ConnectionTimeout),
"ConnectionTimeout - network issue",
),
(
SshError::SshError(russh::Error::NotAuthenticated),
"NotAuthenticated - auth not attempted yet",
),
];
for (error, desc) in non_auth_ssh_errors {
assert!(
!is_auth_error_for_password_fallback(&error),
"{} should NOT trigger password fallback",
desc
);
}
}