mod common;
use common::{create_git_repo, git_crypt_bin, git_crypt_cmd};
use std::fs;
use std::io::Write;
use std::process::{Command as StdCommand, Stdio};
#[test]
fn test_very_large_file_encryption() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let large_data = vec![0x42u8; 10 * 1024 * 1024];
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean
.stdin
.as_mut()
.unwrap()
.write_all(&large_data)
.unwrap();
let encrypted = clean.wait_with_output().unwrap();
assert!(encrypted.status.success());
let mut smudge = StdCommand::new(git_crypt_bin())
.arg("smudge")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
smudge
.stdin
.as_mut()
.unwrap()
.write_all(&encrypted.stdout)
.unwrap();
let decrypted = smudge.wait_with_output().unwrap();
assert!(decrypted.status.success());
assert_eq!(decrypted.stdout.len(), large_data.len());
}
#[test]
fn test_empty_file_encryption() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let empty_data = b"";
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean.stdin.as_mut().unwrap().write_all(empty_data).unwrap();
let encrypted = clean.wait_with_output().unwrap();
assert!(encrypted.status.success());
let mut smudge = StdCommand::new(git_crypt_bin())
.arg("smudge")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
smudge
.stdin
.as_mut()
.unwrap()
.write_all(&encrypted.stdout)
.unwrap();
let decrypted = smudge.wait_with_output().unwrap();
assert!(decrypted.status.success());
assert_eq!(&decrypted.stdout[..], empty_data);
}
#[test]
fn test_binary_file_with_null_bytes() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let binary_data: Vec<u8> = vec![0x00, 0xFF, 0x00, 0x42, 0x00, 0x00, 0xAA, 0xBB];
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean
.stdin
.as_mut()
.unwrap()
.write_all(&binary_data)
.unwrap();
let encrypted = clean.wait_with_output().unwrap();
assert!(encrypted.status.success());
let mut smudge = StdCommand::new(git_crypt_bin())
.arg("smudge")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
smudge
.stdin
.as_mut()
.unwrap()
.write_all(&encrypted.stdout)
.unwrap();
let decrypted = smudge.wait_with_output().unwrap();
assert!(decrypted.status.success());
assert_eq!(decrypted.stdout, binary_data);
}
#[test]
fn test_unicode_filenames_and_content() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let unicode_content = "Hello 世界! Emoji: 🔐🦀 Math: ∑∫∂ ".repeat(5);
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean
.stdin
.as_mut()
.unwrap()
.write_all(unicode_content.as_bytes())
.unwrap();
let encrypted = clean.wait_with_output().unwrap();
assert!(encrypted.status.success());
let mut smudge = StdCommand::new(git_crypt_bin())
.arg("smudge")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
smudge
.stdin
.as_mut()
.unwrap()
.write_all(&encrypted.stdout)
.unwrap();
let decrypted = smudge.wait_with_output().unwrap();
assert!(decrypted.status.success());
assert_eq!(
String::from_utf8(decrypted.stdout).unwrap(),
unicode_content
);
}
#[test]
fn test_corrupted_encrypted_data() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let plaintext = b"Secret message";
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean.stdin.as_mut().unwrap().write_all(plaintext).unwrap();
let encrypted = clean.wait_with_output().unwrap();
assert!(encrypted.status.success());
let mut corrupted = encrypted.stdout.clone();
if corrupted.len() > 15 {
corrupted[15] ^= 0xFF;
}
let mut smudge = StdCommand::new(git_crypt_bin())
.arg("smudge")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
smudge
.stdin
.as_mut()
.unwrap()
.write_all(&corrupted)
.unwrap();
let output = smudge.wait_with_output().unwrap();
assert!(!output.status.success());
}
#[test]
fn test_export_key_to_existing_file() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let key_file = temp.path().join("key.bin");
git_crypt_cmd()
.args(["export-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success();
let first_key = fs::read(&key_file).unwrap();
git_crypt_cmd()
.args(["export-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success();
let second_key = fs::read(&key_file).unwrap();
assert_eq!(first_key, second_key);
}
#[test]
fn test_invalid_command() {
git_crypt_cmd().arg("invalid-command").assert().failure();
}
#[test]
fn test_concurrent_operations() {
use std::thread;
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let temp_path = temp.path().to_path_buf();
let handles: Vec<_> = (0..5)
.map(|i| {
let path = temp_path.clone();
thread::spawn(move || {
let data = format!("Thread {} data", i);
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(&path)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean
.stdin
.as_mut()
.unwrap()
.write_all(data.as_bytes())
.unwrap();
let output = clean.wait_with_output().unwrap();
assert!(output.status.success());
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
}
#[test]
fn test_key_with_directory_permissions() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let git_crypt_dir = temp.path().join(".git/git-crypt");
let keys_dir = git_crypt_dir.join("keys");
assert!(git_crypt_dir.is_dir());
assert!(keys_dir.is_dir());
}
#[test]
fn test_lock_unlock_idempotent() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
for _ in 0..3 {
git_crypt_cmd()
.arg("lock")
.current_dir(temp.path())
.assert()
.success();
}
for _ in 0..3 {
git_crypt_cmd()
.arg("unlock")
.current_dir(temp.path())
.assert()
.success();
}
}
#[test]
fn test_special_characters_in_data() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let special_data = b"Line1\nLine2\r\nTab\there\0null\x01\x02\x03\xFF";
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean
.stdin
.as_mut()
.unwrap()
.write_all(special_data)
.unwrap();
let encrypted = clean.wait_with_output().unwrap();
assert!(encrypted.status.success());
let mut smudge = StdCommand::new(git_crypt_bin())
.arg("smudge")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
smudge
.stdin
.as_mut()
.unwrap()
.write_all(&encrypted.stdout)
.unwrap();
let decrypted = smudge.wait_with_output().unwrap();
assert!(decrypted.status.success());
assert_eq!(&decrypted.stdout[..], special_data);
}
#[test]
fn test_repeated_key_operations() {
let temp = create_git_repo();
git_crypt_cmd()
.arg("init")
.current_dir(temp.path())
.assert()
.success();
let key_file = temp.path().join("key.bin");
for _ in 0..5 {
git_crypt_cmd()
.args(["export-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success();
git_crypt_cmd()
.args(["import-key", key_file.to_str().unwrap()])
.current_dir(temp.path())
.assert()
.success();
}
let plaintext = b"Test after repeated operations";
let mut clean = StdCommand::new(git_crypt_bin())
.arg("clean")
.current_dir(temp.path())
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.unwrap();
clean.stdin.as_mut().unwrap().write_all(plaintext).unwrap();
let encrypted = clean.wait_with_output().unwrap();
assert!(encrypted.status.success());
}