use crate::mocks::helpers::{TestConfigGuard, encrypted_path_for};
use assert_cmd::assert::{Assert, OutputAssertExt};
use assert_cmd::cargo::CommandCargoExt;
use std::fs;
use std::process::Command;
use tempfile::NamedTempFile;
fn run_cli_encrypt(
path: &std::path::Path,
password: Option<&str>,
) -> Result<Assert, Box<dyn std::error::Error>> {
let mut cmd = Command::cargo_bin("eck")?;
if let Some(pwd) = password {
cmd.arg("-p").arg(pwd);
}
cmd.arg(path);
Ok(cmd.assert())
}
fn run_cli_decrypt(
path: &std::path::Path,
password: Option<&str>,
) -> Result<Assert, Box<dyn std::error::Error>> {
run_cli_encrypt(path, password)
}
fn stdout_of(assertion: &Assert) -> String {
String::from_utf8_lossy(&assertion.get_output().stdout).into_owned()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn encrypt_decrypt_password_keytype_no_comp_roundtrip() {
let _guard = TestConfigGuard::new("PassWord", "NoComp");
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"Testing full workflow without compression.").unwrap();
let encrypted_path = encrypted_path_for(original.path());
run_cli_encrypt(original.path(), Some("test123"))
.unwrap()
.success();
assert!(encrypted_path.exists());
run_cli_decrypt(encrypted_path.as_ref(), Some("test123"))
.unwrap()
.success();
let decrypted = fs::read_to_string(original.path()).unwrap();
assert_eq!(decrypted, "Testing full workflow without compression.");
}
#[test]
fn encrypt_decrypt_empty_file() {
let _guard = TestConfigGuard::new("PassWord", "Zstd");
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"").unwrap();
run_cli_encrypt(original.path(), Some("test123"))
.unwrap()
.success();
let encrypted_path = encrypted_path_for(original.path());
assert!(encrypted_path.exists());
run_cli_decrypt(encrypted_path.as_ref(), Some("test123"))
.unwrap()
.success();
assert_eq!(fs::metadata(original.path()).unwrap().len(), 0);
}
#[test]
fn encrypt_decrypt_password_keytype_zstd_roundtrip() {
let _guard = TestConfigGuard::new("PassWord", "Zstd");
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"Testing full workflow with Zstd compression.").unwrap();
let encrypted_path = encrypted_path_for(original.path());
run_cli_encrypt(original.path(), Some("test123"))
.unwrap()
.success();
assert!(encrypted_path.exists());
run_cli_decrypt(encrypted_path.as_ref(), Some("test123"))
.unwrap()
.success();
let decrypted = fs::read_to_string(original.path()).unwrap();
assert_eq!(decrypted, "Testing full workflow with Zstd compression.");
}
#[test]
fn encrypt_decrypt_password_keytype_lz4_roundtrip() {
let _guard = TestConfigGuard::new("PassWord", "Lz4");
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"Testing full workflow with Lz4 compression.").unwrap();
let encrypted_path = encrypted_path_for(original.path());
run_cli_encrypt(original.path(), Some("test123"))
.unwrap()
.success();
assert!(encrypted_path.exists());
run_cli_decrypt(encrypted_path.as_ref(), Some("test123"))
.unwrap()
.success();
let decrypted = fs::read_to_string(original.path()).unwrap();
assert_eq!(decrypted, "Testing full workflow with Lz4 compression.");
}
#[test]
fn encrypt_decrypt_password_keytype_xz_roundtrip() {
let _guard = TestConfigGuard::new("PassWord", "Xz");
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"XZ compression with full workflow validation.").unwrap();
let encrypted_path = encrypted_path_for(original.path());
run_cli_encrypt(original.path(), Some("test123"))
.unwrap()
.success();
assert!(encrypted_path.exists());
run_cli_decrypt(encrypted_path.as_ref(), Some("test123"))
.unwrap()
.success();
let decrypted = fs::read_to_string(original.path()).unwrap();
assert_eq!(decrypted, "XZ compression with full workflow validation.");
}
#[test]
#[ignore = "OS keyring requires manual testing - see TODO below"]
fn encrypt_decrypt_os_keytype_roundtrip() {
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"OS credential store encryption test.").unwrap();
assert!(original.path().exists());
}
#[test]
fn encrypt_decrypt_large_file() {
let _guard = TestConfigGuard::new("PassWord", "Zstd");
let original = NamedTempFile::new().unwrap();
let large_content = vec![b'x'; 20 * 1024 * 1024];
fs::write(&original, &large_content).unwrap();
run_cli_encrypt(original.path(), Some("test123"))
.unwrap()
.success();
let encrypted_path = encrypted_path_for(original.path());
assert!(encrypted_path.exists());
run_cli_decrypt(encrypted_path.as_ref(), Some("test123"))
.unwrap()
.success();
let decrypted = fs::read(original.path()).unwrap();
assert_eq!(decrypted.len(), large_content.len());
assert_eq!(decrypted, large_content);
}
#[test]
fn wrong_password_decryption_fails_cleanly() {
let _guard = TestConfigGuard::new("PassWord", "NoComp");
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"secret content for wrong-password test").unwrap();
run_cli_encrypt(original.path(), Some("right-pass"))
.unwrap()
.success();
let encrypted_path = encrypted_path_for(original.path());
let archive_before = fs::read(&encrypted_path).unwrap();
let wrong = run_cli_decrypt(encrypted_path.as_ref(), Some("wrong-pass")).unwrap();
let stdout = stdout_of(&wrong);
assert!(
stdout.contains("crypto::encryption_decryption_failed"),
"stdout: {stdout}"
);
assert!(encrypted_path.exists());
assert_eq!(fs::read(&encrypted_path).unwrap(), archive_before);
assert_ne!(
fs::read(original.path()).unwrap_or_default(),
b"secret content for wrong-password test".to_vec()
);
}
#[test]
fn tampered_archive_is_detected() {
let _guard = TestConfigGuard::new("PassWord", "NoComp");
let original = NamedTempFile::new().unwrap();
fs::write(&original, b"integrity protected content").unwrap();
run_cli_encrypt(original.path(), Some("tamper-test"))
.unwrap()
.success();
let encrypted_path = encrypted_path_for(original.path());
let mut archive = fs::read(&encrypted_path).unwrap();
let last = archive.len() - 8;
archive[last] ^= 0xFF;
let tampered_archive = archive.clone();
fs::write(&encrypted_path, &tampered_archive).unwrap();
let result = run_cli_decrypt(encrypted_path.as_ref(), Some("tamper-test")).unwrap();
let stdout = stdout_of(&result);
assert!(
stdout.contains("crypto::encryption_decryption_failed"),
"tampering must produce an error message"
);
assert_eq!(fs::read(&encrypted_path).unwrap(), tampered_archive);
assert_ne!(
fs::read(original.path()).unwrap_or_default(),
b"integrity protected content".to_vec()
);
}
}