use crate::error::Error;
use crate::{ensure_removed, remove};
use std::fs;
use std::os::unix::fs as unix_fs;
use std::os::unix::fs::PermissionsExt;
use tempfile::TempDir;
fn tmp() -> TempDir {
tempfile::tempdir().expect("failed to create temp dir")
}
#[test]
fn remove_a_regular_file() {
let dir = tmp();
let file = dir.path().join("file.txt");
fs::write(&file, b"hello").unwrap();
remove(&file).expect("should remove file");
assert!(!file.exists());
}
#[test]
fn remove_an_empty_directory() {
let dir = tmp();
let sub = dir.path().join("empty");
fs::create_dir(&sub).unwrap();
remove(&sub).expect("should remove empty dir");
assert!(!sub.exists());
}
#[test]
fn remove_a_nested_directory_tree() {
let dir = tmp();
let root = dir.path().join("root");
fs::create_dir_all(root.join("a/b/c")).unwrap();
fs::write(root.join("a/b/c/file.txt"), b"deep").unwrap();
fs::write(root.join("a/file.txt"), b"mid").unwrap();
remove(&root).expect("should remove nested tree");
assert!(!root.exists());
}
#[test]
fn remove_a_symlink_without_following_it() {
let dir = tmp();
let target = dir.path().join("target");
let link = dir.path().join("link");
fs::create_dir(&target).unwrap();
unix_fs::symlink(&target, &link).unwrap();
remove(&link).expect("should remove symlink");
assert!(!link.exists(), "symlink should be gone");
assert!(target.exists(), "target must survive");
}
#[test]
fn remove_returns_not_found_for_missing_path() {
let dir = tmp();
let missing = dir.path().join("ghost");
let err = remove(&missing).expect_err("should fail");
assert!(matches!(err, Error::NotFound));
}
#[test]
fn remove_rejects_dotdot_as_last_segment() {
let dir = tmp();
let sub = dir.path().join("sub");
fs::create_dir(&sub).unwrap();
let dotdot = sub.join("..");
let err = remove(&dotdot).expect_err("should reject '..'");
assert!(matches!(err, Error::InvalidTarget(_)));
}
#[test]
fn remove_a_readonly_file() {
let dir = tmp();
let file = dir.path().join("ro.txt");
fs::write(&file, b"locked").unwrap();
let mut perms = fs::metadata(&file).unwrap().permissions();
perms.set_mode(0o444);
fs::set_permissions(&file, perms).unwrap();
remove(&file).expect("should remove read-only file");
assert!(!file.exists());
}
#[test]
fn remove_a_directory_without_read_permission() {
if unsafe { libc::getuid() } == 0 {
return;
}
let dir = tmp();
let locked = dir.path().join("locked");
fs::create_dir(&locked).unwrap();
fs::write(locked.join("file.txt"), b"hi").unwrap();
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&locked).unwrap().permissions();
perms.set_mode(0o000);
fs::set_permissions(&locked, perms).unwrap();
let result = remove(&locked);
if locked.exists() {
if let Ok(meta) = fs::symlink_metadata(&locked) {
let mut perms = meta.permissions();
perms.set_mode(0o755);
let _ = fs::set_permissions(&locked, perms);
}
}
result.expect("should remove directory even without read permission");
assert!(!locked.exists());
}
#[test]
fn ensure_removed_succeeds_when_path_does_not_exist() {
let dir = tmp();
let missing = dir.path().join("never_existed");
ensure_removed(&missing).expect("should succeed silently");
}
#[test]
fn ensure_removed_removes_existing_file() {
let dir = tmp();
let file = dir.path().join("file.txt");
fs::write(&file, b"data").unwrap();
ensure_removed(&file).expect("should remove existing file");
assert!(!file.exists());
}
#[test]
fn ensure_removed_removes_existing_tree() {
let dir = tmp();
let root = dir.path().join("tree");
fs::create_dir_all(root.join("x/y")).unwrap();
fs::write(root.join("x/y/z.txt"), b"leaf").unwrap();
ensure_removed(&root).expect("should remove existing tree");
assert!(!root.exists());
}
#[test]
fn ensure_removed_is_idempotent() {
let dir = tmp();
let file = dir.path().join("once.txt");
fs::write(&file, b"x").unwrap();
ensure_removed(&file).expect("first call");
ensure_removed(&file).expect("second call on already-removed path");
}