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_rejects_sibling_prefix() {
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_err(),
"validate_path_relative_to must reject a path resolving to a sibling directory \
sharing the working_dir name prefix (CVE-2025-53110 pattern)"
);
let err = result.unwrap_err();
let msg = err.message.to_lowercase();
assert!(
msg.contains("outside") || msg.contains("working"),
"Error should mention 'outside' or 'working', got: {}",
err.message
);
}
#[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"
);
}