use super::*;
#[test]
fn grep_preserves_context_output() {
let (temp, runtime) = runtime();
fs::write(temp.path().join("a.txt"), "one\nmatch\nthree\n").unwrap();
let result = runtime.dispatch(
"grep",
json!({"pattern":"match","path":".","limit":10,"context":1}),
);
assert!(result.content.contains("a.txt:1:one"));
assert!(result.content.contains("a.txt:2:match"));
assert!(result.content.contains("a.txt:3:three"));
}
#[test]
fn grep_stops_at_limit_without_scanning_all_matches() {
let (temp, runtime) = runtime();
fs::write(temp.path().join("a.txt"), "match\nmatch\nlater match\n").unwrap();
let result = runtime.dispatch("grep", json!({"pattern":"match","path":".","limit":2}));
assert_eq!(result.content.lines().count(), 2);
assert_eq!(result.metadata["engine"], "native");
assert_eq!(result.metadata["matches_returned"], 2);
assert_eq!(result.metadata["truncated"], true);
assert_eq!(result.metadata["timed_out"], false);
assert_eq!(result.metadata["limit_reason"], "matches");
assert!(result.metadata["files_scanned"].as_u64().unwrap() >= 1);
assert!(result.metadata["bytes_scanned"].as_u64().unwrap() > 0);
assert!(result.content.contains("a.txt:1:match"));
assert!(result.content.contains("a.txt:2:match"));
}
#[test]
fn grep_skips_binary_files() {
let (temp, runtime) = runtime();
fs::write(temp.path().join("bin.dat"), b"early match\n\0hidden").unwrap();
fs::write(temp.path().join("a.txt"), "visible match\n").unwrap();
let result = runtime.dispatch("grep", json!({"pattern":"match","path":".","limit":10}));
assert!(result.content.contains("visible match"));
assert!(!result.content.contains("early match"));
assert!(!result.content.contains("hidden"));
}
#[test]
fn grep_finds_matching_lines_with_native_engine() {
let (temp, runtime) = runtime();
fs::write(temp.path().join("a.txt"), "alpha\nbeta\n").unwrap();
let result = runtime.dispatch("grep", json!({"pattern":"beta","path":".","limit":10}));
assert!(result.success, "{}", result.content);
assert!(result.content.contains("a.txt:2:beta"));
assert_eq!(result.metadata["engine"], "native");
assert!(result.metadata.get("exit_code").is_none());
}
#[test]
fn find_and_grep_exclude_p4ignored_files() {
let (temp, runtime) = runtime();
fs::write(temp.path().join(".p4ignore"), "ignored.txt\n").unwrap();
fs::write(temp.path().join("ignored.txt"), "unique-match\n").unwrap();
fs::write(temp.path().join("visible.txt"), "unique-match\n").unwrap();
let find = runtime.dispatch("find", json!({"query":"txt","limit":10}));
assert!(find.success, "{}", find.content);
assert!(find.content.contains("visible.txt"), "{}", find.content);
assert!(!find.content.contains("ignored.txt"), "{}", find.content);
let grep = runtime.dispatch(
"grep",
json!({"pattern":"unique-match","path":".","limit":10}),
);
assert!(grep.success, "{}", grep.content);
assert!(grep.content.contains("visible.txt"), "{}", grep.content);
assert!(!grep.content.contains("ignored.txt"), "{}", grep.content);
}
#[test]
fn find_rejects_empty_query_and_invalid_limit() {
let (_temp, runtime) = runtime();
for args in [
json!({"query":""}),
json!({"query":" "}),
json!({"query":"lib", "limit":0}),
json!({"query":"lib", "limit":crate::tools::args::FIND_MAX_LIMIT + 1}),
json!({"query":"lib", "unknown":true}),
] {
let result = runtime.dispatch("find", args);
assert!(
!result.success,
"accepted invalid find args: {}",
result.content
);
}
}
#[test]
fn find_returns_fuzzy_relative_file_paths() {
let (temp, runtime) = runtime();
fs::create_dir_all(temp.path().join("src")).unwrap();
fs::create_dir_all(temp.path().join("docs")).unwrap();
fs::write(temp.path().join("src/lib.rs"), "").unwrap();
fs::write(temp.path().join("docs/readme.md"), "").unwrap();
let result = runtime.dispatch("find", json!({"query":"lib", "limit":10}));
assert!(result.success, "{}", result.content);
assert!(
result.content.lines().any(|line| line == "src/lib.rs"),
"{}",
result.content
);
assert!(
!result
.content
.contains(temp.path().to_string_lossy().as_ref())
);
assert_eq!(result.metadata["engine"], "rust");
assert_eq!(
result.metadata["matches_returned"].as_u64().unwrap() as usize,
result.content.lines().count()
);
}
#[test]
fn find_directory_kind_returns_directory_paths_with_slash() {
let (temp, runtime) = runtime();
fs::create_dir_all(temp.path().join("src/components")).unwrap();
fs::write(temp.path().join("src/components/button.rs"), "").unwrap();
let result = runtime.dispatch(
"find",
json!({"query":"components", "kind":"directories", "limit":10}),
);
assert!(result.success, "{}", result.content);
assert!(
result.content.lines().any(|line| line == "src/components/"),
"{}",
result.content
);
}
#[test]
fn find_mixed_kind_returns_files_and_directories() {
let (temp, runtime) = runtime();
fs::create_dir_all(temp.path().join("src/widget")).unwrap();
fs::write(temp.path().join("src/widget.rs"), "").unwrap();
fs::write(temp.path().join("src/widget/mod.rs"), "").unwrap();
let result = runtime.dispatch(
"find",
json!({"query":"widget", "kind":"mixed", "limit":20}),
);
assert!(result.success, "{}", result.content);
assert!(
result.content.lines().any(|line| line == "src/widget.rs"),
"{}",
result.content
);
assert!(
result.content.lines().any(|line| line == "src/widget/"),
"{}",
result.content
);
}
#[test]
fn find_path_filter_scopes_results_to_directory() {
let (temp, runtime) = runtime();
fs::create_dir_all(temp.path().join("src")).unwrap();
fs::create_dir_all(temp.path().join("docs")).unwrap();
fs::write(temp.path().join("src/main.rs"), "").unwrap();
fs::write(temp.path().join("docs/main.md"), "").unwrap();
let result = runtime.dispatch("find", json!({"query":"main", "path":"src", "limit":20}));
assert!(result.success, "{}", result.content);
assert!(
result.content.lines().any(|line| line == "src/main.rs"),
"{}",
result.content
);
assert!(
!result.content.contains("docs/main.md"),
"{}",
result.content
);
}
#[test]
fn find_path_filter_searches_filtered_universe_before_limit() {
let (temp, runtime) = runtime();
fs::create_dir_all(temp.path().join("scoped/deep/nested")).unwrap();
for index in 0..25 {
fs::write(temp.path().join(format!("needle_{index:02}.rs")), "").unwrap();
}
fs::write(temp.path().join("scoped/deep/nested/needle_target.rs"), "").unwrap();
let result = runtime.dispatch(
"find",
json!({"query":"needle", "path":"scoped", "limit":1}),
);
assert!(result.success, "{}", result.content);
assert_eq!(
result.content.lines().collect::<Vec<_>>(),
vec!["scoped/deep/nested/needle_target.rs"]
);
assert_eq!(result.metadata["matches_returned"], 1);
assert_eq!(result.metadata["total_matched"], 1);
assert_eq!(result.metadata["truncated"], false);
}
#[test]
fn find_path_filter_offset_pages_filtered_results() {
let (temp, runtime) = runtime();
fs::create_dir_all(temp.path().join("scoped")).unwrap();
for index in 0..25 {
fs::write(temp.path().join(format!("needle_{index:02}.rs")), "").unwrap();
}
for name in ["alpha", "beta", "gamma"] {
fs::write(temp.path().join(format!("scoped/needle-{name}.rs")), "").unwrap();
}
let first_page = runtime.dispatch(
"find",
json!({"query":"needle", "path":"scoped", "limit":3}),
);
assert!(first_page.success, "{}", first_page.content);
let first_page_paths = first_page.content.lines().collect::<Vec<_>>();
assert_eq!(first_page_paths.len(), 3, "{}", first_page.content);
assert_eq!(first_page.metadata["total_matched"], 3);
let offset_page = runtime.dispatch(
"find",
json!({"query":"needle", "path":"scoped", "limit":1, "offset":1}),
);
assert!(offset_page.success, "{}", offset_page.content);
assert_eq!(
offset_page.content.lines().collect::<Vec<_>>(),
vec![first_page_paths[1]]
);
assert_eq!(offset_page.metadata["matches_returned"], 1);
assert_eq!(offset_page.metadata["total_matched"], 3);
assert_eq!(offset_page.metadata["truncated"], true);
}
#[test]
fn find_rejects_file_path_and_outside_absolute_path_when_disabled() {
let (temp, _runtime) = runtime();
fs::write(temp.path().join("file.txt"), "").unwrap();
let outside = TempDir::new().unwrap();
fs::write(outside.path().join("outside.txt"), "").unwrap();
let restricted = ToolRuntime::new_with_settings(
temp.path(),
ToolSettings {
find: FindToolSettings {
absolute_paths: false,
},
..ToolSettings::default()
},
)
.unwrap();
let file_result = restricted.dispatch("find", json!({"query":"file", "path":"file.txt"}));
assert!(!file_result.success);
assert!(
file_result
.content
.contains("find path must be a directory")
);
let outside_result =
restricted.dispatch("find", json!({"query":"outside", "path": outside.path()}));
assert!(!outside_result.success);
assert!(
outside_result.content.contains("escapes cwd"),
"{}",
outside_result.content
);
}