use super::*;
#[test]
fn test_replace_json_no_match_emits_valid_json() {
let dir = TempDir::new().unwrap();
fs::write(dir.path().join("f.txt"), "hello\n").unwrap();
let output = Command::cargo_bin("patchloom")
.unwrap()
.arg("--json")
.arg("replace")
.arg("zzz_no_match_zzz")
.arg("--to")
.arg("replacement")
.arg(dir.path())
.output()
.unwrap();
assert_eq!(output.status.code(), Some(3), "should exit 3 (no matches)");
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap();
assert_eq!(parsed["ok"], true);
assert_eq!(parsed["match_count"], 0);
assert_eq!(parsed["file_count"], 0);
assert!(parsed["files"].as_array().unwrap().is_empty());
}
#[test]
fn test_replace_apply_modifies_file() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "old_text content\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("old_text")
.arg("--to")
.arg("new_text")
.arg(&file)
.arg("--apply")
.assert()
.code(0);
let content = fs::read_to_string(&file).unwrap();
assert!(content.contains("new_text"), "file should contain new_text");
assert!(
!content.contains("old_text"),
"file should not contain old_text"
);
}
#[test]
fn test_replace_dry_run_does_not_modify_file() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "old_text content\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("old_text")
.arg("--to")
.arg("new_text")
.arg(&file)
.assert()
.code(0);
let content = fs::read_to_string(&file).unwrap();
assert!(
content.contains("old_text"),
"file should be unchanged in dry-run mode"
);
assert!(
!content.contains("new_text"),
"file should not be modified in dry-run mode"
);
}
#[test]
fn test_replace_if_exists_no_match_exit_0() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "some content\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("nonexistent")
.arg("--to")
.arg("new")
.arg("--if-exists")
.arg(&file)
.arg("--apply")
.assert()
.code(0);
}
#[test]
fn test_replace_empty_from_rejected() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "hello\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("")
.arg("--to")
.arg("X")
.arg("--apply")
.arg(file.to_str().unwrap())
.assert()
.failure()
.stderr(predicate::str::contains("search pattern must not be empty"));
assert_eq!(fs::read_to_string(&file).unwrap(), "hello\n");
}
#[test]
fn test_replace_invalid_regex_fails() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "hello world\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("--regex")
.arg("[invalid(regex")
.arg("--to")
.arg("x")
.arg(&file)
.assert()
.failure()
.stderr(predicate::str::contains("regex parse error"));
}
#[test]
fn test_replace_multiline_regex() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("code.rs");
fs::write(&file, "fn main() {\n println!(\"hi\");\n}\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("--regex")
.arg("--multiline")
.arg(r"fn main\(\) \{.*\}")
.arg("--to")
.arg("fn main() { /* replaced */ }")
.arg(&file)
.arg("--apply")
.assert()
.code(0);
let content = fs::read_to_string(&file).unwrap();
assert!(
content.contains("/* replaced */"),
"multiline replace should span newlines"
);
}
#[test]
fn test_replace_whole_line_deletes_matching_lines() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("code.rs");
fs::write(
&file,
"fn main() {\n let _x = foo();\n let y = bar();\n let _z = baz();\n}\n",
)
.unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("let _")
.arg("--whole-line")
.arg("--to")
.arg("")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(
fs::read_to_string(&file).unwrap(),
"fn main() {\n let y = bar();\n}\n"
);
}
#[test]
fn test_replace_whole_line_with_range() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "aaa\nbbb\nccc\nbbb\neee\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("bbb")
.arg("--whole-line")
.arg("--range")
.arg("1:3")
.arg("--to")
.arg("")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(fs::read_to_string(&file).unwrap(), "aaa\nccc\nbbb\neee\n");
}
#[test]
fn test_replace_collapse_blanks_after_whole_line_delete() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "keep\n\nremove\n\nalso keep\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("remove")
.arg("--whole-line")
.arg("--to")
.arg("")
.arg("--collapse-blanks")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(fs::read_to_string(&file).unwrap(), "keep\n\nalso keep\n");
}
#[test]
fn test_replace_range_requires_whole_line() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "hello\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--range")
.arg("1:5")
.arg("--to")
.arg("hi")
.arg(file.to_str().unwrap())
.assert()
.code(1)
.stderr(predicate::str::contains("--range requires --whole-line"));
assert_eq!(fs::read_to_string(&file).unwrap(), "hello\n");
}
#[test]
fn test_replace_check_exits_2() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "hello world\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("hi")
.arg(&file)
.arg("--check")
.assert()
.code(2);
let content = fs::read_to_string(&file).unwrap();
assert_eq!(
content, "hello world\n",
"file should be unchanged in --check mode"
);
}
#[test]
fn test_replace_json_check_output() {
let dir = TempDir::new().unwrap();
fs::write(dir.path().join("file.txt"), "hello world\n").unwrap();
let result = Command::cargo_bin("patchloom")
.unwrap()
.arg("--json")
.arg("replace")
.arg("hello")
.arg("--to")
.arg("bye")
.arg("--check")
.arg(dir.path())
.assert()
.code(2);
let output = result.get_output();
let stdout = String::from_utf8_lossy(&output.stdout);
let v: serde_json::Value = serde_json::from_str(&stdout).expect("stdout should be valid JSON");
assert_eq!(v["ok"], serde_json::json!(true));
assert!(v["files"].is_array(), "files should list affected paths");
}
#[test]
fn test_replace_jsonl_check_output() {
let dir = TempDir::new().unwrap();
fs::write(dir.path().join("a.txt"), "hello world\n").unwrap();
fs::write(dir.path().join("b.txt"), "hello again\n").unwrap();
let output = Command::cargo_bin("patchloom")
.unwrap()
.arg("--jsonl")
.arg("replace")
.arg("hello")
.arg("--to")
.arg("bye")
.arg("--check")
.arg(dir.path())
.output()
.unwrap();
assert_eq!(output.status.code(), Some(2));
let stdout = String::from_utf8_lossy(&output.stdout);
let lines: Vec<serde_json::Value> = stdout
.lines()
.filter(|l| !l.is_empty())
.map(|l| serde_json::from_str(l).expect("each line should be valid JSON"))
.collect();
assert_eq!(
lines.len(),
2,
"should have one JSONL line per matched file"
);
for line in &lines {
assert!(line["path"].is_string());
assert!(line["match_count"].as_u64().unwrap() >= 1);
}
}
#[test]
fn test_replace_no_match_without_if_exists_exits_3() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "some content\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("nonexistent_pattern_xyz")
.arg("--to")
.arg("new")
.arg(&file)
.arg("--apply")
.assert()
.code(3);
}
#[test]
fn test_replace_normalize_eol_lf() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("crlf.txt");
fs::write(&file, "old\r\ncontent\r\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("old")
.arg("--to")
.arg("new")
.arg("--normalize-eol")
.arg("lf")
.arg(&file)
.arg("--apply")
.assert()
.code(0);
let content = fs::read(&file).unwrap();
assert!(
!content.windows(2).any(|w| w == b"\r\n"),
"CRLF should be normalized to LF"
);
assert!(
content.windows(3).any(|w| w == b"new"),
"replacement should be applied"
);
}
#[test]
fn test_replace_directory_modifies_all_matching_files() {
let dir = TempDir::new().unwrap();
fs::write(dir.path().join("a.txt"), "hello world\n").unwrap();
fs::write(dir.path().join("b.txt"), "hello again\n").unwrap();
fs::write(dir.path().join("c.txt"), "no match here\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("bye")
.arg(dir.path())
.arg("--apply")
.assert()
.code(0);
assert_eq!(
fs::read_to_string(dir.path().join("a.txt")).unwrap(),
"bye world\n",
"a.txt should have hello replaced with bye"
);
assert_eq!(
fs::read_to_string(dir.path().join("b.txt")).unwrap(),
"bye again\n",
"b.txt should have hello replaced with bye"
);
assert_eq!(
fs::read_to_string(dir.path().join("c.txt")).unwrap(),
"no match here\n",
"file without match should be untouched"
);
}
#[test]
fn test_replace_nth_replaces_only_nth_occurrence() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "foo bar foo baz foo\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("foo")
.arg("--to")
.arg("REPLACED")
.arg("--nth")
.arg("2")
.arg("--apply")
.arg(file.to_str().unwrap())
.assert()
.code(0);
let content = fs::read_to_string(&file).unwrap();
assert_eq!(content, "foo bar REPLACED baz foo\n");
}
#[test]
fn test_replace_nth_zero_rejected() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "hello world\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("hi")
.arg("--nth")
.arg("0")
.arg("--apply")
.arg(file.to_str().unwrap())
.assert()
.code(1)
.stderr(predicates::str::contains("1-based"));
let content = fs::read_to_string(&file).unwrap();
assert_eq!(content, "hello world\n");
}
#[test]
fn test_replace_nth_no_match_when_out_of_range() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "foo bar\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("foo")
.arg("--to")
.arg("REPLACED")
.arg("--nth")
.arg("5")
.arg("--apply")
.arg(file.to_str().unwrap())
.assert()
.code(3);
assert_eq!(fs::read_to_string(&file).unwrap(), "foo bar\n");
}
#[test]
fn test_replace_insert_before() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("code.rs");
fs::write(&file, " /// Doc comment.\n pub field: bool,\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg(" /// Doc comment.")
.arg("--insert-before")
.arg(" // marker\n")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(
fs::read_to_string(&file).unwrap(),
" // marker\n /// Doc comment.\n pub field: bool,\n"
);
}
#[test]
fn test_replace_insert_after() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("code.rs");
fs::write(&file, "line1\nanchor\nline3\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("anchor")
.arg("--insert-after")
.arg(" // tagged")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(
fs::read_to_string(&file).unwrap(),
"line1\nanchor // tagged\nline3\n"
);
}
#[test]
fn test_replace_insert_before_with_regex() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "aaa\nbbb\nccc\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("b+")
.arg("--regex")
.arg("--insert-before")
.arg("X")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(fs::read_to_string(&file).unwrap(), "aaa\nXbbb\nccc\n");
}
#[test]
fn test_replace_insert_after_with_regex() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "aaa\nbbb\nccc\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("b+")
.arg("--regex")
.arg("--insert-after")
.arg("X")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(fs::read_to_string(&file).unwrap(), "aaa\nbbbX\nccc\n");
}
#[test]
fn test_replace_insert_before_nth() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "x a x a x\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("a")
.arg("--insert-before")
.arg("[")
.arg("--nth")
.arg("2")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(fs::read_to_string(&file).unwrap(), "x a x [a x\n");
}
#[test]
fn test_replace_insert_after_nth() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "x a x a x\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("a")
.arg("--insert-after")
.arg("]")
.arg("--nth")
.arg("2")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
assert_eq!(fs::read_to_string(&file).unwrap(), "x a x a] x\n");
}
#[test]
fn test_replace_insert_before_and_to_conflict() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("data.txt");
fs::write(&file, "hello\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("world")
.arg("--insert-before")
.arg("X")
.arg(file.to_str().unwrap())
.arg("--apply")
.assert()
.code(1)
.stderr(predicate::str::contains(
"--to cannot be combined with --insert-before",
));
}
#[test]
fn test_replace_case_insensitive() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "Hello HELLO hello\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("HI")
.arg("-i")
.arg("--apply")
.arg(file.to_str().unwrap())
.assert()
.code(0);
let content = fs::read_to_string(&file).unwrap();
assert_eq!(content, "HI HI HI\n");
}
#[test]
fn test_replace_nth_regex_replaces_only_nth() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "v1.0 and v2.0 and v3.0\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("--regex")
.arg(r"v\d+\.\d+")
.arg("--to")
.arg("vX.Y")
.arg("--nth")
.arg("2")
.arg("--apply")
.arg(file.to_str().unwrap())
.assert()
.code(0);
let content = fs::read_to_string(&file).unwrap();
assert_eq!(content, "v1.0 and vX.Y and v3.0\n");
}
#[test]
fn test_replace_skips_binary_files() {
let dir = TempDir::new().unwrap();
let text_file = dir.path().join("text.txt");
fs::write(&text_file, "old value\n").unwrap();
let bin_file = dir.path().join("data.bin");
let mut bin_content = b"old value".to_vec();
bin_content.push(0);
bin_content.extend_from_slice(b" more data");
fs::write(&bin_file, &bin_content).unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("old value")
.arg("--to")
.arg("new value")
.arg(dir.path().to_str().unwrap())
.arg("--apply")
.assert()
.code(0);
let text_content = fs::read_to_string(&text_file).unwrap();
assert_eq!(text_content, "new value\n");
let bin_after = fs::read(&bin_file).unwrap();
assert_eq!(bin_after, bin_content, "binary file should not be modified");
}
#[test]
fn test_replace_skips_invalid_utf8_files() {
let dir = TempDir::new().unwrap();
let text_file = dir.path().join("text.txt");
fs::write(&text_file, "old value\n").unwrap();
let invalid_file = dir.path().join("invalid.txt");
let invalid_content = b"old value\xff trailing".to_vec();
fs::write(&invalid_file, &invalid_content).unwrap();
let output = Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("old value")
.arg("--to")
.arg("new value")
.arg(dir.path().to_str().unwrap())
.arg("--apply")
.output()
.unwrap();
assert!(output.status.success());
let text_content = fs::read_to_string(&text_file).unwrap();
assert_eq!(text_content, "new value\n");
let invalid_after = fs::read(&invalid_file).unwrap();
assert_eq!(
invalid_after, invalid_content,
"invalid UTF-8 file should not be modified"
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("invalid.txt (invalid UTF-8)"),
"expected replace invalid UTF-8 warning, got: {stderr}"
);
}
#[test]
fn test_replace_apply_creates_backup_session() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "hello world\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("goodbye")
.arg("--apply")
.arg("--cwd")
.arg(dir.path())
.arg(file.to_str().unwrap())
.assert()
.code(0);
let backup_dir = dir.path().join(".patchloom/backups");
assert!(backup_dir.exists(), "backup directory should be created");
let sessions: Vec<_> = fs::read_dir(&backup_dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.collect();
assert_eq!(sessions.len(), 1, "exactly one backup session expected");
let manifest = sessions[0].path().join("manifest.json");
assert!(manifest.exists(), "manifest.json should exist in session");
}
#[test]
fn test_replace_apply_prunes_old_backup_sessions() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "aaa\n").unwrap();
let eight_days_ago = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
- std::time::Duration::from_secs(8 * 24 * 60 * 60);
let old_ts = format!("{}_0", eight_days_ago.as_nanos());
let old_session = dir.path().join(format!(".patchloom/backups/{old_ts}"));
fs::create_dir_all(&old_session).unwrap();
fs::write(
old_session.join("manifest.json"),
r#"{"timestamp":"old","entries":[]}"#,
)
.unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("aaa")
.arg("--to")
.arg("bbb")
.arg("--apply")
.arg("--cwd")
.arg(dir.path())
.arg(file.to_str().unwrap())
.assert()
.code(0);
let backup_dir = dir.path().join(".patchloom/backups");
let sessions: Vec<_> = fs::read_dir(&backup_dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.collect();
assert_eq!(
sessions.len(),
1,
"old session should be pruned, only the new one remains"
);
assert!(
!old_session.exists(),
"the old_session directory should be removed"
);
}
#[test]
fn test_replace_apply_keeps_recent_backup_sessions() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("test.txt");
fs::write(&file, "first\n").unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("first")
.arg("--to")
.arg("second")
.arg("--apply")
.arg("--cwd")
.arg(dir.path())
.arg(file.to_str().unwrap())
.assert()
.code(0);
std::thread::sleep(std::time::Duration::from_millis(10));
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("second")
.arg("--to")
.arg("third")
.arg("--apply")
.arg("--cwd")
.arg(dir.path())
.arg(file.to_str().unwrap())
.assert()
.code(0);
let backup_dir = dir.path().join(".patchloom/backups");
let sessions: Vec<_> = fs::read_dir(&backup_dir)
.unwrap()
.filter_map(|e| e.ok())
.filter(|e| e.path().is_dir())
.collect();
assert_eq!(
sessions.len(),
2,
"both recent backup sessions should be kept"
);
}
#[cfg(unix)]
#[test]
fn test_replace_follows_symlink_within_cwd() {
let dir = TempDir::new().unwrap();
let real_file = dir.path().join("real.txt");
fs::write(&real_file, "hello world\n").unwrap();
let link = dir.path().join("link.txt");
std::os::unix::fs::symlink(&real_file, &link).unwrap();
Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("goodbye")
.arg("--apply")
.arg(&link)
.assert()
.code(0);
let content = fs::read_to_string(&link).unwrap();
assert_eq!(content, "goodbye world\n");
}
#[cfg(unix)]
#[test]
fn test_replace_apply_readonly_dir_fails_gracefully() {
use std::os::unix::fs::PermissionsExt;
let dir = TempDir::new().unwrap();
let sub = dir.path().join("locked");
fs::create_dir(&sub).unwrap();
let file = sub.join("target.txt");
fs::write(&file, "hello world\n").unwrap();
if !readonly_dir_blocks_writes(&sub) {
return;
}
let output = Command::cargo_bin("patchloom")
.unwrap()
.arg("replace")
.arg("hello")
.arg("--to")
.arg("goodbye")
.arg("--apply")
.arg(&file)
.output()
.unwrap();
assert_ne!(
output.status.code(),
Some(0),
"write in readonly dir should fail"
);
let content = fs::read_to_string(&file).unwrap();
assert_eq!(content, "hello world\n");
fs::set_permissions(&sub, fs::Permissions::from_mode(0o755)).unwrap();
}
#[test]
fn test_replace_format_flag_runs_after_apply() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("f.txt");
fs::write(&file, "aaa\n").unwrap();
let marker = dir.path().join("format_ran.marker");
patchloom_in(dir.path())
.args([
"replace",
"aaa",
"--to",
"bbb",
"--apply",
"--format",
&shell_touch(&marker),
])
.assert()
.code(0);
assert!(
marker.exists(),
"--format command should have created the marker file"
);
assert_eq!(fs::read_to_string(&file).unwrap(), "bbb\n");
}
#[test]
fn test_replace_format_flag_skipped_in_diff_mode() {
let dir = TempDir::new().unwrap();
let file = dir.path().join("f.txt");
fs::write(&file, "aaa\n").unwrap();
let marker = dir.path().join("format_ran.marker");
patchloom_in(dir.path())
.args([
"replace",
"aaa",
"--to",
"bbb",
"--format",
&shell_touch(&marker),
])
.assert()
.code(0);
assert!(
!marker.exists(),
"--format should NOT run in diff-only mode"
);
}