use super::{Identity, MAX_DEPTH, OpenFailure, Store};
use crate::command_test_support::{fixture, remove};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
fn folder_in(root: &Path) -> (PathBuf, PathBuf) {
let common: PathBuf = root.join("repo.git");
let folder: PathBuf = common.join("cli-git/discarded");
std::fs::create_dir_all(&folder).expect("the folder");
return (common, folder);
}
#[test]
fn a_link_at_either_component_is_refused_and_an_absent_folder_is_nothing() {
let root: PathBuf = fixture("store-open");
let (common, folder) = folder_in(root.as_path());
assert!(Store::open(common.as_path(), "cli-git/discarded").expect("opens").is_some());
assert!(Store::open(root.as_path(), "cli-git/discarded").expect("absent is not a failure").is_none());
let elsewhere: PathBuf = root.join("elsewhere");
std::fs::create_dir(&elsewhere).expect("elsewhere");
std::fs::remove_dir(&folder).expect("remove the folder");
std::os::unix::fs::symlink(&elsewhere, &folder).expect("link");
assert!(matches!(Store::open(common.as_path(), "cli-git/discarded"), Err(OpenFailure::Link)));
std::fs::remove_file(&folder).expect("remove the link");
std::fs::write(&folder, "a file").expect("a file where the folder belongs");
assert!(matches!(Store::open(common.as_path(), "cli-git/discarded"), Err(OpenFailure::Link)), "a file is not a folder");
remove(root.as_path());
}
#[test]
fn a_link_at_the_common_directory_itself_is_followed() {
let root: PathBuf = fixture("store-common-link");
let (common, _) = folder_in(root.as_path());
let through: PathBuf = root.join("through");
std::os::unix::fs::symlink(&common, &through).expect("link");
assert!(Store::open(through.as_path(), "cli-git/discarded").expect("opens").is_some());
remove(root.as_path());
}
#[test]
fn a_survey_counts_and_a_removal_takes_the_same_files_and_never_enters_a_link() {
let root: PathBuf = fixture("store-walk");
let (common, folder) = folder_in(root.as_path());
let entry: PathBuf = folder.join("2026-11-02T14-01-07");
std::fs::create_dir_all(entry.join("a/b")).expect("tree");
std::fs::write(entry.join("top.txt"), "12345").expect("file");
std::fs::write(entry.join("a/mid.txt"), "123").expect("file");
std::fs::write(entry.join("a/b/deep.txt"), "1").expect("file");
let outside: PathBuf = root.join("elsewhere");
std::fs::create_dir(&outside).expect("elsewhere");
std::fs::write(outside.join("precious.txt"), "not ours").expect("file");
std::os::unix::fs::symlink(&outside, entry.join("link")).expect("link");
let store: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
let directory = store.open_directory(OsStr::new("2026-11-02T14-01-07")).expect("opens").expect("exists");
let surveyed = directory.survey().expect("surveyed");
let mut names: Vec<String> = surveyed.names.clone();
names.sort();
assert_eq!(names, vec!["a/b/deep.txt", "a/mid.txt", "link", "top.txt"]);
assert_eq!(surveyed.bytes, 5 + 3 + 1 + u128::try_from(outside.as_os_str().len()).expect("small"), "a link counts by its own size");
let emptied = directory.empty().expect("emptied");
assert_eq!(emptied.bytes, surveyed.bytes);
assert_eq!(std::fs::read_dir(&entry).expect("the directory stays").count(), 0);
assert_eq!(std::fs::read_to_string(outside.join("precious.txt")).expect("untouched"), "not ours");
remove(root.as_path());
}
#[test]
fn files_go_before_subdirectories() {
let root: PathBuf = fixture("store-order");
let (common, folder) = folder_in(root.as_path());
let entry: PathBuf = folder.join("2026-11-02T14-01-07");
std::fs::create_dir_all(entry.join("sub")).expect("tree");
std::fs::write(entry.join("sub/inner.txt"), "x").expect("file");
std::fs::write(entry.join("z-last-by-name.txt"), "y").expect("file");
let store: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
let directory = store.open_directory(OsStr::new("2026-11-02T14-01-07")).expect("opens").expect("exists");
let emptied = directory.empty().expect("emptied");
assert_eq!(emptied.names, vec!["z-last-by-name.txt", "sub/inner.txt"]);
remove(root.as_path());
}
#[test]
fn a_link_a_file_and_a_gone_name_are_not_directories() {
let root: PathBuf = fixture("store-kinds");
let (common, folder) = folder_in(root.as_path());
let elsewhere: PathBuf = root.join("elsewhere");
std::fs::create_dir(&elsewhere).expect("elsewhere");
std::os::unix::fs::symlink(&elsewhere, folder.join("a-link")).expect("link");
std::fs::write(folder.join("a-file"), "x").expect("file");
let store: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
for name in ["a-link", "a-file"] {
let error = store.open_directory(OsStr::new(name)).expect_err(name);
assert_eq!(error.kind(), std::io::ErrorKind::NotADirectory, "{name}");
assert_eq!(store.directory_identity(OsStr::new(name)).expect("inspected"), None, "{name}");
}
assert!(store.open_directory(OsStr::new("gone")).expect("not an error").is_none());
assert_eq!(store.directory_identity(OsStr::new("gone")).expect("inspected"), None);
remove(root.as_path());
}
#[test]
fn a_replaced_directory_has_another_identity_and_its_name_is_not_removed() {
let root: PathBuf = fixture("store-identity");
let (common, folder) = folder_in(root.as_path());
let name: &OsStr = OsStr::new("2026-11-02T14-01-07");
std::fs::create_dir(folder.join(name)).expect("directory");
let store: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
let judged: Identity = store.directory_identity(name).expect("inspected").expect("a directory");
std::fs::rename(folder.join(name), folder.join("aside")).expect("aside");
std::fs::create_dir(folder.join(name)).expect("a replacement");
assert!(!store.remove_empty_directory(name, judged).expect("refused without an error"), "the replacement was removed");
assert!(folder.join(name).is_dir());
remove(root.as_path());
}
#[test]
fn a_directory_with_content_is_not_removed_as_empty() {
let root: PathBuf = fixture("store-not-empty");
let (common, folder) = folder_in(root.as_path());
let name: &OsStr = OsStr::new("2026-11-02T14-01-07");
std::fs::create_dir(folder.join(name)).expect("directory");
std::fs::write(folder.join(name).join("kept.txt"), "x").expect("file");
let store: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
let judged: Identity = store.directory_identity(name).expect("inspected").expect("a directory");
assert!(store.remove_empty_directory(name, judged).is_err());
assert!(folder.join(name).join("kept.txt").exists());
remove(root.as_path());
}
#[test]
fn a_tree_deeper_than_the_bound_is_stopped_at() {
let root: PathBuf = fixture("store-deep");
let (common, folder) = folder_in(root.as_path());
let mut deep: PathBuf = folder.join("2026-11-02T14-01-07");
for _level in 0..MAX_DEPTH + 2 {
deep.push("d");
}
std::fs::create_dir_all(&deep).expect("a deep tree");
std::fs::write(deep.join("bottom.txt"), "x").expect("file");
let store: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
let directory = store.open_directory(OsStr::new("2026-11-02T14-01-07")).expect("opens").expect("exists");
let interrupted = directory.survey().expect_err("too deep");
assert!(interrupted.tally.names.is_empty());
assert!(interrupted.error.to_string().contains("deeper"), "{}", interrupted.error);
assert!(deep.join("bottom.txt").exists());
remove(root.as_path());
}
#[test]
fn the_lock_is_exclusive_and_released_with_the_store() {
let root: PathBuf = fixture("store-lock");
let (common, _) = folder_in(root.as_path());
let first: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
let second: Store = Store::open(common.as_path(), "cli-git/discarded").expect("opens").expect("exists");
assert!(first.try_lock().expect("locked"));
assert!(!second.try_lock().expect("asked"), "a second holder got the lock");
drop(first);
assert!(second.try_lock().expect("locked after release"));
remove(root.as_path());
}