use std::{fs, path::Path};
use objects::{object::ThreadName, store::ObjectStore};
use repo::Repository;
use tempfile::TempDir;
use super::heddle;
fn delete_loose_tree(repo_root: &Path, target_hex: &str) -> bool {
let prefix = &target_hex[..2];
let rest = &target_hex[2..];
let path = repo_root
.join(".heddle/objects/trees")
.join(prefix)
.join(rest);
match fs::remove_file(&path) {
Ok(()) => true,
Err(error) if error.kind() == std::io::ErrorKind::NotFound => false,
Err(error) => panic!("failed to delete loose tree at {path:?}: {error}"),
}
}
fn current_state_tree_hex(repo_root: &Path) -> String {
let repo = Repository::open(repo_root).unwrap();
let tip = repo
.refs()
.get_thread(&ThreadName::new("main"))
.unwrap()
.expect("main thread must have a tip after capture");
let state = repo
.store()
.get_state(&tip)
.unwrap()
.expect("state must exist after capture");
state.tree.to_hex()
}
fn assert_missing_tree_error(err: &str, tree_hex: &str) {
assert!(
err.contains("missing") && err.contains("tree"),
"error must surface the missing-tree diagnostic so the operator can tell \
store corruption from a normal absent-state case; got: {err}"
);
assert!(
err.contains(tree_hex),
"error must include the missing tree's hash so the operator can correlate \
with `heddle fsck` output; got: {err}"
);
assert!(
err.contains("heddle fsck"),
"error must point at the recovery command so the operator has a next step \
instead of just a stack trace; got: {err}"
);
}
#[test]
fn test_status_missing_tree_fails_loud_not_silent_empty() {
let temp = TempDir::new().unwrap();
heddle(&["init"], Some(temp.path())).unwrap();
fs::write(temp.path().join("a.txt"), "hello\n").unwrap();
heddle(&["capture", "-m", "initial"], Some(temp.path())).unwrap();
let tree_hex = current_state_tree_hex(temp.path());
assert!(
delete_loose_tree(temp.path(), &tree_hex),
"test setup: expected to find loose tree at hash {tree_hex} to delete",
);
let err = heddle(&["status"], Some(temp.path())).expect_err(
"status against a corrupt baseline tree must fail loud; \
pre-#93 it silently rendered the entire worktree as 'added' content",
);
assert_missing_tree_error(&err, &tree_hex);
}
#[test]
fn test_ready_missing_tree_fails_loud_not_silent_empty() {
let temp = TempDir::new().unwrap();
heddle(&["init"], Some(temp.path())).unwrap();
fs::write(temp.path().join("a.txt"), "hello\n").unwrap();
heddle(&["capture", "-m", "initial"], Some(temp.path())).unwrap();
let tree_hex = current_state_tree_hex(temp.path());
assert!(
delete_loose_tree(temp.path(), &tree_hex),
"test setup: expected to find loose tree at hash {tree_hex} to delete",
);
let err = heddle(&["ready"], Some(temp.path())).expect_err(
"ready against a corrupt baseline tree must fail loud; \
pre-#93 it silently reported the worktree as dirty against an empty baseline",
);
assert_missing_tree_error(&err, &tree_hex);
}
#[test]
fn test_revert_missing_parent_tree_fails_loud_not_silent_empty() {
let temp = TempDir::new().unwrap();
heddle(&["init"], Some(temp.path())).unwrap();
fs::write(temp.path().join("a.txt"), "first\n").unwrap();
let first_capture = heddle(&["capture", "-m", "first"], Some(temp.path())).unwrap();
fs::write(temp.path().join("a.txt"), "second\n").unwrap();
heddle(&["capture", "-m", "second"], Some(temp.path())).unwrap();
let parent_tree_hex = {
let repo = Repository::open(temp.path()).unwrap();
let tip = repo
.refs()
.get_thread(&ThreadName::new("main"))
.unwrap()
.unwrap();
let second_state = repo.store().get_state(&tip).unwrap().unwrap();
let parent_id = second_state
.first_parent()
.expect("second state must have a parent");
let parent_state = repo.store().get_state(parent_id).unwrap().unwrap();
parent_state.tree.to_hex()
};
assert!(
delete_loose_tree(temp.path(), &parent_tree_hex),
"test setup: expected to find loose tree at parent hash {parent_tree_hex}",
);
let _ = first_capture;
let err = heddle(&["revert", "@"], Some(temp.path())).expect_err(
"revert against a state whose parent tree is corrupt must fail loud; \
pre-#93 it silently computed an inverse diff against an empty baseline",
);
assert_missing_tree_error(&err, &parent_tree_hex);
}