use crate::brain::tools::Tool;
use crate::brain::tools::ToolExecutionContext;
use crate::brain::tools::edit::EditTool;
use crate::brain::tools::file_versions::forget_session;
use crate::brain::tools::hashline::edit::HashlineEditTool;
use crate::brain::tools::hashline::hash::hash_line;
use crate::brain::tools::write::WriteTool;
use serde_json::json;
use tempfile::TempDir;
use uuid::Uuid;
const ORIGINAL: &str = "apples=3\nbananas=5\ncherries=7\ndates=9\n";
const REPLACEMENT: &str = "kiwis=1\nlemons=2\n";
fn ctx(dir: &std::path::Path, session: Uuid) -> ToolExecutionContext {
ToolExecutionContext::new(session)
.with_working_directory(dir.to_path_buf())
.with_auto_approve(true)
}
async fn seed(dir: &std::path::Path, session: Uuid, path: &std::path::Path) {
let result = WriteTool
.execute(
json!({
"path": path.to_str().unwrap(),
"content": ORIGINAL,
}),
&ctx(dir, session),
)
.await
.expect("write_file create");
assert!(result.success, "seed write failed: {:?}", result.output);
}
async fn overwrite(dir: &std::path::Path, session: Uuid, path: &std::path::Path) -> String {
let result = WriteTool
.execute(
json!({
"path": path.to_str().unwrap(),
"content": REPLACEMENT,
"overwrite_read_confirm": true,
}),
&ctx(dir, session),
)
.await
.expect("write_file overwrite");
assert!(
result.success,
"a whole-file write after this session's own edit must not be refused: {}",
result.output
);
result.output
}
#[tokio::test]
async fn a_write_after_edit_file_is_not_refused() {
let dir = TempDir::new().unwrap();
let session = Uuid::new_v4();
let path = dir.path().join("inventory.txt");
seed(dir.path(), session, &path).await;
let edited = EditTool
.execute(
json!({
"path": path.to_str().unwrap(),
"operation": "replace",
"old_text": "bananas=5",
"new_text": "bananas=6",
}),
&ctx(dir.path(), session),
)
.await
.expect("edit_file");
assert!(edited.success, "edit failed: {:?}", edited.output);
assert!(
std::fs::read_to_string(&path)
.unwrap()
.contains("bananas=6")
);
overwrite(dir.path(), session, &path).await;
assert_eq!(std::fs::read_to_string(&path).unwrap(), REPLACEMENT);
forget_session(session);
}
#[tokio::test]
async fn a_write_after_hashline_edit_is_not_refused() {
let dir = TempDir::new().unwrap();
let session = Uuid::new_v4();
let path = dir.path().join("inventory.txt");
seed(dir.path(), session, &path).await;
let edited = HashlineEditTool
.execute(
json!({
"path": path.to_str().unwrap(),
"edits": [{
"op": "replace",
"pos": format!("2#{}", hash_line("bananas=5")),
"lines": "bananas=6"
}],
}),
&ctx(dir.path(), session),
)
.await
.expect("hashline_edit");
assert!(edited.success, "hashline edit failed: {:?}", edited.output);
overwrite(dir.path(), session, &path).await;
assert_eq!(std::fs::read_to_string(&path).unwrap(), REPLACEMENT);
forget_session(session);
}
#[tokio::test]
async fn a_write_after_another_writer_moved_the_file_is_still_refused() {
let dir = TempDir::new().unwrap();
let session = Uuid::new_v4();
let path = dir.path().join("inventory.txt");
seed(dir.path(), session, &path).await;
let edited = EditTool
.execute(
json!({
"path": path.to_str().unwrap(),
"operation": "replace",
"old_text": "bananas=5",
"new_text": "bananas=6",
}),
&ctx(dir.path(), session),
)
.await
.expect("edit_file");
assert!(edited.success);
std::fs::write(&path, "someone else was here\n").unwrap();
let refused = WriteTool
.execute(
json!({
"path": path.to_str().unwrap(),
"content": REPLACEMENT,
"overwrite_read_confirm": true,
}),
&ctx(dir.path(), session),
)
.await
.expect("write_file");
assert!(
!refused.success,
"a concurrent change must still be refused, got: {:?}",
refused.output
);
assert_eq!(
std::fs::read_to_string(&path).unwrap(),
"someone else was here\n",
"the refused write must not touch the file"
);
forget_session(session);
}