#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use fren_date::{execute, plan_with_year, ConflictPolicy, PlanOpts, SlugOpts};
use slug_preserve::CaseMode;
use std::fs;
use tempfile::TempDir;
fn touch(path: impl AsRef<std::path::Path>) {
let path = path.as_ref();
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).unwrap();
}
fs::write(path, b"x").unwrap();
}
fn rust_default_opts() -> SlugOpts {
SlugOpts {
separator: '-',
case: CaseMode::Preserve,
split_camel: false,
}
}
#[test]
fn renames_files_with_dates() {
let tmp = TempDir::new().unwrap();
touch(tmp.path().join("Photo 2024-01-15.jpg"));
let plans = plan_with_year(
&[tmp.path()],
&rust_default_opts(),
&PlanOpts {
recursive: true,
..PlanOpts::default()
},
2024,
)
.unwrap();
let report = execute(&plans).unwrap();
assert_eq!(report.applied, plans.len(), "all plans should apply");
assert_eq!(report.errors.len(), 0);
assert!(tmp.path().join("Photo-2024-01-15.jpg").exists());
assert!(!tmp.path().join("Photo 2024-01-15.jpg").exists());
}
#[test]
fn bottom_up_renames_dir_after_its_files() {
let tmp = TempDir::new().unwrap();
touch(tmp.path().join("My Vacation").join("photo 1.jpg"));
touch(tmp.path().join("My Vacation").join("photo 2.jpg"));
let plans = plan_with_year(
&[tmp.path()],
&rust_default_opts(),
&PlanOpts {
recursive: true,
..PlanOpts::default()
},
2024,
)
.unwrap();
let report = execute(&plans).unwrap();
assert_eq!(report.applied, plans.len());
let new_dir = tmp.path().join("My-Vacation");
assert!(new_dir.exists(), "renamed dir must exist");
assert!(new_dir.join("photo-1.jpg").exists());
assert!(new_dir.join("photo-2.jpg").exists());
}
#[test]
fn aborts_when_target_exists_outside_batch() {
let tmp = TempDir::new().unwrap();
touch(tmp.path().join("Hello World.txt"));
touch(tmp.path().join("Hello-World.txt"));
let result = plan_with_year(
&[tmp.path()],
&rust_default_opts(),
&PlanOpts {
recursive: true,
on_conflict: ConflictPolicy::Abort,
..PlanOpts::default()
},
2024,
);
assert!(matches!(result, Err(fren_date::FrenError::TargetExists(_))));
assert!(tmp.path().join("Hello World.txt").exists());
assert!(tmp.path().join("Hello-World.txt").exists());
}
#[test]
fn does_not_overwrite_existing_file_on_execute() {
let tmp = TempDir::new().unwrap();
touch(tmp.path().join("source.txt"));
let plans = vec![fren_date::RenamePlan {
original_path: tmp.path().join("source.txt"),
parent: tmp.path().to_path_buf(),
old_name: "source.txt".into(),
new_name: "target.txt".into(),
depth: 1,
kind: fren_date::ItemKind::File,
detected_date: None,
batch_id: uuid::Uuid::nil(),
}];
touch(tmp.path().join("target.txt"));
fs::write(tmp.path().join("target.txt"), b"PRESERVE_ME").unwrap();
let report = execute(&plans).unwrap();
assert_eq!(report.applied, 0, "execute should refuse to overwrite");
assert_eq!(report.errors.len(), 1);
let content = fs::read(tmp.path().join("target.txt")).unwrap();
assert_eq!(content, b"PRESERVE_ME");
}
#[test]
fn empty_plans_succeed_with_zero_applied() {
let report = execute(&[]).unwrap();
assert_eq!(report.applied, 0);
assert_eq!(report.errors.len(), 0);
}
#[test]
#[cfg(unix)]
fn symlink_renamed_not_target() {
let tmp = TempDir::new().unwrap();
let target_dir = TempDir::new().unwrap();
touch(target_dir.path().join("real-file.txt"));
std::os::unix::fs::symlink(
target_dir.path().join("real-file.txt"),
tmp.path().join("My Link.txt"),
)
.unwrap();
let plans = plan_with_year(
&[tmp.path()],
&rust_default_opts(),
&PlanOpts {
recursive: true,
..PlanOpts::default()
},
2024,
)
.unwrap();
assert_eq!(plans.len(), 1, "expected exactly one plan (the symlink)");
let plan = &plans[0];
assert_eq!(plan.kind, fren_date::ItemKind::Symlink);
assert_eq!(plan.old_name, std::ffi::OsStr::new("My Link.txt"));
assert_eq!(plan.new_name, std::ffi::OsStr::new("My-Link.txt"));
let report = execute(&plans).unwrap();
assert_eq!(report.applied, 1);
assert_eq!(report.errors.len(), 0);
let new_link = tmp.path().join("My-Link.txt");
assert!(
new_link
.symlink_metadata()
.unwrap()
.file_type()
.is_symlink(),
"renamed entry must still be a symlink"
);
assert!(new_link.exists(), "symlink must still resolve after rename");
assert!(!tmp.path().join("My Link.txt").exists());
assert!(target_dir.path().join("real-file.txt").exists());
}