#[cfg(test)]
mod encryption_file_test {
use inklog::sink::encryption::{Encryptor, EncryptionKey};
use inklog::{FileSinkConfig, InklogConfig, LoggerManager};
use std::fs::{self, File};
use std::io::{Read, Write};
use std::path::PathBuf;
use tempfile::TempDir;
fn generate_test_key() -> String {
"MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=".to_string()
}
fn create_test_dir() -> (TempDir, PathBuf) {
let temp_dir = TempDir::new().unwrap();
let log_path = temp_dir.path().join("encrypted_test.log.enc");
(temp_dir, log_path)
}
#[tokio::test]
async fn test_encryptor_initialization() {
let key_base64 = generate_test_key();
let encryptor = Encryptor::new(&key_base64).unwrap();
assert!(encryptor.is_initialized());
}
#[tokio::test]
async fn test_encryptor_invalid_key_length() {
let invalid_key = "short_key".to_string();
let result = Encryptor::new(&invalid_key);
assert!(result.is_err());
if let Err(e) = result {
assert!(e.to_string().contains("invalid") || e.to_string().contains("length"));
}
}
#[tokio::test]
async fn test_encryptor_invalid_base64() {
let invalid_base64 = "!!!invalid-base64!!!".to_string();
let result = Encryptor::new(&invalid_base64);
assert!(result.is_err());
}
#[tokio::test]
async fn test_encryptor_encrypt_decrypt() {
let key_base64 = generate_test_key();
let encryptor = Encryptor::new(&key_base64).unwrap();
let plaintext = b"Test message for encryption";
let nonce = encryptor.generate_nonce();
let ciphertext = encryptor.encrypt(plaintext, &nonce).unwrap();
let decrypted = encryptor.decrypt(&ciphertext, &nonce).unwrap();
assert_eq!(plaintext.to_vec(), decrypted);
}
#[tokio::test]
async fn test_encryptor_empty_message() {
let key_base64 = generate_test_key();
let encryptor = Encryptor::new(&key_base64).unwrap();
let plaintext = b"";
let nonce = encryptor.generate_nonce();
let ciphertext = encryptor.encrypt(plaintext, &nonce).unwrap();
let decrypted = encryptor.decrypt(&ciphertext, &nonce).unwrap();
assert_eq!(plaintext.to_vec(), decrypted);
}
#[tokio::test]
async fn test_encryptor_large_message() {
let key_base64 = generate_test_key();
let encryptor = Encryptor::new(&key_base64).unwrap();
let plaintext = vec![0u8; 1024 * 1024];
let nonce = encryptor.generate_nonce();
let ciphertext = encryptor.encrypt(&plaintext, &nonce).unwrap();
let decrypted = encryptor.decrypt(&ciphertext, &nonce).unwrap();
assert_eq!(plaintext, decrypted);
}
#[tokio::test]
async fn test_encryptor_tampered_ciphertext() {
let key_base64 = generate_test_key();
let encryptor = Encryptor::new(&key_base64).unwrap();
let plaintext = b"Secret message";
let nonce = encryptor.generate_nonce();
let ciphertext = encryptor.encrypt(plaintext, &nonce).unwrap();
let mut tampered = ciphertext.to_vec();
if tampered.len() > 10 {
tampered[10] ^= 0xFF;
}
let result = encryptor.decrypt(&tampered, &nonce);
assert!(result.is_err());
}
#[tokio::test]
async fn test_encryptor_wrong_nonce() {
let key_base64 = generate_test_key();
let encryptor = Encryptor::new(&key_base64).unwrap();
let plaintext = b"Secret message";
let nonce1 = encryptor.generate_nonce();
let nonce2 = encryptor.generate_nonce();
let ciphertext = encryptor.encrypt(plaintext, &nonce1).unwrap();
let result = encryptor.decrypt(&ciphertext, &nonce2);
assert!(result.is_err());
}
#[tokio::test]
async fn test_encryption_key_from_env() {
std::env::set_var("INKLOG_TEST_ENCRYPTION_KEY", generate_test_key());
let key_env = std::env::var("INKLOG_TEST_ENCRYPTION_KEY").unwrap();
let encryptor = Encryptor::new(&key_env).unwrap();
assert!(encryptor.is_initialized());
std::env::remove_var("INKLOG_TEST_ENCRYPTION_KEY");
}
#[tokio::test]
async fn test_encryption_key_memory_protection() {
use inklog::archive::SecretString;
let secret = SecretString::new("sensitive_key_data".to_string());
let key = secret.as_str_safe().unwrap();
assert_eq!(key, "sensitive_key_data");
assert!(secret.is_some());
let taken = secret.take();
assert_eq!(taken, Some("sensitive_key_data".to_string()));
assert!(secret.is_none());
}
#[tokio::test]
async fn test_encrypted_file_sink_write() {
let (_temp_dir, log_path) = create_test_dir();
std::env::set_var("INKLOG_ENCRYPTION_KEY", generate_test_key());
let file_config = FileSinkConfig {
enabled: true,
path: log_path.clone(),
max_size: "1MB".into(),
rotation_time: "daily".into(),
keep_files: 5,
compress: false, encrypt: true,
encryption_key_env: Some("INKLOG_ENCRYPTION_KEY".into()),
..Default::default()
};
let config = InklogConfig {
file_sink: Some(file_config),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
log::info!("Encrypted test message 1");
log::info!("Encrypted test message 2");
log::info!("Sensitive data: secret_value_12345");
tokio::time::sleep(tokio::time::Duration::from_millis(200)).await;
assert!(log_path.exists());
let metadata = fs::metadata(&log_path).unwrap();
assert!(metadata.len() > 0);
let mut file = File::open(&log_path).unwrap();
let mut content = String::new();
file.read_to_string(&mut content).unwrap();
assert!(!content.contains("Encrypted test message"));
assert!(!content.contains("secret_value_12345"));
std::env::remove_var("INKLOG_ENCRYPTION_KEY");
}
#[tokio::test]
async fn test_encrypted_file_sink_rotation() {
let (temp_dir, log_path) = create_test_dir();
std::env::set_var("INKLOG_ENCRYPTION_KEY", generate_test_key());
let file_config = FileSinkConfig {
enabled: true,
path: log_path.clone(),
max_size: "1KB".into(), rotation_time: "daily".into(),
keep_files: 3,
compress: false,
encrypt: true,
encryption_key_env: Some("INKLOG_ENCRYPTION_KEY".into()),
..Default::default()
};
let config = InklogConfig {
file_sink: Some(file_config),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: false,
..Default::default()
}),
..Default::default()
};
let _logger = LoggerManager::with_config(config).await.unwrap();
for i in 0..100 {
log::info!("Encrypted rotation test message #{}", i);
}
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
let parent = log_path.parent().unwrap();
let entries = fs::read_dir(parent).unwrap();
let count = entries.filter(|e| {
if let Ok(e) = e {
let path = e.path();
path.extension().map(|e| e.to_string_lossy() == "enc").unwrap_or(false)
|| path.file_stem().map(|s| s.to_string_lossy().contains("encrypted_test"))
.unwrap_or(false)
} else {
false
}
}).count();
assert!(log_path.exists() || count > 0);
std::env::remove_var("INKLOG_ENCRYPTION_KEY");
}
#[tokio::test]
async fn test_encrypted_sink_without_key_fails() {
let (_temp_dir, log_path) = create_test_dir();
std::env::remove_var("INKLOG_ENCRYPTION_KEY");
let file_config = FileSinkConfig {
enabled: true,
path: log_path,
max_size: "1MB".into(),
encrypt: true,
encryption_key_env: Some("INKLOG_ENCRYPTION_KEY".into()),
..Default::default()
};
let config = InklogConfig {
file_sink: Some(file_config),
console_sink: Some(inklog::config::ConsoleSinkConfig {
enabled: true,
..Default::default()
}),
..Default::default()
};
let result = LoggerManager::with_config(config).await;
assert!(result.is_ok() || result.is_err());
}
#[test]
fn test_encryption_config_validation() {
let valid_config = FileSinkConfig {
enabled: true,
path: PathBuf::from("/tmp/test.log.enc"),
encrypt: true,
encryption_key_env: Some("INKLOG_KEY".into()),
compress: false,
..Default::default()
};
assert!(valid_config.enabled);
assert!(valid_config.encrypt);
let invalid_config = FileSinkConfig {
enabled: true,
path: PathBuf::from("/tmp/test.log.enc"),
encrypt: true,
compression_level: 3,
..Default::default()
};
}
#[tokio::test]
async fn test_encryption_performance() {
use std::time::Instant;
let key_base64 = generate_test_key();
let encryptor = Encryptor::new(&key_base64).unwrap();
let iterations = 1000;
let start = Instant::now();
for _ in 0..iterations {
let plaintext = b"Short test message";
let nonce = encryptor.generate_nonce();
let _ciphertext = encryptor.encrypt(plaintext, &nonce).unwrap();
}
let elapsed = start.elapsed();
assert!(elapsed.as_secs() < 5, "Encryption too slow: {:?}", elapsed);
let large_plaintext = vec![0u8; 1024 * 100]; let start = Instant::now();
for _ in 0..10 {
let nonce = encryptor.generate_nonce();
let _ciphertext = encryptor.encrypt(&large_plaintext, &nonce).unwrap();
}
let elapsed = start.elapsed();
assert!(elapsed.as_secs() < 2, "Large encryption too slow: {:?}", elapsed);
}
#[tokio::test]
async fn test_concurrent_encryption() {
use inklog::tokio::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
let key_base64 = generate_test_key();
let encryptor = Arc::new(Encryptor::new(&key_base64).unwrap());
let counter = Arc::new(AtomicUsize::new(0));
let plaintext = b"Concurrent test message";
let tasks: Vec<_> = (0..10)
.map(|_| {
let encryptor = encryptor.clone();
let counter = counter.clone();
tokio::spawn(async move {
for _ in 0..100 {
let nonce = encryptor.generate_nonce();
let ciphertext = encryptor.encrypt(plaintext, &nonce).unwrap();
let decrypted = encryptor.decrypt(&ciphertext, &nonce).unwrap();
assert_eq!(plaintext.to_vec(), decrypted);
counter.fetch_add(1, Ordering::SeqCst);
}
})
})
.collect();
for task in tasks {
task.await.unwrap();
}
assert_eq!(counter.load(Ordering::SeqCst), 1000);
}
}