use super::*;
use std::path::PathBuf;
fn tmp_workspace() -> (tempfile::TempDir, PathBuf) {
let dir = tempfile::tempdir().unwrap();
let root = std::fs::canonicalize(dir.path()).unwrap();
(dir, root)
}
#[test]
fn relative_path_within_workspace_ok() {
let (_dir, root) = tmp_workspace();
std::fs::create_dir_all(root.join("src")).unwrap();
std::fs::write(root.join("src/main.rs"), "fn main() {}").unwrap();
let result = resolve_workspace_path_with_allowed(&root, "src/main.rs", false, &[]);
assert!(result.is_ok(), "got: {:?}", result);
assert!(result.unwrap().starts_with(&root));
}
#[test]
fn absolute_path_in_allowed_list_ok() {
let allowed_dir = tempfile::tempdir().unwrap();
let allowed = std::fs::canonicalize(allowed_dir.path()).unwrap();
std::fs::write(allowed.join("data.txt"), "hello").unwrap();
let (_dir, root) = tmp_workspace();
let result = resolve_workspace_path_with_allowed(
&root,
allowed.join("data.txt").to_str().unwrap(),
false,
std::slice::from_ref(&allowed),
);
assert!(result.is_ok(), "got: {:?}", result);
}
#[test]
fn absolute_path_not_in_allowed_rejected() {
let (_dir, root) = tmp_workspace();
let outside = tempfile::tempdir().unwrap();
let outside_path = std::fs::canonicalize(outside.path()).unwrap();
std::fs::write(outside_path.join("secret.txt"), "nope").unwrap();
let result = resolve_workspace_path_with_allowed(
&root,
outside_path.join("secret.txt").to_str().unwrap(),
false,
&[], );
assert!(result.is_err());
let msg = result.unwrap_err().message;
assert!(
msg.contains("Sandbox boundary") || msg.contains("outside"),
"unexpected error: {msg}"
);
}
#[test]
fn path_traversal_blocked() {
let (_dir, root) = tmp_workspace();
let result = resolve_workspace_path_with_allowed(&root, "../../etc/passwd", false, &[]);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("path traversal"));
}
#[test]
fn allowed_path_nonexistent_with_flag() {
let allowed_dir = tempfile::tempdir().unwrap();
let allowed = std::fs::canonicalize(allowed_dir.path()).unwrap();
let target = allowed.join("does_not_exist.txt");
let (_dir, root) = tmp_workspace();
let result = resolve_workspace_path_with_allowed(
&root,
target.to_str().unwrap(),
true, std::slice::from_ref(&allowed),
);
assert!(result.is_ok(), "got: {:?}", result);
}
#[test]
fn allowed_path_nonexistent_without_flag() {
let allowed_dir = tempfile::tempdir().unwrap();
let allowed = std::fs::canonicalize(allowed_dir.path()).unwrap();
let target = allowed.join("does_not_exist.txt");
let (_dir, root) = tmp_workspace();
let result = resolve_workspace_path_with_allowed(
&root,
target.to_str().unwrap(),
false, std::slice::from_ref(&allowed),
);
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("does not exist"));
}
#[cfg(unix)]
#[test]
fn symlink_escape_blocked() {
let (_dir, root) = tmp_workspace();
let outside = tempfile::tempdir().unwrap();
let outside_path = std::fs::canonicalize(outside.path()).unwrap();
std::fs::write(outside_path.join("secret.txt"), "secret").unwrap();
let link_path = root.join("sneaky_link");
std::os::unix::fs::symlink(outside_path.join("secret.txt"), &link_path).unwrap();
let result = resolve_workspace_path_with_allowed(&root, "sneaky_link", false, &[]);
assert!(result.is_err(), "symlink escape should be blocked");
}
#[test]
fn empty_allowed_paths_rejects_all_absolute() {
let outside = tempfile::tempdir().unwrap();
let outside_path = std::fs::canonicalize(outside.path()).unwrap();
std::fs::write(outside_path.join("file.txt"), "data").unwrap();
let (_dir, root) = tmp_workspace();
let result = resolve_workspace_path_with_allowed(
&root,
outside_path.join("file.txt").to_str().unwrap(),
false,
&[], );
assert!(result.is_err());
}
#[test]
fn workspace_root_itself_ok() {
let (_dir, root) = tmp_workspace();
let result = resolve_workspace_path_with_allowed(&root, ".", false, &[]);
assert!(result.is_ok(), "got: {:?}", result);
}
#[test]
fn dot_path_resolves_to_workspace() {
let (_dir, root) = tmp_workspace();
let resolved = resolve_workspace_path_with_allowed(&root, ".", false, &[]).unwrap();
assert_eq!(
std::fs::canonicalize(&resolved).unwrap(),
std::fs::canonicalize(&root).unwrap()
);
}
#[test]
fn multiple_allowed_paths_any_match() {
let dir_a = tempfile::tempdir().unwrap();
let dir_b = tempfile::tempdir().unwrap();
let path_a = std::fs::canonicalize(dir_a.path()).unwrap();
let path_b = std::fs::canonicalize(dir_b.path()).unwrap();
std::fs::write(path_b.join("b_file.txt"), "in b").unwrap();
let (_dir, root) = tmp_workspace();
let result = resolve_workspace_path_with_allowed(
&root,
path_b.join("b_file.txt").to_str().unwrap(),
false,
&[path_a, path_b],
);
assert!(result.is_ok(), "got: {:?}", result);
}