#![cfg(feature = "authentication")]
#[allow(clippy::duplicate_mod)]
#[path = "fixtures/mod.rs"]
mod fixtures;
#[allow(clippy::duplicate_mod)]
#[path = "helpers.rs"]
mod helpers;
use nut_shell::config::{DefaultConfig, ShellConfig};
#[test]
fn test_password_masking_basic() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "admin");
let output = shell.io().output();
assert!(
output.contains("admin"),
"Username should be echoed normally"
);
assert!(!output.contains("*"), "No masking before colon");
shell.io_mut().clear_output();
shell.process_char(':').unwrap();
let output = shell.io().output();
assert_eq!(output, ":", "Colon should be echoed normally");
shell.io_mut().clear_output();
helpers::type_input_auth(&mut shell, "pass");
let output = shell.io().output();
assert_eq!(output, "****", "Password should be masked with asterisks");
}
#[test]
fn test_password_with_special_chars() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "user@example.com:P@ss!");
let output = shell.io().output();
assert!(
output.contains("user@example.com:"),
"Username with @ should be visible"
);
assert!(
output.contains("*****"),
"Password with special chars should be masked"
);
assert!(
!output.contains("P@ss!"),
"Actual password should not appear"
);
}
#[test]
fn test_password_with_multiple_colons() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "admin:P:a:s:s");
let output = shell.io().output();
assert!(
output.contains("admin:"),
"Username and first colon visible"
);
let asterisk_count = output.matches('*').count();
assert_eq!(
asterisk_count, 7,
"All password characters including colons should be masked"
);
assert!(
!output.contains("P:a:s:s"),
"Actual password should not appear"
);
}
#[test]
fn test_password_masking_with_backspace() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "admin:pass");
let output_before = shell.io().output();
assert!(output_before.contains("****"), "Password should be masked");
shell.io_mut().clear_output();
shell.process_char('\x7f').unwrap();
let output_after = shell.io().output();
assert!(
output_after.contains("\x08"),
"Should send backspace sequence"
);
}
#[test]
fn test_password_masking_empty_username() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, ":password");
let output = shell.io().output();
assert!(output.starts_with(":"), "Colon should be visible");
assert!(
output.contains("********"),
"Password should be masked even with empty username"
);
}
#[test]
fn test_password_masking_unicode_chars() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "admin:päss");
let output = shell.io().output();
let asterisk_count = output.matches('*').count();
assert_eq!(
asterisk_count, 4,
"Unicode characters should be masked individually"
);
assert!(
!output.contains("päss"),
"Actual password should not appear"
);
}
#[test]
fn test_double_esc_clears_masked_input() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "admin:pass");
let output_before = shell.io_mut().output();
assert!(
output_before.contains("admin:****"),
"Password should be masked before clearing: {}",
output_before
);
shell.io_mut().clear_output();
shell.process_char('\x1b').unwrap(); shell.process_char('\x1b').unwrap();
let clear_output = shell.io().output();
assert!(
clear_output.contains("\r"),
"Should send carriage return after double ESC: {:?}",
clear_output
);
assert!(
clear_output.contains("\x1b[K"),
"Should send clear-to-EOL sequence after double ESC: {:?}",
clear_output
);
shell.io_mut().clear_output();
shell.process_char('\n').unwrap();
let output_after = shell.io_mut().output();
assert!(
output_after.contains(DefaultConfig::MSG_INVALID_LOGIN_FORMAT),
"Empty input after double ESC should show invalid format message, got: {}",
output_after
);
assert!(
!output_after.contains(DefaultConfig::MSG_LOGIN_SUCCESS)
&& !output_after.contains(DefaultConfig::MSG_LOGIN_FAILED),
"Double ESC should have cleared login buffer, got: {}",
output_after
);
}
#[test]
fn test_login_with_wrong_password() {
let mut shell = helpers::create_auth_shell();
let output = helpers::execute_command_auth(&mut shell, "admin:wrongpass");
assert!(
output.contains(DefaultConfig::MSG_LOGIN_FAILED),
"Wrong password should show failure: {}",
output
);
}
#[test]
fn test_login_with_nonexistent_user() {
let mut shell = helpers::create_auth_shell();
let output = helpers::execute_command_auth(&mut shell, "nobody:password");
assert!(
output.contains(DefaultConfig::MSG_LOGIN_FAILED),
"Non-existent user should show generic failure: {}",
output
);
}
#[test]
fn test_login_with_empty_username() {
let mut shell = helpers::create_auth_shell();
let output = helpers::execute_command_auth(&mut shell, ":password");
assert!(
output.contains(DefaultConfig::MSG_LOGIN_FAILED),
"Empty username should show login failed: {}",
output
);
}
#[test]
fn test_login_with_empty_password() {
let mut shell = helpers::create_auth_shell();
let output = helpers::execute_command_auth(&mut shell, "admin:");
assert!(
output.contains(DefaultConfig::MSG_LOGIN_FAILED),
"Empty password should show failure: {}",
output
);
}
#[test]
fn test_login_without_colon() {
let mut shell = helpers::create_auth_shell();
let output = helpers::execute_command_auth(&mut shell, "admin");
assert!(
output.contains(DefaultConfig::MSG_INVALID_LOGIN_FORMAT),
"Missing colon should show invalid format: {}",
output
);
}
#[test]
fn test_multiple_login_attempts() {
let mut shell = helpers::create_auth_shell();
let output1 = helpers::execute_command_auth(&mut shell, "admin:wrong1");
assert!(output1.contains(DefaultConfig::MSG_LOGIN_FAILED));
let output2 = helpers::execute_command_auth(&mut shell, "admin:wrong2");
assert!(output2.contains(DefaultConfig::MSG_LOGIN_FAILED));
let output3 = helpers::execute_command_auth(&mut shell, "admin:admin123");
assert!(
output3.contains(DefaultConfig::MSG_LOGIN_SUCCESS),
"Should eventually succeed with correct credentials: {}",
output3
);
}
#[test]
fn test_password_backspace_removes_asterisks() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "admin:pass");
let before = shell.io().output();
assert_eq!(
helpers::count_char(&before, '*'),
4,
"Should have 4 asterisks before backspace"
);
shell.io_mut().clear_output();
helpers::press_backspace_auth(&mut shell);
let after = shell.io().output();
assert!(
after.contains("\x08"),
"Backspace should emit backspace sequence"
);
}
#[test]
fn test_successful_login_changes_prompt() {
let mut shell = helpers::create_auth_shell();
helpers::execute_command_auth(&mut shell, "admin:admin123");
shell.io_mut().clear_output();
helpers::execute_command_auth(&mut shell, "echo test");
let output = shell.io().output();
assert!(
output.contains("admin@/>"),
"Prompt should show username after login: {}",
output
);
}
#[test]
fn test_case_sensitive_username() {
let mut shell = helpers::create_auth_shell();
let output = helpers::execute_command_auth(&mut shell, "Admin:admin123");
assert!(
output.contains(DefaultConfig::MSG_LOGIN_FAILED),
"Username should be case-sensitive: {}",
output
);
}
#[test]
fn test_password_masking_immediate() {
let mut shell = helpers::create_auth_shell();
helpers::type_input_auth(&mut shell, "admin:");
shell.io_mut().clear_output();
helpers::type_input_auth(&mut shell, "x");
let output = shell.io().output();
assert_eq!(
output, "*",
"First char after colon should be masked: {:?}",
output
);
}