app / src-tauri /src /proxy /tests /security_integration_tests.rs
AZILS's picture
Upload 323 files
a21c316 verified
//! IP Security Integration Tests
//! IP 安全功能的集成测试
//!
//! 这些测试需要启动完整的代理服务器来验证端到端的功能
#[cfg(test)]
mod integration_tests {
use crate::modules::security_db::{
self, init_db, add_to_blacklist, remove_from_blacklist,
add_to_whitelist, remove_from_whitelist, get_blacklist, get_whitelist,
};
use std::time::Duration;
/// 辅助函数:清理测试环境
fn cleanup_test_data() {
if let Ok(entries) = get_blacklist() {
for entry in entries {
let _ = remove_from_blacklist(&entry.id);
}
}
if let Ok(entries) = get_whitelist() {
for entry in entries {
let _ = remove_from_whitelist(&entry.id);
}
}
}
// ============================================================================
// 集成测试场景 1:黑名单阻止请求
// ============================================================================
/// 测试场景:当 IP 在黑名单中时,请求应该被拒绝
///
/// 预期行为:
/// 1. 添加 IP 到黑名单
/// 2. 该 IP 发起的请求返回 403 Forbidden
/// 3. 响应体包含封禁原因
#[test]
fn test_scenario_blacklist_blocks_request() {
let _ = init_db();
cleanup_test_data();
// 添加测试 IP 到黑名单
let entry = add_to_blacklist(
"192.168.100.100",
Some("Integration test - malicious activity"),
None,
"integration_test",
);
assert!(entry.is_ok(), "Should add IP to blacklist");
// 验证黑名单条目存在
let blacklist = get_blacklist().unwrap();
let found = blacklist.iter().any(|e| e.ip_pattern == "192.168.100.100");
assert!(found, "IP should be in blacklist");
// 实际的 HTTP 请求测试需要启动服务器
// 这里验证数据层正确性
let is_blocked = security_db::is_ip_in_blacklist("192.168.100.100").unwrap();
assert!(is_blocked, "IP should be blocked");
cleanup_test_data();
}
// ============================================================================
// 集成测试场景 2:白名单优先模式
// ============================================================================
/// 测试场景:白名单优先模式下,白名单 IP 跳过黑名单检查
///
/// 预期行为:
/// 1. IP 同时存在于黑名单和白名单
/// 2. 启用 whitelist_priority 模式
/// 3. 请求应该被允许(白名单优先)
#[test]
fn test_scenario_whitelist_priority() {
let _ = init_db();
cleanup_test_data();
// 添加 IP 到黑名单
let _ = add_to_blacklist(
"10.0.0.50",
Some("Should be overridden by whitelist"),
None,
"test",
);
// 添加相同 IP 到白名单
let _ = add_to_whitelist(
"10.0.0.50",
Some("Trusted - override blacklist"),
);
// 验证两个列表都包含该 IP
assert!(security_db::is_ip_in_blacklist("10.0.0.50").unwrap());
assert!(security_db::is_ip_in_whitelist("10.0.0.50").unwrap());
// 在实际中间件中,whitelist_priority=true 时,会先检查白名单
// 如果在白名单中,则跳过黑名单检查
// 这里只验证数据正确性,中间件逻辑由 ip_filter.rs 保证
cleanup_test_data();
}
// ============================================================================
// 集成测试场景 3:临时封禁与过期
// ============================================================================
/// 测试场景:临时封禁在过期后自动解除
///
/// 预期行为:
/// 1. 添加临时封禁(已过期)
/// 2. 查询时自动清理过期条目
/// 3. 请求应该被允许
#[test]
fn test_scenario_temporary_ban_expiration() {
let _ = init_db();
cleanup_test_data();
// 获取当前时间戳
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
// 添加已过期的临时封禁
let _ = add_to_blacklist(
"expired.ban.test",
Some("Temporary ban - should be expired"),
Some(now - 60), // 1分钟前过期
"test",
);
// 查询时应该触发过期清理
let is_blocked = security_db::is_ip_in_blacklist("expired.ban.test").unwrap();
assert!(!is_blocked, "Expired ban should not block");
cleanup_test_data();
}
// ============================================================================
// 集成测试场景 4:CIDR 范围封禁
// ============================================================================
/// 测试场景:CIDR 范围封禁覆盖整个子网
///
/// 预期行为:
/// 1. 封禁 192.168.1.0/24
/// 2. 192.168.1.x 的所有请求被拒绝
/// 3. 192.168.2.x 的请求正常通过
#[test]
fn test_scenario_cidr_subnet_blocking() {
let _ = init_db();
cleanup_test_data();
// 封禁整个子网
let _ = add_to_blacklist(
"192.168.1.0/24",
Some("Entire subnet blocked"),
None,
"test",
);
// 验证子网内的 IP 被阻止
for last_octet in [1, 50, 100, 200, 254] {
let ip = format!("192.168.1.{}", last_octet);
let is_blocked = security_db::is_ip_in_blacklist(&ip).unwrap();
assert!(is_blocked, "IP {} should be blocked by CIDR", ip);
}
// 验证子网外的 IP 不被阻止
for last_octet in [1, 50, 100] {
let ip = format!("192.168.2.{}", last_octet);
let is_blocked = security_db::is_ip_in_blacklist(&ip).unwrap();
assert!(!is_blocked, "IP {} should NOT be blocked", ip);
}
cleanup_test_data();
}
// ============================================================================
// 集成测试场景 5:封禁消息详情
// ============================================================================
/// 测试场景:封禁响应包含详细信息
///
/// 预期行为:
/// 1. 添加带原因的封禁
/// 2. 请求被拒绝时,响应包含:
/// - 封禁原因
/// - 是否为临时/永久封禁
/// - 剩余封禁时间(如果是临时)
#[test]
fn test_scenario_ban_message_details() {
let _ = init_db();
cleanup_test_data();
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
// 添加临时封禁(2小时后过期)
let _ = add_to_blacklist(
"temp.ban.message",
Some("Rate limit exceeded"),
Some(now + 7200), // 2小时后
"rate_limiter",
);
// 获取封禁详情
let entry = security_db::get_blacklist_entry_for_ip("temp.ban.message")
.unwrap()
.unwrap();
assert_eq!(entry.reason.as_deref(), Some("Rate limit exceeded"));
assert!(entry.expires_at.is_some());
let remaining = entry.expires_at.unwrap() - now;
assert!(remaining > 0 && remaining <= 7200, "Should have ~2h remaining");
cleanup_test_data();
}
// ============================================================================
// 集成测试场景 6:访问日志记录
// ============================================================================
/// 测试场景:被阻止的请求记录到日志
///
/// 预期行为:
/// 1. 黑名单 IP 发起请求
/// 2. 请求被拒绝
/// 3. 访问日志记录:IP、时间、状态(403)、封禁原因
#[test]
fn test_scenario_blocked_request_logging() {
let _ = init_db();
cleanup_test_data();
// 模拟保存被阻止的访问日志
let log = security_db::IpAccessLog {
id: uuid::Uuid::new_v4().to_string(),
client_ip: "blocked.request.test".to_string(),
timestamp: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64,
method: Some("POST".to_string()),
path: Some("/v1/messages".to_string()),
user_agent: Some("TestClient/1.0".to_string()),
status: Some(403),
duration: Some(0),
api_key_hash: None,
blocked: true,
block_reason: Some("IP in blacklist".to_string()),
username: None,
};
let save_result = security_db::save_ip_access_log(&log);
assert!(save_result.is_ok());
// 验证日志可以检索
let logs = security_db::get_ip_access_logs(10, 0, None, true).unwrap();
let found = logs.iter().any(|l| l.client_ip == "blocked.request.test");
assert!(found, "Blocked request should be logged");
let _ = security_db::clear_ip_access_logs();
}
// ============================================================================
// 集成测试场景 7:不影响正常请求性能
// ============================================================================
/// 测试场景:安全检查不显著影响正常请求性能
///
/// 预期行为:
/// 1. 黑名单/白名单检查时间 < 5ms
/// 2. 与没有安全检查的基线相比,延迟增加 < 10ms
#[test]
fn test_scenario_performance_impact() {
let _ = init_db();
cleanup_test_data();
// 添加一些黑名单条目
for i in 0..50 {
let _ = add_to_blacklist(&format!("perf.test.{}", i), None, None, "test");
}
// 添加一些 CIDR 规则
for i in 0..10 {
let _ = add_to_blacklist(&format!("172.{}.0.0/16", i), None, None, "test");
}
// 测试查找性能
let start = std::time::Instant::now();
let iterations = 100;
for _ in 0..iterations {
// 模拟正常请求的安全检查
let _ = security_db::is_ip_in_whitelist("10.0.0.1");
let _ = security_db::is_ip_in_blacklist("10.0.0.1");
}
let duration = start.elapsed();
let avg_per_check = duration / (iterations * 2);
println!("Average security check time: {:?}", avg_per_check);
// 断言:平均每次检查应该在 5ms 以内
assert!(
avg_per_check < Duration::from_millis(5),
"Security check should be fast"
);
cleanup_test_data();
}
// ============================================================================
// 集成测试场景 8:数据持久化
// ============================================================================
/// 测试场景:黑名单/白名单数据持久化
///
/// 预期行为:
/// 1. 添加数据后重新初始化数据库连接
/// 2. 数据仍然存在
#[test]
fn test_scenario_data_persistence() {
let _ = init_db();
cleanup_test_data();
// 添加数据
let _ = add_to_blacklist("persist.test.ip", Some("Persistence test"), None, "test");
let _ = add_to_whitelist("persist.white.ip", Some("Persistence test"));
// 重新初始化(实际上只是验证数据仍然可读)
let _ = init_db();
// 验证数据仍然存在
assert!(security_db::is_ip_in_blacklist("persist.test.ip").unwrap());
assert!(security_db::is_ip_in_whitelist("persist.white.ip").unwrap());
cleanup_test_data();
}
}
// ============================================================================
// 压力测试
// ============================================================================
#[cfg(test)]
mod stress_tests {
use crate::modules::security_db::{
init_db, add_to_blacklist, remove_from_blacklist,
is_ip_in_blacklist, get_blacklist, save_ip_access_log,
IpAccessLog, clear_ip_access_logs,
};
use std::thread;
use std::time::{Duration, Instant};
/// 辅助函数:清理测试环境
fn cleanup_test_data() {
if let Ok(entries) = get_blacklist() {
for entry in entries {
let _ = remove_from_blacklist(&entry.id);
}
}
let _ = clear_ip_access_logs();
}
/// 压力测试:大量黑名单条目
#[test]
fn stress_test_large_blacklist() {
let _ = init_db();
cleanup_test_data();
let count = 500;
// 批量添加
let start = Instant::now();
for i in 0..count {
let _ = add_to_blacklist(&format!("stress.{}.{}.{}.{}", i/256, (i/16)%16, i%16, i), None, None, "stress");
}
let add_duration = start.elapsed();
println!("Added {} entries in {:?}", count, add_duration);
// 随机查找测试
let start = Instant::now();
for i in 0..100 {
let _ = is_ip_in_blacklist(&format!("stress.{}.{}.{}.{}", i/256, (i/16)%16, i%16, i));
}
let lookup_duration = start.elapsed();
println!("100 lookups in large blacklist took {:?}", lookup_duration);
// 验证性能合理
assert!(
lookup_duration < Duration::from_secs(1),
"Lookups should be reasonably fast even with large blacklist"
);
cleanup_test_data();
}
/// 压力测试:大量访问日志
#[test]
fn stress_test_access_logging() {
let _ = init_db();
let _ = clear_ip_access_logs();
let count = 1000;
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
// 批量写入日志
let start = Instant::now();
for i in 0..count {
let log = IpAccessLog {
id: uuid::Uuid::new_v4().to_string(),
client_ip: format!("log.stress.{}", i % 100),
timestamp: now,
method: Some("POST".to_string()),
path: Some("/v1/messages".to_string()),
user_agent: Some("StressTest/1.0".to_string()),
status: Some(200),
duration: Some(100),
api_key_hash: Some("hash".to_string()),
blocked: false,
block_reason: None,
username: None,
};
let _ = save_ip_access_log(&log);
}
let write_duration = start.elapsed();
println!("Wrote {} access logs in {:?}", count, write_duration);
// 验证写入性能合理
assert!(
write_duration < Duration::from_secs(10),
"Access log writing should be reasonably fast"
);
let _ = clear_ip_access_logs();
}
/// 压力测试:并发操作
#[test]
fn stress_test_concurrent_operations() {
let _ = init_db();
cleanup_test_data();
let thread_count = 5;
let ops_per_thread = 20;
let handles: Vec<_> = (0..thread_count)
.map(|t| {
thread::spawn(move || {
for i in 0..ops_per_thread {
// 每个线程添加-查询-删除
let ip = format!("concurrent.{}.{}", t, i);
if let Ok(entry) = add_to_blacklist(&ip, None, None, "concurrent") {
let _ = is_ip_in_blacklist(&ip);
let _ = remove_from_blacklist(&entry.id);
}
}
})
})
.collect();
// 等待所有线程完成
for handle in handles {
handle.join().expect("Thread should not panic");
}
// 验证没有遗留数据
let remaining = get_blacklist().unwrap();
let concurrent_remaining: Vec<_> = remaining
.iter()
.filter(|e| e.ip_pattern.starts_with("concurrent."))
.collect();
assert!(
concurrent_remaining.is_empty(),
"All concurrent test data should be cleaned up"
);
cleanup_test_data();
}
}