use std::fs;
use std::path::{Path, PathBuf};
use super::{SessionWorkspace, bind, check_state_overlap, resolve_root};
fn temp_dir(label: &str) -> PathBuf {
let root = std::env::temp_dir().join(format!("saya-session-ws-{label}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
root
}
#[test]
fn an_explicit_workspace_binds_canonically() {
let dir = temp_dir("explicit");
let SessionWorkspace { root, workspace } = bind(Some(Path::new(&dir)), Path::new("/"))
.expect("explicit bind")
.expect("the root binds");
assert_eq!(root, std::fs::canonicalize(&dir).unwrap());
assert_eq!(workspace.root(), std::fs::canonicalize(&dir).unwrap());
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn an_explicit_workspace_must_exist() {
let parent = temp_dir("missing");
let missing = parent.join("no-such");
let error = bind(Some(Path::new(&missing)), Path::new("/"))
.map(|bound| bound.is_some())
.expect_err("a nonexistent explicit root must refuse");
assert!(
error.contains("could not be resolved"),
"the refusal must name the resolution failure: {error}"
);
let _ = fs::remove_dir_all(&parent);
}
#[test]
fn outside_a_worktree_nothing_binds() {
let dir = temp_dir("no-worktree");
let nested = dir.join("deep").join("deeper");
std::fs::create_dir_all(&nested).unwrap();
let bound = bind(None, &nested).expect("the resolution runs");
assert!(bound.is_none(), "no .git above: nothing binds");
let _ = fs::remove_dir_all(&dir);
}
#[test]
fn the_worktree_top_binds_from_a_subdirectory() {
let project = temp_dir("worktree");
std::fs::create_dir_all(project.join(".git")).unwrap();
let nested = project.join("src").join("sub");
std::fs::create_dir_all(&nested).unwrap();
let bound = bind(None, &nested).expect("the resolution runs");
let bound = bound.expect("the worktree top binds");
assert_eq!(bound.root, std::fs::canonicalize(&project).unwrap());
let _ = fs::remove_dir_all(&project);
}
#[test]
fn a_worktree_pointer_file_binds() {
let main = temp_dir("main-repo");
let linked = temp_dir("linked-worktree");
std::fs::create_dir_all(main.join(".git")).unwrap();
std::fs::write(linked.join(".git"), "gitdir: elsewhere\n").unwrap();
let bound = bind(None, &linked).expect("the resolution runs");
let bound = bound.expect("the worktree binds");
assert_eq!(bound.root, std::fs::canonicalize(&linked).unwrap());
let _ = fs::remove_dir_all(&main);
let _ = fs::remove_dir_all(&linked);
}
#[test]
fn a_state_root_inside_the_workspace_refuses_the_binding() {
let project = temp_dir("overlap");
std::fs::create_dir_all(project.join(".git")).unwrap();
let runs = project.join("runs");
let sessions = project.join(".saya-sessions");
std::fs::create_dir_all(&runs).unwrap();
std::fs::create_dir_all(&sessions).unwrap();
let canonical = std::fs::canonicalize(&project).unwrap();
for state_root in [&runs, &sessions] {
let state_canonical = std::fs::canonicalize(state_root).unwrap();
let error = check_state_overlap(&canonical, &state_canonical)
.expect_err("the state root inside the project must refuse");
assert!(
error.contains(canonical.display().to_string().as_str()),
"the refusal must name the workspace root: {error}"
);
assert!(
error.contains(state_canonical.display().to_string().as_str()),
"the refusal must name the state root: {error}"
);
}
let _ = fs::remove_dir_all(&project);
}
#[test]
fn the_workspace_inside_a_state_root_refuses_the_binding() {
let state = temp_dir("state-root");
let project = state.join("proj");
std::fs::create_dir_all(&project).unwrap();
let error = check_state_overlap(&project, &state)
.expect_err("a workspace inside a state root must refuse");
assert!(
error.contains(project.display().to_string().as_str())
&& error.contains(state.display().to_string().as_str()),
"the refusal must name both roots: {error}"
);
let _ = fs::remove_dir_all(&state);
}
#[test]
fn disjoint_roots_bind() {
let project = temp_dir("disjoint");
let state = temp_dir("disjoint-state");
check_state_overlap(&project, &state).expect("disjoint binds");
let _ = fs::remove_dir_all(&project);
let _ = fs::remove_dir_all(&state);
}
#[test]
fn resolve_root_mirrors_the_binding() {
let project = temp_dir("resolve");
std::fs::create_dir_all(project.join(".git")).unwrap();
assert_eq!(
resolve_root(Some(Path::new(&project)), Path::new("/"))
.expect("explicit resolves")
.expect("explicit binds"),
std::fs::canonicalize(&project).unwrap()
);
assert!(resolve_root(None, &project).expect("resolves").is_some());
let plain = temp_dir("resolve-plain");
assert!(resolve_root(None, &plain).expect("resolves").is_none());
let _ = fs::remove_dir_all(&project);
let _ = fs::remove_dir_all(&plain);
}