test: add regression coverage for edit_file fuzz omission (#2138)

- Test that edit_file accepts calls with fuzz omitted, fuzz=false, and fuzz=true
- Verify fuzz is excluded from schema required fields but present as optional boolean
- Add agent-mode catalog test confirming edit_file is loaded and fuzz-less calls execute
- Update existing required-fields assertions to check for exactly path/search/replace
This commit is contained in:
Hunter Bown
2026-05-26 16:37:33 -05:00
parent aa83446d6b
commit 8d48b19b5d
2 changed files with 106 additions and 1 deletions
+64
View File
@@ -472,6 +472,70 @@ fn model_tool_catalog_applies_native_and_mcp_deferral() {
assert_eq!(defer_loading("mcp_server_write"), Some(true));
}
#[test]
fn agent_catalog_keeps_edit_file_loaded_when_fuzz_is_omitted() {
let (engine, _handle) = Engine::new(EngineConfig::default(), &Config::default());
let registry = engine
.build_turn_tool_registry_builder(
AppMode::Agent,
engine.config.todos.clone(),
engine.config.plan_state.clone(),
)
.build(engine.build_tool_context(AppMode::Agent, false));
let always_load = HashSet::new();
let catalog = build_model_tool_catalog(
registry.to_api_tools_with_cache(true),
vec![],
AppMode::Agent,
&always_load,
);
let edit = catalog
.iter()
.find(|tool| tool.name == "edit_file")
.expect("edit_file registered");
assert_eq!(edit.defer_loading, Some(false));
let required = edit.input_schema["required"]
.as_array()
.expect("edit_file schema should include required fields");
assert!(required.iter().any(|field| field.as_str() == Some("path")));
assert!(
required
.iter()
.any(|field| field.as_str() == Some("search"))
);
assert!(
required
.iter()
.any(|field| field.as_str() == Some("replace"))
);
assert!(!required.iter().any(|field| field.as_str() == Some("fuzz")));
assert_eq!(
edit.input_schema["properties"]["fuzz"]["type"].as_str(),
Some("boolean")
);
let active_at_batch_start = initial_active_tools(&catalog);
assert!(active_at_batch_start.contains("edit_file"));
let mut hydrated_this_batch = HashSet::new();
assert!(
maybe_hydrate_requested_deferred_tool(
"edit_file",
&json!({
"path": "src/foo.rs",
"search": "before",
"replace": "after"
}),
&catalog,
&active_at_batch_start,
&mut hydrated_this_batch,
)
.is_none(),
"loaded edit_file calls without fuzz should execute instead of hydrating the schema"
);
assert!(hydrated_this_batch.is_empty());
}
#[test]
fn tools_always_load_overrides_default_native_deferral() {
let always_load = HashSet::from(["git_show".to_string()]);
+42 -1
View File
@@ -1475,6 +1475,41 @@ mod tests {
assert_eq!(edited, "hi world hi");
}
#[tokio::test]
async fn test_edit_file_accepts_omitted_and_explicit_fuzz() {
let tmp = tempdir().expect("tempdir");
let ctx = ToolContext::new(tmp.path().to_path_buf());
let tool = EditFileTool;
for (file_name, fuzz) in [
("fuzz_omitted.txt", None),
("fuzz_false.txt", Some(false)),
("fuzz_true.txt", Some(true)),
] {
let test_file = tmp.path().join(file_name);
fs::write(&test_file, "hello world").expect("write");
let mut input = serde_json::Map::from_iter([
("path".to_string(), json!(file_name)),
("search".to_string(), json!("hello")),
("replace".to_string(), json!("hi")),
]);
if let Some(fuzz) = fuzz {
input.insert("fuzz".to_string(), json!(fuzz));
}
let result = tool
.execute(Value::Object(input), &ctx)
.await
.expect("execute");
assert!(result.success, "{file_name}: {}", result.content);
assert!(result.content.contains("Replaced 1 occurrence"));
let edited = fs::read_to_string(&test_file).expect("read");
assert_eq!(edited, "hi world");
}
}
#[tokio::test]
async fn test_edit_file_single_match_has_no_multi_match_warning() {
let tmp = tempdir().expect("tempdir");
@@ -1827,7 +1862,13 @@ mod tests {
.get("required")
.and_then(|value| value.as_array())
.expect("edit schema should include required array");
assert_eq!(required.len(), 3);
let required_fields: Vec<_> = required.iter().filter_map(|value| value.as_str()).collect();
assert_eq!(required_fields, vec!["path", "search", "replace"]);
assert!(!required_fields.contains(&"fuzz"));
assert_eq!(
edit_schema["properties"]["fuzz"]["type"].as_str(),
Some("boolean")
);
let search_desc = edit_schema["properties"]["search"]["description"]
.as_str()
.expect("search description");