use std::sync as path_std_sync;
use super::*;
fn args(limit: CborValue) -> CborValue {
CborValue::Map(vec![
(
CborValue::Text("pattern".to_owned()),
CborValue::Text("*".to_owned()),
),
(CborValue::Text("limit".to_owned()), limit),
])
}
#[test]
fn find_rejects_wrong_type_limit() {
let err = run_find(&args(CborValue::Text("10".to_owned())))
.expect_err("string limit should be rejected");
assert_eq!(err.message, "argument `limit` must be an integer");
}
#[test]
fn find_rejects_wrong_type_path() {
let args = CborValue::Map(vec![
(
CborValue::Text("pattern".to_owned()),
CborValue::Text("*".to_owned()),
),
(
CborValue::Text("path".to_owned()),
CborValue::Integer(1.into()),
),
]);
let err = run_find(&args).expect_err("integer path should be rejected");
assert_eq!(err.message, "argument `path` must be a string");
}
#[test]
fn find_parse_retains_typed_root_and_display() {
let omitted = parse_find_request(&CborValue::Map(vec![(
CborValue::Text("pattern".to_owned()),
CborValue::Text("*.rs".to_owned()),
)]))
.expect("omitted path parses");
assert_eq!(omitted.path, PathBuf::from("."));
assert_eq!(omitted.display_args, "*.rs in .");
let explicit_path = "./search-root/suffix";
let explicit = parse_find_request(&CborValue::Map(vec![
(
CborValue::Text("pattern".to_owned()),
CborValue::Text("*.rs".to_owned()),
),
(
CborValue::Text("path".to_owned()),
CborValue::Text(explicit_path.to_owned()),
),
]))
.expect("explicit path parses");
assert_eq!(explicit.path, PathBuf::from(explicit_path));
assert_eq!(explicit.display_args, "*.rs in ./search-root/suffix");
}
#[test]
fn find_rejects_non_positive_limit() {
let err =
run_find(&args(CborValue::Integer(0.into()))).expect_err("zero limit should be rejected");
assert_eq!(err.message, "limit must be >= 1");
}
#[test]
fn find_limit_bounds_collected_matches() {
let tempdir = tempfile::TempDir::new().expect("tempdir");
for name in ["alpha.txt", "beta.txt", "gamma.txt"] {
std::fs::write(tempdir.path().join(name), "x").expect("write file");
}
let args = CborValue::Map(vec![
(
CborValue::Text("pattern".to_owned()),
CborValue::Text("*.txt".to_owned()),
),
(
CborValue::Text("path".to_owned()),
CborValue::Text(tempdir.path().display().to_string()),
),
(
CborValue::Text("limit".to_owned()),
CborValue::Integer(1.into()),
),
]);
let result = run_find(&args).expect("find").result;
let CborValue::Map(entries) = result else {
panic!("expected result map");
};
let output = entries
.iter()
.find_map(|(key, value)| match (key, value) {
(CborValue::Text(key), CborValue::Text(value)) if key == "output" => Some(value),
_ => None,
})
.expect("output");
let matches: i64 = entries
.iter()
.find_map(|(key, value)| match (key, value) {
(CborValue::Text(key), CborValue::Integer(value)) if key == "matches" => {
i128::from(*value).try_into().ok()
}
_ => None,
})
.expect("matches");
let limit_reached = entries.iter().any(|(key, value)| {
matches!(
(key, value),
(CborValue::Text(key), CborValue::Bool(true)) if key == "limit_reached"
)
});
assert_eq!(
output.lines().take_while(|line| !line.is_empty()).count(),
1
);
assert_eq!(matches, 1);
assert!(limit_reached);
assert!(output.contains("1 results limit reached"));
}
#[test]
fn find_rejects_limit_above_output_cap() {
let err = run_find(&args(CborValue::Integer(
(MAX_FIND_LIMIT as i64 + 1).into(),
)))
.expect_err("limit over cap");
assert_eq!(err.message, format!("limit must be <= {MAX_FIND_LIMIT}"));
}
#[test]
fn find_max_limit_notice_asks_to_refine() {
let notice = limit_reached_notice(MAX_FIND_LIMIT);
assert!(notice.contains("Maximum limit reached"));
assert!(!notice.contains(&format!("limit={}", MAX_FIND_LIMIT * 2)));
}
#[test]
fn find_cancellable_stops_on_early_cancel_request() {
let tempdir = tempfile::TempDir::new().expect("tempdir");
std::fs::write(tempdir.path().join("alpha.txt"), "x").expect("write file");
let args = CborValue::Map(vec![
(
CborValue::Text("pattern".to_owned()),
CborValue::Text("*.txt".to_owned()),
),
(
CborValue::Text("path".to_owned()),
CborValue::Text(tempdir.path().display().to_string()),
),
]);
let (cancel_tx, cancel_rx) = path_std_sync::mpsc::channel();
cancel_tx.send(()).expect("send cancel");
let result = run_find_cancellable(&args, Some(&cancel_rx)).expect("find result");
assert!(matches!(result, CancellableToolRun::Cancelled));
}
#[test]
fn find_collect_stops_when_cancelled_during_traversal() {
let tempdir = tempfile::TempDir::new().expect("tempdir");
std::fs::write(tempdir.path().join("alpha.txt"), "x").expect("write alpha");
std::fs::write(tempdir.path().join("beta.txt"), "x").expect("write beta");
let request = FindRequest {
pattern: "*.txt".to_owned(),
path: tempdir.path().to_owned(),
limit: DEFAULT_FIND_LIMIT,
display_args: "test".to_owned(),
};
let search = prepare_find_search(&request).expect("search");
let mut checks = 0usize;
let result = collect_find_matches(&search, &mut || {
checks += 1;
1 < checks
})
.expect("find collection");
assert!(result.is_none());
}
#[test]
fn find_escapes_control_characters_in_paths() {
let tempdir = tempfile::TempDir::new().expect("tempdir");
std::fs::write(tempdir.path().join("line\nbreak.txt"), "x").expect("write file");
let args = CborValue::Map(vec![
(
CborValue::Text("pattern".to_owned()),
CborValue::Text("*.txt".to_owned()),
),
(
CborValue::Text("path".to_owned()),
CborValue::Text(tempdir.path().display().to_string()),
),
]);
let result = run_find(&args).expect("find").result;
let CborValue::Map(entries) = result else {
panic!("expected result map");
};
let output = entries
.iter()
.find_map(|(key, value)| match (key, value) {
(CborValue::Text(key), CborValue::Text(value)) if key == "output" => Some(value),
_ => None,
})
.expect("output");
assert_eq!(output, "line\\nbreak.txt");
}
#[test]
fn find_notices_stay_within_output_cap() {
let notice = "10 KiB/2000 line visible output limit reached.".to_owned();
let suffix_len = format!("\n\n[{notice}]").len();
let output = append_notices_within_cap(
format!("{}étail", "x".repeat(MAX_OUTPUT_BYTES - suffix_len - 1)),
std::slice::from_ref(¬ice),
);
assert!(output.len() <= MAX_OUTPUT_BYTES);
assert!(output.contains(¬ice));
}