use std::fmt::Write as _;
use serde_json::json;
use super::tests::{Place, harness, say};
use crate::provider::fake::{FakeServer, calls, says};
fn long_file(place: &Place, name: &str, total: usize) -> String {
let text: String = (1..=total).fold(String::new(), |mut text, number| {
let _ = writeln!(text, "line {number}");
text
});
std::fs::write(place.work().join(name), &text).expect("write the long file");
text
}
fn result_of(server: &FakeServer, index: usize, id: &str) -> (String, bool) {
let request = &server.received()[index].body;
let message = request["messages"].as_array().expect("messages").last().expect("a message");
let block = message["content"]
.as_array()
.expect("blocks")
.iter()
.find(|block| block["tool_use_id"] == id)
.unwrap_or_else(|| panic!("no result for the call {id}: {message}"));
(block["content"].as_str().expect("text").to_owned(), block["is_error"] == json!(true))
}
#[test]
fn a_read_of_a_range_in_the_middle_of_a_long_file_gives_those_lines() {
let place = Place::new("read-range");
long_file(&place, "long.txt", 3000);
let server = FakeServer::start(vec![
(200, calls("r1", "read_file", &json!({ "path": "long.txt", "start_line": 500, "end_line": 700 }))),
(200, says("Lines 500 to 700 are about lines 500 to 700.")),
]);
let mut harness = harness(place.app(Some(&server.address)));
say(&mut harness, "what is on lines 500 to 700 of long.txt?");
let (text, is_error) = result_of(&server, 1, "r1");
assert!(!is_error, "{text}");
let lines: Vec<&str> = text.lines().collect();
assert_eq!(lines.len(), 202, "the 201 lines asked for and the note under them");
assert_eq!(lines[0], "line 500");
assert_eq!(lines[1], "line 501");
assert_eq!(lines[200], "line 700");
assert_eq!(lines[201], "(Lines 500-700 of 3000. Not shown: 499 lines above and 2300 lines below.)");
assert!(!text.contains("line 499"), "and nothing above the range");
assert!(!text.contains("line 701"), "nor below it");
let screen = harness.screen();
assert!(screen.contains("read long.txt"), "{screen}");
assert!(screen.contains("line 500"), "and the start of the answer: {screen}");
}
#[test]
fn a_read_without_a_range_is_the_head_of_the_file_as_it_always_was() {
let place = Place::new("read-head");
long_file(&place, "long.txt", 3000);
let server = FakeServer::start(vec![
(200, calls("r1", "read_file", &json!({ "path": "long.txt" }))),
(200, says("I read the top of it.")),
]);
let mut harness = harness(place.app(Some(&server.address)));
say(&mut harness, "read long.txt");
let (text, is_error) = result_of(&server, 1, "r1");
assert!(!is_error, "{text}");
assert!(text.contains("line 1\n") && text.contains("line 2000\n"), "the first 2000 lines");
assert!(!text.contains("line 2001\n"), "and no more");
assert!(
text.ends_with("(Cut: the file has 3000 lines; only the first 2000 are shown.)"),
"the note a read without a range has always had: {}",
&text[text.len() - 80..]
);
assert!(harness.screen().contains("read long.txt"), "{}", harness.screen());
}
#[test]
fn a_range_that_cannot_be_used_is_an_error_the_person_sees() {
let place = Place::new("read-bad");
let file = long_file(&place, "long.txt", 3000);
let server = FakeServer::start(vec![
(
200,
crate::provider::fake::sse(&[
json!({"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "a", "name": "read_file", "input": {}}}),
json!({"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"long.txt\", \"start_line\": 3001, \"end_line\": 4000}"}}),
json!({"type": "content_block_stop", "index": 0}),
json!({"type": "content_block_start", "index": 1, "content_block": {"type": "tool_use", "id": "b", "name": "read_file", "input": {}}}),
json!({"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"long.txt\", \"start_line\": 700, \"end_line\": 500}"}}),
json!({"type": "content_block_stop", "index": 1}),
json!({"type": "content_block_start", "index": 2, "content_block": {"type": "tool_use", "id": "c", "name": "read_file", "input": {}}}),
json!({"type": "content_block_delta", "index": 2, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"long.txt\", \"start_line\": 0, \"end_line\": 10}"}}),
json!({"type": "content_block_stop", "index": 2}),
json!({"type": "message_delta", "delta": {"stop_reason": "tool_use"}}),
json!({"type": "message_stop"}),
]),
),
(200, says("Those ranges are not ones I can use.")),
]);
let mut harness = harness(place.app(Some(&server.address)));
say(&mut harness, "read me the last lines of long.txt");
let (past, error) = result_of(&server, 1, "a");
assert!(error, "{past}");
assert_eq!(past, "The file has 3000 lines, so `start_line` 3001 is past its end.");
let (inverted, error) = result_of(&server, 1, "b");
assert!(error, "{inverted}");
assert!(inverted.contains("`end_line` is before `start_line`"), "{inverted}");
let (zero, error) = result_of(&server, 1, "c");
assert!(error, "{zero}");
assert_eq!(zero, "Lines are counted from 1, so `start_line` must be 1 or more.");
let screen = harness.screen();
assert!(screen.contains("past its end"), "{screen}");
assert!(screen.contains("before `start_line`"), "{screen}");
assert_eq!(std::fs::read_to_string(place.work().join("long.txt")).expect("read"), file);
}
#[test]
fn a_listing_with_a_depth_is_a_tree_and_a_deeper_one_names_the_maximum() {
let place = Place::new("list-depth");
std::fs::create_dir_all(place.work().join("src/deep")).expect("folder");
std::fs::write(place.work().join("src/one.txt"), "0123456789").expect("write");
std::fs::write(place.work().join("src/deep/two.md"), "x".repeat(2500)).expect("write");
std::fs::write(place.work().join("top.txt"), "top").expect("write");
let server = FakeServer::start(vec![
(
200,
crate::provider::fake::sse(&[
json!({"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "a", "name": "list_dir", "input": {}}}),
json!({"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"\", \"depth\": 2}"}}),
json!({"type": "content_block_stop", "index": 0}),
json!({"type": "content_block_start", "index": 1, "content_block": {"type": "tool_use", "id": "b", "name": "list_dir", "input": {}}}),
json!({"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"\", \"depth\": 9}"}}),
json!({"type": "content_block_stop", "index": 1}),
json!({"type": "message_delta", "delta": {"stop_reason": "tool_use"}}),
json!({"type": "message_stop"}),
]),
),
(200, says("The folder has src and top.txt in it.")),
]);
let mut harness = harness(place.app(Some(&server.address)));
say(&mut harness, "what is in this folder?");
let (tree, is_error) = result_of(&server, 1, "a");
assert!(!is_error, "{tree}");
assert_eq!(
tree,
"src/\n\
\x20 deep/\n\
\x20 one.txt 10 B\n\
top.txt 3 B",
"an indented tree, folders before files, with the size of every file"
);
let (deep, is_error) = result_of(&server, 1, "b");
assert!(is_error, "{deep}");
assert_eq!(deep, "A depth of 9 is not one; `list_dir` takes a depth from 1 to 3.");
let screen = harness.screen();
assert!(screen.contains("one.txt"), "the tree is on the screen: {screen}");
assert!(screen.contains("10 B"), "with the sizes: {screen}");
assert!(screen.contains("from 1 to 3"), "and the refusal: {screen}");
}
#[test]
fn a_search_can_ignore_case_and_be_narrowed_by_a_glob() {
let place = Place::new("search-narrow");
std::fs::create_dir_all(place.work().join("src")).expect("folder");
std::fs::create_dir_all(place.work().join("docs")).expect("folder");
std::fs::write(place.work().join("src/main.rs"), "let widget = 1;\n").expect("write");
std::fs::write(place.work().join("docs/guide.md"), "the Widget is here\n").expect("write");
let server = FakeServer::start(vec![
(
200,
crate::provider::fake::sse(&[
json!({"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "a", "name": "search", "input": {}}}),
json!({"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"pattern\": \"widget\", \"ignore_case\": true}"}}),
json!({"type": "content_block_stop", "index": 0}),
json!({"type": "content_block_start", "index": 1, "content_block": {"type": "tool_use", "id": "b", "name": "search", "input": {}}}),
json!({"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"pattern\": \"widget\", \"glob\": \"src/*\"}"}}),
json!({"type": "content_block_stop", "index": 1}),
json!({"type": "content_block_start", "index": 2, "content_block": {"type": "tool_use", "id": "c", "name": "search", "input": {}}}),
json!({"type": "content_block_delta", "index": 2, "delta": {"type": "input_json_delta", "partial_json": "{\"pattern\": \"gadget\", \"ignore_case\": true, \"glob\": \"*.md\"}"}}),
json!({"type": "content_block_stop", "index": 2}),
json!({"type": "message_delta", "delta": {"stop_reason": "tool_use"}}),
json!({"type": "message_stop"}),
]),
),
(200, says("Two files mention a widget.")),
]);
let mut harness = harness(place.app(Some(&server.address)));
say(&mut harness, "where is a widget?");
let (any_case, is_error) = result_of(&server, 1, "a");
assert!(!is_error, "{any_case}");
assert_eq!(any_case, "docs/guide.md:1: the Widget is here\nsrc/main.rs:1: let widget = 1;");
let (one_folder, is_error) = result_of(&server, 1, "b");
assert!(!is_error, "{one_folder}");
assert_eq!(one_folder, "src/main.rs:1: let widget = 1;");
let (none, is_error) = result_of(&server, 1, "c");
assert!(!is_error, "{none}");
assert_eq!(none, "No matches for `gadget` in `.`. Try `ignore_case` for the same text in another case.");
let screen = harness.screen();
assert!(screen.contains("search widget"), "{screen}");
assert!(screen.contains("main.rs:1"), "the person follows the matches: {screen}");
}
#[test]
fn the_request_carries_the_five_tools_with_the_new_fields() {
let place = Place::new("tools");
let server = FakeServer::start(vec![(200, says("Hello."))]);
let mut harness = harness(place.app(Some(&server.address)));
say(&mut harness, "hello");
let request = &server.received()[0].body;
let tools = request["tools"].as_array().expect("the five tools");
assert_eq!(tools.len(), 5, "the five tools are still five");
let names: Vec<&str> = tools.iter().map(|tool| tool["name"].as_str().expect("a name")).collect();
assert_eq!(names, ["read_file", "list_dir", "search", "edit_file", "run_command"]);
let field = |tool: &str, name: &str| {
let schema = &tools[tools.iter().position(|entry| entry["name"] == tool).expect("a tool")]["input_schema"];
assert_eq!(schema["properties"][name]["type"], tool_type_of(name), "{tool}.{name}");
schema["properties"][name]["description"].as_str().expect("a description").to_owned()
};
for (tool, name) in [("read_file", "start_line"), ("read_file", "end_line"), ("list_dir", "depth")] {
assert!(field(tool, name).contains("500") || field(tool, name).contains("2"), "{tool}.{name} has no example");
}
assert!(field("search", "glob").contains("src/*.rs"), "the glob names an example");
assert!(field("search", "ignore_case").contains("Widget"), "the flag names an example");
assert_eq!(tools[0]["input_schema"]["required"], json!(["path"]));
assert_eq!(tools[2]["input_schema"]["required"], json!(["pattern"]));
let system = request["system"].as_str().expect("the system prompt");
for said in ["list_dir", "start_line", "end_line", "ignore_case", "glob"] {
assert!(system.contains(said), "`{said}` is not in the prompt:\n{system}");
}
assert!(harness.screen().contains("Hello."), "{}", harness.screen());
}
fn tool_type_of(name: &str) -> &'static str {
match name {
"ignore_case" => "boolean",
"glob" => "string",
_ => "integer",
}
}
#[test]
fn a_new_field_does_not_open_a_way_out_of_the_working_folder() {
let place = Place::new("confine-fields");
let outside_folder = place.work().parent().expect("the test's own folder").to_owned();
let outside = outside_folder.join("secret.txt");
std::fs::write(&outside, "the secret is out\n").expect("write outside");
let server = FakeServer::start(vec![
(
200,
crate::provider::fake::sse(&[
json!({"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "a", "name": "read_file", "input": {}}}),
json!({"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"../secret.txt\", \"start_line\": 1, \"end_line\": 1}"}}),
json!({"type": "content_block_stop", "index": 0}),
json!({"type": "content_block_start", "index": 1, "content_block": {"type": "tool_use", "id": "b", "name": "search", "input": {}}}),
json!({"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"pattern\": \"secret\", \"path\": \"..\", \"glob\": \"*\"}"}}),
json!({"type": "content_block_stop", "index": 1}),
json!({"type": "content_block_start", "index": 2, "content_block": {"type": "tool_use", "id": "c", "name": "search", "input": {}}}),
json!({"type": "content_block_delta", "index": 2, "delta": {"type": "input_json_delta", "partial_json": "{\"pattern\": \"secret\", \"glob\": \"../*\"}"}}),
json!({"type": "content_block_stop", "index": 2}),
json!({"type": "message_delta", "delta": {"stop_reason": "tool_use"}}),
json!({"type": "message_stop"}),
]),
),
(200, says("I cannot read outside the folder.")),
]);
let mut harness = harness(place.app(Some(&server.address)));
say(&mut harness, "read the secret above this folder");
let (read_out, is_error) = result_of(&server, 1, "a");
assert!(is_error, "{read_out}");
assert_eq!(read_out, "`../secret.txt` is outside the working folder; only paths inside it can be used.");
let (search_out, is_error) = result_of(&server, 1, "b");
assert!(is_error, "{search_out}");
assert!(search_out.contains("outside the working folder"), "{search_out}");
let (glob_out, is_error) = result_of(&server, 1, "c");
assert!(!is_error, "{glob_out}");
assert!(glob_out.starts_with("No matches for `secret` in `.`."), "{glob_out}");
assert_eq!(std::fs::read_to_string(&outside).expect("read outside"), "the secret is out\n", "nothing was read");
assert!(harness.screen().contains("outside the working folder"), "{}", harness.screen());
}
#[test]
fn an_empty_folder_and_a_file_that_is_not_text_give_a_sentence() {
let place = Place::new("awkward");
std::fs::create_dir_all(place.work().join("empty")).expect("folder");
std::fs::write(place.work().join("image.bin"), b"\x89PNG\r\n\x1a\n\0\0binary").expect("write");
let server = FakeServer::start(vec![
(
200,
crate::provider::fake::sse(&[
json!({"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "a", "name": "list_dir", "input": {}}}),
json!({"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"empty\", \"depth\": 3}"}}),
json!({"type": "content_block_stop", "index": 0}),
json!({"type": "content_block_start", "index": 1, "content_block": {"type": "tool_use", "id": "b", "name": "read_file", "input": {}}}),
json!({"type": "content_block_delta", "index": 1, "delta": {"type": "input_json_delta", "partial_json": "{\"path\": \"image.bin\", \"start_line\": 1, \"end_line\": 2}"}}),
json!({"type": "content_block_stop", "index": 1}),
json!({"type": "message_delta", "delta": {"stop_reason": "tool_use"}}),
json!({"type": "message_stop"}),
]),
),
(200, says("The folder is empty and the file is not text.")),
]);
let mut harness = harness(place.app(Some(&server.address)));
assert!(harness.screen().contains("Ask anything about this folder"), "{}", harness.screen());
say(&mut harness, "what is in empty, and what is in image.bin?");
let (empty, is_error) = result_of(&server, 1, "a");
assert!(!is_error, "{empty}");
assert_eq!(empty, "`empty` is empty.");
let (binary, is_error) = result_of(&server, 1, "b");
assert!(is_error, "{binary}");
assert!(binary.contains("is not a text file"), "{binary}");
let system = server.received()[0].body["system"].as_str().expect("the system prompt").to_owned();
assert!(system.contains("You are qcli"), "{system}");
assert!(!system.contains("AGENTS.md says"), "the folder has no instructions of its own: {system}");
let screen = harness.screen();
assert!(screen.contains("is empty"), "{screen}");
assert!(screen.contains("is not a text file"), "{screen}");
}