use brainwires_core::ToolContext;
use brainwires_tools::FileOpsTool;
use proptest::prelude::*;
use std::fs;
use tempfile::TempDir;
fn ctx(dir: &std::path::Path) -> ToolContext {
ToolContext {
working_directory: dir.to_string_lossy().to_string(),
..Default::default()
}
}
#[test]
fn relative_path_resolves_against_working_directory() {
let tmp = TempDir::new().unwrap();
let wd = tmp.path().canonicalize().unwrap();
let child = wd.join("data.txt");
fs::write(&child, "hi").unwrap();
let resolved = FileOpsTool::resolve_path("data.txt", &ctx(&wd)).unwrap();
assert_eq!(resolved, child);
}
#[test]
fn absolute_path_is_returned_verbatim_when_canonical() {
let tmp = TempDir::new().unwrap();
let wd = tmp.path().canonicalize().unwrap();
let target = wd.join("abs.txt");
fs::write(&target, "x").unwrap();
let resolved = FileOpsTool::resolve_path(target.to_str().unwrap(), &ctx(&wd)).unwrap();
assert_eq!(resolved, target);
}
#[test]
fn nonexistent_relative_path_still_anchors_against_working_directory() {
let tmp = TempDir::new().unwrap();
let wd = tmp.path().canonicalize().unwrap();
let resolved = FileOpsTool::resolve_path("missing_subdir/new.txt", &ctx(&wd)).unwrap();
assert_eq!(resolved, wd.join("missing_subdir").join("new.txt"));
}
#[test]
fn dotdot_traversal_is_not_blocked_current_behaviour() {
let tmp_root = TempDir::new().unwrap();
let root = tmp_root.path().canonicalize().unwrap();
let inner = root.join("inner");
fs::create_dir_all(&inner).unwrap();
let sibling = root.join("sibling.txt");
fs::write(&sibling, "stolen").unwrap();
let resolved = FileOpsTool::resolve_path("../sibling.txt", &ctx(&inner)).unwrap();
assert_eq!(
resolved, sibling,
"FYI: resolve_path currently permits `..` escapes — document in changelog if you fix",
);
}
#[test]
fn nested_relative_path_resolves_correctly() {
let tmp = TempDir::new().unwrap();
let wd = tmp.path().canonicalize().unwrap();
let nested = wd.join("a").join("b");
fs::create_dir_all(&nested).unwrap();
let file = nested.join("c.txt");
fs::write(&file, "k").unwrap();
let resolved = FileOpsTool::resolve_path("a/b/c.txt", &ctx(&wd)).unwrap();
assert_eq!(resolved, file);
}
proptest! {
#[test]
fn resolve_path_never_panics_on_arbitrary_string(input in ".{0,100}") {
let tmp = TempDir::new().unwrap();
let wd = tmp.path().canonicalize().unwrap();
let _ = FileOpsTool::resolve_path(&input, &ctx(&wd));
}
#[test]
fn unicode_path_roundtrips_through_resolution(
prefix in "[a-z]{1,5}",
suffix in "[a-z]{1,5}",
) {
let tmp = TempDir::new().unwrap();
let wd = tmp.path().canonicalize().unwrap();
let name = format!("{prefix}é{suffix}.txt");
let resolved = FileOpsTool::resolve_path(&name, &ctx(&wd)).unwrap();
prop_assert_eq!(resolved, wd.join(&name));
}
}