use derusted::mitm::{
http_parser::{parse_http1_request, parse_http1_response},
log_storage::LogStorage,
logging::PiiRedactor,
};
use tempfile::TempDir;
#[tokio::test]
async fn test_http_parser_request_integration() {
let request_data =
b"GET /api/users?id=123 HTTP/1.1\r\nHost: example.com\r\nUser-Agent: Test\r\n\r\n";
let request = parse_http1_request(request_data).unwrap();
assert_eq!(request.method, "GET");
assert_eq!(request.path, "/api/users?id=123");
assert_eq!(request.version, "HTTP/1.1");
assert_eq!(request.headers.get("host").unwrap(), "example.com");
assert_eq!(request.headers.get("user-agent").unwrap(), "Test");
assert_eq!(request.content_length, None);
assert!(request.body_preview.is_empty());
}
#[tokio::test]
async fn test_http_parser_post_with_body() {
let request_data = b"POST /api/login HTTP/1.1\r\nHost: example.com\r\nContent-Type: application/json\r\nContent-Length: 27\r\n\r\n{\"user\":\"test\",\"pass\":\"secret\"}";
let request = parse_http1_request(request_data).unwrap();
assert_eq!(request.method, "POST");
assert_eq!(request.path, "/api/login");
assert_eq!(request.content_length, Some(27));
assert!(request.body_preview.contains("\"user\":\"test\""));
}
#[tokio::test]
async fn test_http_parser_response_integration() {
let response_data = b"HTTP/1.1 200 OK\r\nContent-Type: application/json\r\nContent-Length: 16\r\n\r\n{\"status\":\"ok\"}";
let response = parse_http1_response(response_data).unwrap();
assert_eq!(response.version, "HTTP/1.1");
assert_eq!(response.status_code, 200);
assert_eq!(response.reason, "OK");
assert_eq!(
response.headers.get("content-type").unwrap(),
"application/json"
);
assert_eq!(response.content_length, Some(16));
assert!(response.is_success());
assert!(!response.is_error());
}
#[tokio::test]
async fn test_binary_response_handling() {
let mut response_data =
b"HTTP/1.1 200 OK\r\nContent-Type: image/png\r\nContent-Length: 100\r\n\r\n".to_vec();
response_data.extend_from_slice(&[0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); response_data.extend_from_slice(&vec![0; 92]);
let response = parse_http1_response(&response_data).unwrap();
assert_eq!(response.status_code, 200);
assert_eq!(response.content_type().unwrap(), "image/png");
assert!(response.body_preview.contains("[Binary data:"));
}
#[tokio::test]
async fn test_pii_redaction_in_logging() {
let path_with_cc = "/payment?card=4532-1234-5678-9010&cvv=123";
let redacted = PiiRedactor::redact(path_with_cc);
assert!(redacted.contains("[REDACTED]"));
assert!(!redacted.contains("4532-1234-5678-9010"));
let path_with_email = "/user?email=john.doe@example.com&action=verify";
let redacted = PiiRedactor::redact(path_with_email);
assert!(redacted.contains("[REDACTED]"));
assert!(!redacted.contains("john.doe@example.com"));
let path_with_ssn = "/profile?ssn=123-45-6789&name=test";
let redacted = PiiRedactor::redact(path_with_ssn);
assert!(redacted.contains("[REDACTED]"));
assert!(!redacted.contains("123-45-6789"));
}
#[tokio::test]
async fn test_sqlite_logging_integration() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("test_requests.db");
let storage = LogStorage::new(db_path.to_str().unwrap()).await.unwrap();
let metadata = derusted::mitm::logging::RequestMetadata {
timestamp: chrono::Utc::now().timestamp(),
method: "GET".to_string(),
host: "example.com".to_string(),
port: 443,
path: "/api/test".to_string(),
http_version: "HTTP/1.1".to_string(),
status_code: Some(200),
request_size: 512,
response_size: 1024,
duration_ms: 150,
tls_version: Some("TLS 1.3".to_string()),
mitm_applied: true,
bypass_reason: None,
};
let log_id = storage.log_request(&metadata).await.unwrap();
assert!(log_id > 0);
let start_ts = chrono::Utc::now().timestamp() - 3600;
let end_ts = chrono::Utc::now().timestamp() + 3600;
let logs = storage.query_logs(start_ts, end_ts, 10).await.unwrap();
assert_eq!(logs.len(), 1);
assert_eq!(logs[0].method, "GET");
assert_eq!(logs[0].host, "example.com");
assert_eq!(logs[0].path, "/api/test");
assert_eq!(logs[0].status_code, Some(200));
let host_logs = storage.query_logs_by_host("example.com", 10).await.unwrap();
assert_eq!(host_logs.len(), 1);
let count = storage.count_logs().await.unwrap();
assert_eq!(count, 1);
}
#[tokio::test]
async fn test_logging_policy_sampling() {
let mut sampled_count = 0;
for _ in 0..100 {
if PiiRedactor::should_sample(0.0) {
sampled_count += 1;
}
}
assert_eq!(sampled_count, 0);
let mut sampled_count = 0;
for _ in 0..100 {
if PiiRedactor::should_sample(1.0) {
sampled_count += 1;
}
}
assert_eq!(sampled_count, 100);
let mut sampled_count = 0;
for _ in 0..1000 {
if PiiRedactor::should_sample(0.1) {
sampled_count += 1;
}
}
assert!(sampled_count >= 50);
assert!(sampled_count <= 150);
}
#[tokio::test]
async fn test_multiple_request_logging() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("multi_requests.db");
let storage = LogStorage::new(db_path.to_str().unwrap()).await.unwrap();
for i in 0..10 {
let metadata = derusted::mitm::logging::RequestMetadata {
timestamp: chrono::Utc::now().timestamp(),
method: "GET".to_string(),
host: format!("host{}.example.com", i),
port: 443,
path: format!("/api/endpoint{}", i),
http_version: "HTTP/1.1".to_string(),
status_code: Some(200 + i as u16),
request_size: 100 + i * 10,
response_size: 500 + i * 50,
duration_ms: 100 + (i * 10) as u64,
tls_version: Some("TLS 1.3".to_string()),
mitm_applied: true,
bypass_reason: None,
};
storage.log_request(&metadata).await.unwrap();
}
let count = storage.count_logs().await.unwrap();
assert_eq!(count, 10);
let start_ts = chrono::Utc::now().timestamp() - 3600;
let end_ts = chrono::Utc::now().timestamp() + 3600;
let logs = storage.query_logs(start_ts, end_ts, 5).await.unwrap();
assert_eq!(logs.len(), 5);
let host_logs = storage
.query_logs_by_host("host5.example.com", 10)
.await
.unwrap();
assert_eq!(host_logs.len(), 1);
assert_eq!(host_logs[0].path, "/api/endpoint5");
}
#[tokio::test]
async fn test_pii_redaction_multiple_patterns() {
let path =
"/checkout?card=4532-1234-5678-9010&email=user@test.com&phone=555-123-4567&ssn=123-45-6789";
let redacted = PiiRedactor::redact(path);
assert!(redacted.contains("[REDACTED]"));
assert!(!redacted.contains("4532-1234-5678-9010"));
assert!(!redacted.contains("user@test.com"));
assert!(!redacted.contains("555-123-4567"));
assert!(!redacted.contains("123-45-6789"));
assert!(redacted.contains("/checkout"));
assert!(redacted.contains("card="));
assert!(redacted.contains("email="));
}
#[tokio::test]
async fn test_database_cleanup() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("cleanup_test.db");
let storage = LogStorage::new(db_path.to_str().unwrap()).await.unwrap();
let old_metadata = derusted::mitm::logging::RequestMetadata {
timestamp: chrono::Utc::now().timestamp() - (365 * 86400),
method: "GET".to_string(),
host: "old.example.com".to_string(),
port: 443,
path: "/old".to_string(),
http_version: "HTTP/1.1".to_string(),
status_code: Some(200),
request_size: 100,
response_size: 500,
duration_ms: 100,
tls_version: Some("TLS 1.2".to_string()),
mitm_applied: true,
bypass_reason: None,
};
let recent_metadata = derusted::mitm::logging::RequestMetadata {
timestamp: chrono::Utc::now().timestamp(),
method: "GET".to_string(),
host: "new.example.com".to_string(),
port: 443,
path: "/new".to_string(),
http_version: "HTTP/1.1".to_string(),
status_code: Some(200),
request_size: 100,
response_size: 500,
duration_ms: 100,
tls_version: Some("TLS 1.3".to_string()),
mitm_applied: true,
bypass_reason: None,
};
storage.log_request(&old_metadata).await.unwrap();
storage.log_request(&recent_metadata).await.unwrap();
let count = storage.count_logs().await.unwrap();
assert_eq!(count, 2);
let deleted = storage.cleanup_old_logs(30).await.unwrap();
assert_eq!(deleted, 1);
let count = storage.count_logs().await.unwrap();
assert_eq!(count, 1);
storage.vacuum().await.unwrap();
}
#[tokio::test]
async fn test_error_response_logging() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("error_responses.db");
let storage = LogStorage::new(db_path.to_str().unwrap()).await.unwrap();
let metadata_404 = derusted::mitm::logging::RequestMetadata {
timestamp: chrono::Utc::now().timestamp(),
method: "GET".to_string(),
host: "example.com".to_string(),
port: 443,
path: "/not-found".to_string(),
http_version: "HTTP/1.1".to_string(),
status_code: Some(404),
request_size: 100,
response_size: 200,
duration_ms: 50,
tls_version: Some("TLS 1.3".to_string()),
mitm_applied: true,
bypass_reason: None,
};
let metadata_500 = derusted::mitm::logging::RequestMetadata {
timestamp: chrono::Utc::now().timestamp(),
method: "POST".to_string(),
host: "api.example.com".to_string(),
port: 443,
path: "/api/action".to_string(),
http_version: "HTTP/1.1".to_string(),
status_code: Some(500),
request_size: 500,
response_size: 300,
duration_ms: 200,
tls_version: Some("TLS 1.3".to_string()),
mitm_applied: true,
bypass_reason: None,
};
storage.log_request(&metadata_404).await.unwrap();
storage.log_request(&metadata_500).await.unwrap();
let count = storage.count_logs().await.unwrap();
assert_eq!(count, 2);
let start_ts = chrono::Utc::now().timestamp() - 3600;
let end_ts = chrono::Utc::now().timestamp() + 3600;
let logs = storage.query_logs(start_ts, end_ts, 10).await.unwrap();
assert_eq!(logs.len(), 2);
let status_codes: Vec<u16> = logs.iter().filter_map(|l| l.status_code).collect();
assert!(status_codes.contains(&404));
assert!(status_codes.contains(&500));
}