focus/crates/focus-tools/tests/edit_tests.rs

107 lines
3.3 KiB
Rust

//! `edit` 工具的单元测试。
//! Unit tests for the `edit` tool.
use focus_core::tool::Tool;
use focus_json::JsonValue;
use focus_tools::EditTool;
use std::fs;
use std::path::PathBuf;
fn temp_dir(tag: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("focus-tools-edit-{}-{}", tag, std::process::id()));
let _ = fs::remove_dir_all(&d);
fs::create_dir_all(&d).unwrap();
d
}
fn edit_args(path: &str, edits: Vec<JsonValue>) -> JsonValue {
let mut args = JsonValue::obj();
args.insert("path", path.into()).ok();
args.insert("edits", JsonValue::Arr(edits)).ok();
args
}
fn pair(old: &str, new: &str) -> JsonValue {
let mut e = JsonValue::obj();
e.insert("oldString", old.into()).ok();
e.insert("newString", new.into()).ok();
e
}
#[test]
fn applies_single_replacement() {
let dir = temp_dir("single");
fs::write(dir.join("f.txt"), "foo bar foo").unwrap();
let tool = EditTool::new(&dir);
let r = tool
.execute("id", &edit_args("f.txt", vec![pair("bar", "baz")]), None)
.unwrap();
assert!(r.content[0].as_text().unwrap().text.contains("1 edit"));
assert_eq!(
fs::read_to_string(dir.join("f.txt")).unwrap(),
"foo baz foo"
);
}
#[test]
fn applies_multiple_edits_in_order() {
let dir = temp_dir("multi");
fs::write(dir.join("f.txt"), "a b c").unwrap();
let tool = EditTool::new(&dir);
let r = tool
.execute(
"id",
&edit_args(
"f.txt",
vec![pair("a", "1"), pair("b", "2"), pair("c", "3")],
),
None,
)
.unwrap();
assert!(r.content[0].as_text().unwrap().text.contains("3 edit"));
assert_eq!(fs::read_to_string(dir.join("f.txt")).unwrap(), "1 2 3");
}
#[test]
fn ambiguous_match_requires_occurrence() {
let dir = temp_dir("ambig");
fs::write(dir.join("f.txt"), "x x x").unwrap();
let tool = EditTool::new(&dir);
// 无 occurrence → 报错。
// No occurrence → error.
let err = tool.execute("id", &edit_args("f.txt", vec![pair("x", "y")]), None);
assert!(err.is_err());
let msg = err.unwrap_err().to_string();
assert!(msg.contains("3 times"), "got: {}", msg);
// 带 occurrence → 只替换第 2 个。
// With occurrence → replace only the 2nd.
let mut e = pair("x", "y");
e.insert("occurrence", 2u64.into()).ok();
tool.execute("id", &edit_args("f.txt", vec![e]), None)
.unwrap();
assert_eq!(fs::read_to_string(dir.join("f.txt")).unwrap(), "x y x");
}
#[test]
fn not_found_reports_descriptively() {
let dir = temp_dir("nf");
fs::write(dir.join("f.txt"), "hello").unwrap();
let tool = EditTool::new(&dir);
let err = tool
.execute("id", &edit_args("f.txt", vec![pair("zzz", "y")]), None)
.unwrap_err();
assert!(err.to_string().contains("not found"), "got: {}", err);
}
#[test]
fn preserves_crlf_when_not_matched() {
let dir = temp_dir("crlf");
fs::write(dir.join("f.txt"), "a\r\nb\r\nc\r\n").unwrap();
let tool = EditTool::new(&dir);
tool.execute("id", &edit_args("f.txt", vec![pair("b", "B")]), None)
.unwrap();
// 未匹配的 CRLF 原样保留。
// Unmatched CRLF bytes are preserved as-is.
assert_eq!(fs::read(dir.join("f.txt")).unwrap(), b"a\r\nB\r\nc\r\n");
}