use crate::validation::{validate_path, validate_path_relative_to};
#[test]
fn test_validate_path_rejects_absolute_path_outside_cwd() {
let result = validate_path("/etc/passwd", true);
assert!(
result.is_err(),
"validate_path should reject /etc/passwd (outside CWD)"
);
let err = result.unwrap_err();
let err_msg = err.message.to_lowercase();
assert!(
err_msg.contains("outside") || err_msg.contains("not found"),
"Error message should mention 'outside' or 'not found': {}",
err.message
);
}
#[test]
fn test_validate_path_accepts_relative_path_in_cwd() {
let result = validate_path("Cargo.toml", true);
assert!(
result.is_ok(),
"validate_path should accept Cargo.toml (exists in CWD)"
);
}
#[test]
fn test_validate_path_creates_parent_for_nonexistent_file() {
let cwd = std::env::current_dir().expect("should get cwd");
let parent = tempfile::TempDir::new_in(&cwd).expect("should create temp dir in cwd");
let parent_path = parent.path().to_path_buf();
let child = parent_path.join("new_file.txt");
let child_str = child.to_str().expect("path should be valid UTF-8");
let result = validate_path(child_str, false);
assert!(
result.is_ok(),
"validate_path should accept non-existent file with existing parent (require_exists=false)"
);
let path = result.unwrap();
let canonical_cwd = std::fs::canonicalize(&cwd).expect("should canonicalize cwd");
assert!(
path.starts_with(&canonical_cwd),
"Resolved path should be within CWD: {:?} should start with {:?}",
path,
canonical_cwd
);
}
#[test]
fn test_edit_overwrite_with_working_dir() {
let cwd = std::env::current_dir().expect("should get cwd");
let temp_dir = tempfile::TempDir::new_in(&cwd).expect("should create temp dir in cwd");
let temp_path = temp_dir.path();
let result = validate_path_relative_to("test_file.txt", false, temp_path);
assert!(
result.is_ok(),
"validate_path_relative_to should accept relative path in valid working_dir: {:?}",
result.err()
);
let resolved = result.unwrap();
assert!(
resolved.starts_with(temp_path),
"Resolved path should be within working_dir: {:?} should start with {:?}",
resolved,
temp_path
);
}
#[test]
fn test_validate_path_in_dir_accepts_outside_cwd() {
let temp_dir = std::env::temp_dir();
let canonical_temp_dir =
std::fs::canonicalize(&temp_dir).expect("should canonicalize temp_dir");
let result = validate_path_relative_to("probe.txt", false, &temp_dir);
assert!(
result.is_ok(),
"validate_path_relative_to should accept working_dir outside CWD: {:?}",
result.err()
);
let resolved = result.unwrap();
assert!(
resolved.starts_with(&canonical_temp_dir),
"Resolved path should be within working_dir: {:?} should start with {:?}",
resolved,
canonical_temp_dir
);
}
#[test]
fn test_edit_replace_with_working_dir() {
let cwd = std::env::current_dir().expect("should get cwd");
let temp_dir = tempfile::TempDir::new_in(&cwd).expect("should create temp dir in cwd");
let temp_path = temp_dir.path();
let file_path = temp_path.join("test.txt");
std::fs::write(&file_path, "hello world").expect("should write test file");
let result = validate_path_relative_to("test.txt", true, temp_path);
assert!(
result.is_ok(),
"validate_path_relative_to should find existing file in working_dir: {:?}",
result.err()
);
let resolved = result.unwrap();
assert_eq!(
resolved, file_path,
"Resolved path should match the actual file path"
);
}
#[test]
fn test_edit_overwrite_no_working_dir() {
let result = validate_path("Cargo.toml", true);
assert!(
result.is_ok(),
"validate_path should still work without working_dir"
);
}
#[test]
fn test_edit_overwrite_working_dir_is_file() {
let cwd = std::env::current_dir().expect("should get cwd");
let temp_dir = tempfile::TempDir::new_in(&cwd).expect("should create temp dir in cwd");
let temp_file = temp_dir.path().join("test_file.txt");
std::fs::write(&temp_file, "test content").expect("should write test file");
let result = validate_path_relative_to("some_file.txt", false, &temp_file);
assert!(
result.is_err(),
"validate_path_relative_to should reject a file as working_dir"
);
let err = result.unwrap_err();
let err_msg = err.message.to_lowercase();
assert!(
err_msg.contains("directory"),
"Error message should mention 'directory': {}",
err.message
);
}
#[test]
fn test_validate_path_in_dir_traversal_to_sibling_accepted_with_working_dir() {
let cwd = std::env::current_dir().expect("should get cwd");
let parent = tempfile::TempDir::new_in(&cwd).expect("should create parent temp dir");
let allowed = parent.path().join("allowed");
let sibling = parent.path().join("allowed_sibling");
std::fs::create_dir_all(&allowed).expect("should create allowed dir");
std::fs::create_dir_all(&sibling).expect("should create sibling dir");
let result = validate_path_relative_to("../allowed_sibling/secret.txt", false, &allowed);
assert!(
result.is_ok(),
"validate_path_relative_to must accept a path resolving outside working_dir; \
containment is operator responsibility, not per-call: {:?}",
result.err()
);
let resolved = result.unwrap();
let canonical_sibling = std::fs::canonicalize(&sibling).expect("should canonicalize sibling");
assert!(
resolved.starts_with(&canonical_sibling),
"Resolved path should be inside the sibling dir: {resolved:?}"
);
assert_eq!(
resolved.file_name().and_then(|n| n.to_str()),
Some("secret.txt")
);
}
#[test]
fn test_validate_path_in_dir_rejects_sibling_prefix_validate_path() {
let result = validate_path("/etc/passwd", true);
assert!(
result.is_err(),
"validate_path must reject absolute paths outside CWD"
);
}
#[test]
fn test_validate_path_relative_to_accepts_absolute_path_with_working_dir() {
let external = tempfile::TempDir::new().expect("should create external temp dir");
let canonical_external =
std::fs::canonicalize(external.path()).expect("should canonicalize external dir");
let abs_path = canonical_external.join("AGENTS.md");
let abs_path_str = abs_path.to_str().expect("should be valid UTF-8");
let result = validate_path_relative_to(abs_path_str, false, &canonical_external);
assert!(
result.is_ok(),
"validate_path_relative_to must accept an absolute path when working_dir is set: {:?}",
result.err()
);
let resolved = result.unwrap();
assert_eq!(
resolved, abs_path,
"Resolved path must match the supplied absolute path"
);
}
#[test]
fn test_validate_path_in_dir_nonexistent_deep_path() {
let temp_dir = tempfile::TempDir::new().expect("should create temp dir");
let result = validate_path_relative_to("a/b/c/d/new.txt", false, temp_dir.path());
assert!(
result.is_err(),
"validate_path_relative_to should reject deeply nested non-existent path"
);
}
#[test]
fn test_validate_path_in_dir_nonexistent_with_existing_parent() {
let temp_dir = tempfile::TempDir::new().expect("should create temp dir");
let sub = temp_dir.path().join("sub");
std::fs::create_dir_all(&sub).expect("should create sub dir");
let result = validate_path_relative_to("sub/new.txt", false, temp_dir.path());
assert!(
result.is_ok(),
"validate_path_relative_to should accept file in existing subdir: {:?}",
result.err()
);
let resolved = result.unwrap();
let canonical_sub = std::fs::canonicalize(&sub).expect("should canonicalize sub");
assert!(
resolved.starts_with(&canonical_sub),
"Resolved path should anchor at the existing sub/ dir: {resolved:?}"
);
assert_eq!(
resolved.file_name().and_then(|n| n.to_str()),
Some("new.txt"),
"File name component must be preserved"
);
}