use u2secure::domain::audit::{AuditItem, AuditReport, AuditStatus, PackageManager};
use u2secure::domain::steps::{ExecuteParams, SshKeyAction, StepKind};
#[test]
fn test_audit_status_icons() {
assert_eq!(AuditStatus::Safe.icon(), "✅");
assert_eq!(AuditStatus::Partial.icon(), "⚠️");
assert_eq!(AuditStatus::Missing.icon(), "❌");
assert_eq!(AuditStatus::NeedsUpdate.icon(), "🔄");
}
#[test]
fn test_audit_item_constructors() {
let item = AuditItem::safe("测试", "已启用".into());
assert_eq!(item.status, AuditStatus::Safe);
assert_eq!(item.detail, "已启用");
let item = AuditItem::missing("测试", "未配置".into());
assert_eq!(item.status, AuditStatus::Missing);
let item = AuditItem::needs_update("测试", "需要更新".into());
assert_eq!(item.status, AuditStatus::NeedsUpdate);
let item = AuditItem::partial("测试", "部分配置".into());
assert_eq!(item.status, AuditStatus::Partial);
}
#[test]
fn test_package_manager_display() {
assert_eq!(PackageManager::Apt.name(), "apt");
assert_eq!(PackageManager::Yum.name(), "yum");
assert_eq!(PackageManager::Dnf.to_string(), "dnf");
assert_eq!(PackageManager::Unknown.to_string(), "unknown");
}
#[test]
fn test_package_manager_update_upgrade_cmd() {
assert_eq!(PackageManager::Apt.update_cmd(), &["apt", "update"]);
assert_eq!(PackageManager::Apt.upgrade_cmd(), &["apt", "upgrade", "-y"]);
assert_eq!(PackageManager::Yum.update_cmd(), &["yum", "update", "-y"]);
assert_eq!(PackageManager::Unknown.update_cmd(), &[] as &[&str]);
}
#[test]
fn test_step_kind_labels() {
assert_eq!(StepKind::SystemUpdate.label(), "系统更新");
assert_eq!(StepKind::UserCreation.label(), "非 root 用户创建");
assert_eq!(StepKind::SshRootLogin.label(), "禁止 root SSH 登录");
assert_eq!(StepKind::SshPortChange.label(), "SSH 端口修改");
assert_eq!(StepKind::SshPasswordAuth.label(), "禁止密码登录");
assert_eq!(StepKind::SshKeySetup.label(), "ED25519 密钥设置");
assert_eq!(StepKind::Ufw.label(), "UFW 防火墙配置");
assert_eq!(StepKind::Fail2ban.label(), "Fail2ban 安装配置");
assert_eq!(StepKind::AutoUpdates.label(), "自动安全更新");
assert_eq!(StepKind::SecurityScan.label(), "安全扫描");
assert_eq!(StepKind::LogAudit.label(), "日志与审计增强");
assert_eq!(StepKind::RestartSsh.label(), "SSH 服务重启与验证");
}
#[test]
fn test_step_kind_all_contains_all() {
let all = StepKind::all();
assert_eq!(all.len(), 12);
assert!(all.contains(&StepKind::SystemUpdate));
assert!(all.contains(&StepKind::RestartSsh));
}
#[test]
fn test_audit_report_status_for_all_secure() {
let report = AuditReport {
items: vec![],
is_root: true,
package_manager: PackageManager::Apt,
ssh_port: 2222,
password_auth_disabled: true,
root_login_disabled: true,
sudo_users: vec!["alice".into()],
fail2ban_installed: true,
ufw_enabled: true,
auto_updates_enabled: true,
system_up_to_date: true,
};
assert_eq!(report.status_for(StepKind::SystemUpdate), AuditStatus::Safe);
assert_eq!(report.status_for(StepKind::UserCreation), AuditStatus::Safe);
assert_eq!(report.status_for(StepKind::SshRootLogin), AuditStatus::Safe);
assert_eq!(
report.status_for(StepKind::SshPortChange),
AuditStatus::Safe
);
assert_eq!(
report.status_for(StepKind::SshPasswordAuth),
AuditStatus::Safe
);
assert_eq!(report.status_for(StepKind::Ufw), AuditStatus::Safe);
assert_eq!(report.status_for(StepKind::Fail2ban), AuditStatus::Safe);
assert_eq!(report.status_for(StepKind::AutoUpdates), AuditStatus::Safe);
}
#[test]
fn test_audit_report_status_for_all_missing() {
let report = AuditReport {
items: vec![],
is_root: false,
package_manager: PackageManager::Unknown,
ssh_port: 22,
password_auth_disabled: false,
root_login_disabled: false,
sudo_users: vec![],
fail2ban_installed: false,
ufw_enabled: false,
auto_updates_enabled: false,
system_up_to_date: false,
};
assert_eq!(
report.status_for(StepKind::SystemUpdate),
AuditStatus::NeedsUpdate
);
assert_eq!(
report.status_for(StepKind::UserCreation),
AuditStatus::Missing
);
assert_eq!(
report.status_for(StepKind::SshRootLogin),
AuditStatus::Missing
);
assert_eq!(
report.status_for(StepKind::SshPortChange),
AuditStatus::Missing
);
assert_eq!(
report.status_for(StepKind::SshPasswordAuth),
AuditStatus::Missing
);
assert_eq!(
report.status_for(StepKind::SshKeySetup),
AuditStatus::Missing
);
assert_eq!(report.status_for(StepKind::Ufw), AuditStatus::Missing);
assert_eq!(report.status_for(StepKind::Fail2ban), AuditStatus::Missing);
assert_eq!(
report.status_for(StepKind::AutoUpdates),
AuditStatus::Missing
);
assert_eq!(
report.status_for(StepKind::SecurityScan),
AuditStatus::Missing
);
assert_eq!(report.status_for(StepKind::LogAudit), AuditStatus::Missing);
assert_eq!(
report.status_for(StepKind::RestartSsh),
AuditStatus::Missing
);
}
#[test]
fn test_audit_report_status_for_key_setup_with_sudo_users() {
let report = AuditReport {
items: vec![],
is_root: true,
package_manager: PackageManager::Apt,
ssh_port: 22,
password_auth_disabled: false,
root_login_disabled: false,
sudo_users: vec!["bob".into()],
fail2ban_installed: false,
ufw_enabled: false,
auto_updates_enabled: false,
system_up_to_date: false,
};
assert_eq!(
report.status_for(StepKind::SshKeySetup),
AuditStatus::Partial
);
}
#[test]
fn test_audit_report_summary_lines() {
let report = AuditReport {
items: vec![],
is_root: false,
package_manager: PackageManager::Apt,
ssh_port: 2222,
password_auth_disabled: true,
root_login_disabled: true,
sudo_users: vec!["admin".into()],
fail2ban_installed: false,
ufw_enabled: true,
auto_updates_enabled: false,
system_up_to_date: true,
};
let summary = report.summary_lines();
assert_eq!(summary.len(), 8);
assert_eq!(summary[0].1, AuditStatus::Safe);
assert_eq!(summary[1].1, AuditStatus::Safe);
assert_eq!(summary[2].1, AuditStatus::Safe);
assert_eq!(summary[3].1, AuditStatus::Safe);
assert_eq!(summary[4].1, AuditStatus::Safe);
assert_eq!(summary[5].1, AuditStatus::Safe);
assert_eq!(summary[6].1, AuditStatus::Missing);
assert_eq!(summary[7].1, AuditStatus::Missing);
}
#[test]
fn test_step_kind_display() {
assert_eq!(StepKind::SystemUpdate.to_string(), "系统更新");
assert_eq!(StepKind::RestartSsh.to_string(), "SSH 服务重启与验证");
}
#[test]
fn test_step_kind_check_default_status() {
let report = AuditReport {
items: vec![],
is_root: true,
package_manager: PackageManager::Apt,
ssh_port: 2222,
password_auth_disabled: true,
root_login_disabled: false,
sudo_users: vec!["admin".into()],
fail2ban_installed: false,
ufw_enabled: true,
auto_updates_enabled: false,
system_up_to_date: true,
};
assert_eq!(
StepKind::SystemUpdate.check_default_status(&report),
AuditStatus::Safe
);
assert_eq!(
StepKind::SshRootLogin.check_default_status(&report),
AuditStatus::Missing
);
assert_eq!(
StepKind::Fail2ban.check_default_status(&report),
AuditStatus::Missing
);
}
#[test]
fn test_execute_params_default() {
let params = ExecuteParams::default();
assert!(params.new_username.is_none());
assert!(params.lock_password);
assert!(params.new_ssh_port.is_none());
assert!(params.ssh_key_action.is_none());
assert!(params.ssh_key_username.is_none());
}
#[test]
fn test_execute_params_user_creation() {
let params = ExecuteParams {
new_username: Some("deploy".into()),
lock_password: true,
..Default::default()
};
assert_eq!(params.new_username.as_deref(), Some("deploy"));
assert!(params.lock_password);
}
#[test]
fn test_execute_params_ssh_port() {
let params = ExecuteParams {
new_ssh_port: Some(2222),
..Default::default()
};
assert_eq!(params.new_ssh_port, Some(2222));
}
#[test]
fn test_execute_params_ssh_key_generate() {
let params = ExecuteParams {
ssh_key_username: Some("admin".into()),
ssh_key_action: Some(SshKeyAction::GenerateNew),
..Default::default()
};
assert_eq!(params.ssh_key_username.as_deref(), Some("admin"));
assert!(matches!(
params.ssh_key_action,
Some(SshKeyAction::GenerateNew)
));
}
#[test]
fn test_execute_params_ssh_key_paste() {
let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... user@host".to_string();
let params = ExecuteParams {
ssh_key_username: Some("admin".into()),
ssh_key_action: Some(SshKeyAction::PasteKey(key.clone())),
..Default::default()
};
assert!(matches!(¶ms.ssh_key_action, Some(SshKeyAction::PasteKey(k)) if k == &key));
}
#[test]
fn test_execute_params_all_fields() {
let params = ExecuteParams {
new_username: Some("deploy".into()),
lock_password: false,
new_ssh_port: Some(2222),
ssh_key_username: Some("deploy".into()),
ssh_key_action: Some(SshKeyAction::GenerateNew),
};
assert_eq!(params.new_username.as_deref(), Some("deploy"));
assert!(!params.lock_password);
assert_eq!(params.new_ssh_port, Some(2222));
assert_eq!(params.ssh_key_username.as_deref(), Some("deploy"));
}
#[test]
fn test_ssh_key_action_debug_generate() {
let action = SshKeyAction::GenerateNew;
assert_eq!(format!("{action:?}"), "GenerateNew");
}
#[test]
fn test_ssh_key_action_debug_paste_redacts_key() {
let long_key =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8O1oQK7Q8z6fVc9pZ3sX2yR4mW5jH0nBvLkPqR1sT"
.to_string();
let action = SshKeyAction::PasteKey(long_key);
let debug_str = format!("{action:?}");
assert!(
debug_str.contains("[redacted]"),
"PasteKey Debug 应脱敏: {debug_str}"
);
assert!(
!debug_str.contains("AAAAC3NzaC1lZDI1NTE5AAAAIK8O1oQK7Q8z6fVc9pZ3sX2yR4mW5jH0nBvLkPqR1sT"),
"Debug 不应泄露完整公钥: {debug_str}"
);
assert!(debug_str.starts_with("PasteKey(\"ssh-ed25519 "));
}
#[test]
fn test_ssh_key_action_clone() {
let action = SshKeyAction::PasteKey("ssh-ed25519 key".into());
let cloned = action.clone();
assert!(matches!(cloned, SshKeyAction::PasteKey(_)));
}
#[test]
fn test_ssh_key_action_debug_utf8_boundary_no_panic() {
let key_with_cjk = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5你-test";
let action = SshKeyAction::PasteKey(key_with_cjk.to_string());
let debug_str = format!("{action:?}");
assert!(
debug_str.contains("[redacted]") || debug_str.contains(key_with_cjk),
"Debug 输出异常: {debug_str}"
);
}
#[test]
fn test_ssh_key_action_debug_emoji_boundary_no_panic() {
let key_with_emoji = "ssh-ed25519 A🚀test";
let action = SshKeyAction::PasteKey(key_with_emoji.to_string());
let debug_str = format!("{action:?}");
assert!(!debug_str.is_empty());
}
fn sshd_line_match(line: &str, key: &str) -> bool {
let trimmed = line.trim();
if trimmed.starts_with('#') || trimmed.is_empty() {
return false;
}
let first_word = trimmed.split_whitespace().next().unwrap_or("");
first_word == key
}
#[test]
fn test_sshd_config_exact_key_matching() {
assert!(sshd_line_match("Port 2222", "Port"));
assert!(sshd_line_match("PermitRootLogin no", "PermitRootLogin"));
assert!(sshd_line_match(
" PasswordAuthentication no",
"PasswordAuthentication"
));
assert!(
!sshd_line_match("PortNumberOfSomething 123", "Port"),
"Port 不应匹配 PortNumberOfSomething"
);
assert!(
!sshd_line_match("PermitRootLoginExtra yes", "PermitRootLogin"),
"PermitRootLogin 不应匹配 PermitRootLoginExtra"
);
assert!(!sshd_line_match("#Port 2222", "Port"));
assert!(!sshd_line_match("", "Port"));
}
#[test]
fn test_authorized_keys_append_not_overwrite() {
use std::io::Write;
let tmp = tempfile::NamedTempFile::new().expect("创建临时文件失败");
let path = tmp.path().to_str().unwrap().to_string();
if path.is_empty() {
panic!("临时文件路径为空");
}
{
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.unwrap();
writeln!(file, "ssh-ed25519 KEY1 user@host").unwrap();
}
{
let mut file = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&path)
.unwrap();
writeln!(file, "ssh-ed25519 KEY2 admin@host").unwrap();
}
let content = std::fs::read_to_string(&path).unwrap();
assert!(content.contains("KEY1"), "第一条密钥应保留");
assert!(content.contains("KEY2"), "第二条密钥应追加");
let lines: Vec<&str> = content.lines().collect();
assert_eq!(lines.len(), 2, "应有 2 行密钥: {:?}", lines);
}
#[test]
fn test_execute_params_debug_redacts_key() {
let params = ExecuteParams {
ssh_key_username: Some("admin".into()),
ssh_key_action: Some(SshKeyAction::PasteKey(
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK8O1oQK7Q8z6fVc9pZ3sX2yR4mW5jH0nBvLkPqR1sT"
.into(),
)),
..Default::default()
};
let debug_str = format!("{params:?}");
assert!(
debug_str.contains("[redacted]"),
"ExecuteParams Debug 应脱敏: {debug_str}"
);
assert!(
!debug_str.contains("AAAAC3NzaC1lZDI1NTE5AAAAIK8O1oQK7Q8z6fVc9pZ3sX2yR4mW5jH0nBvLkPqR1sT"),
"ExecuteParams Debug 不应泄露密钥: {debug_str}"
);
}