107 lines
3.3 KiB
Rust
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");
|
|
}
|