use std::fs;
use std::process::Command;
use tempfile::TempDir;
fn sniper() -> Command {
let mut cmd = Command::new("cargo");
cmd.args(["run", "--quiet", "--"]);
cmd
}
#[test]
#[cfg(unix)]
fn test_atomic_write_preserves_permissions() {
use std::os::unix::fs::PermissionsExt;
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("script.sh");
fs::write(&file_path, "echo 'hello'\n").unwrap();
let mut perms = fs::metadata(&file_path).unwrap().permissions();
perms.set_mode(0o755);
fs::set_permissions(&file_path, perms).unwrap();
let status = sniper()
.args([
file_path.to_str().unwrap(),
"1",
"1",
"6563686f2027776f726c6427",
])
.status()
.unwrap();
assert!(status.success());
assert_eq!(fs::read_to_string(&file_path).unwrap(), "echo 'world'\n");
let final_perms = fs::metadata(&file_path).unwrap().permissions();
assert_eq!(final_perms.mode() & 0o777, 0o755);
}
#[test]
fn test_multi_step_undo_stack() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("stack.txt");
fs::write(&file_path, "v0\n").unwrap();
for i in 1..=5 {
let hex = format!("{:02x}", i + 48); let status = sniper()
.args([file_path.to_str().unwrap(), "1", "1", &hex])
.status()
.unwrap();
assert!(status.success());
}
assert_eq!(fs::read_to_string(&file_path).unwrap(), "5\n");
for i in (0..5).rev() {
let status = sniper()
.args([file_path.to_str().unwrap(), "--undo"])
.status()
.unwrap();
assert!(status.success());
let expected = if i == 0 {
"v0\n".to_string()
} else {
format!("{}\n", i)
};
assert_eq!(fs::read_to_string(&file_path).unwrap(), expected);
}
let output = sniper()
.args([file_path.to_str().unwrap(), "--undo"])
.output()
.unwrap();
assert!(!output.status.success());
}
#[test]
fn test_path_normalization_consistency() {
let dir = TempDir::new().unwrap();
let sub = dir.path().join("sub");
fs::create_dir(&sub).unwrap();
let file_path = sub.join("norm.txt");
fs::write(&file_path, "orig\n").unwrap();
let status = sniper()
.args([file_path.to_str().unwrap(), "1", "1", "78"]) .status()
.unwrap();
assert!(status.success());
let status = sniper()
.args([file_path.to_str().unwrap(), "--undo"])
.status()
.unwrap();
assert!(status.success());
assert_eq!(fs::read_to_string(&file_path).unwrap(), "orig\n");
}
#[test]
fn test_encode_stdin_integrity() {
let input = "special: ' \" \\ \n \t \0";
let mut child = sniper()
.args(["encode", "--stdin"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap();
use std::io::Write;
let mut stdin = child
.stdin
.take()
.expect("stdin should be captured by Stdio::piped");
stdin.write_all(input.as_bytes()).unwrap();
drop(stdin);
let output = child.wait_with_output().unwrap();
let hex = String::from_utf8(output.stdout).unwrap().trim().to_string();
let _ = sniper()
.args(["encode", "--stdin"])
.stdin(std::process::Stdio::piped())
.status();
let expected_hex: String = input
.as_bytes()
.iter()
.map(|b| format!("{:02x}", b))
.collect();
assert_eq!(hex, expected_hex);
}
#[test]
fn test_splicing_boundaries() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("boundary.txt");
fs::write(&file_path, "line1\nline2\nline3\n").unwrap();
sniper()
.args([file_path.to_str().unwrap(), "1", "0", "610a"])
.status()
.unwrap(); assert_eq!(
fs::read_to_string(&file_path).unwrap(),
"a\nline1\nline2\nline3\n"
);
sniper()
.args([file_path.to_str().unwrap(), "5", "4", "7a"])
.status()
.unwrap(); assert_eq!(
fs::read_to_string(&file_path).unwrap(),
"a\nline1\nline2\nline3\nz\n"
); }
#[test]
fn test_unicode_payload() {
let dir = TempDir::new().unwrap();
let file_path = dir.path().join("unicode.txt");
fs::write(&file_path, "empty\n").unwrap();
sniper()
.args([file_path.to_str().unwrap(), "1", "1", "f09fa680"])
.status()
.unwrap();
assert_eq!(fs::read_to_string(&file_path).unwrap(), "🦀\n");
}
#[test]
fn test_concurrency_locking() {
use std::sync::{Arc, Barrier};
use std::thread;
let dir = Arc::new(TempDir::new().unwrap());
let file_path = Arc::new(dir.path().join("concurrent.txt"));
fs::write(&*file_path, "base\n").unwrap();
let num_threads = 5;
let barrier = Arc::new(Barrier::new(num_threads));
let mut handles = vec![];
for i in 0..num_threads {
let b = barrier.clone();
let f = file_path.clone();
handles.push(thread::spawn(move || {
b.wait();
let hex = format!("{:02x}", i + 65); let output = Command::new("cargo")
.args(["run", "--quiet", "--", f.to_str().unwrap(), "1", "1", &hex])
.output()
.unwrap();
output.status.success()
}));
}
let results: Vec<bool> = handles.into_iter().map(|h| h.join().unwrap()).collect();
assert!(results.iter().any(|&r| r));
let final_content = fs::read_to_string(&*file_path).unwrap();
let trimmed = final_content.trim();
assert_eq!(
trimmed.len(),
1,
"File must contain exactly one char after concurrent edit, got: {:?}",
trimmed
);
assert!(
trimmed.chars().next().unwrap().is_ascii_uppercase(),
"Content must be an uppercase letter (A-E), got: {:?}",
trimmed
);
}