use super::*;
#[test]
fn a_link_is_recorded_once() {
let root = tempfile::tempdir().expect("root");
assert!(record_link(root.path(), "managed-1", "claude-a"));
assert!(
!record_link(root.path(), "managed-1", "claude-a"),
"re-recording the same id must be a no-op"
);
assert_eq!(
linked_claude_ids(root.path(), "claude-a"),
vec!["claude-a".to_string()]
);
}
#[test]
fn a_second_id_joins_the_same_managed_session() {
let root = tempfile::tempdir().expect("root");
record_link(root.path(), "managed-1", "claude-a");
record_link(root.path(), "managed-1", "claude-b");
let from_new = linked_claude_ids(root.path(), "claude-b");
assert_eq!(
from_new,
vec!["claude-a".to_string(), "claude-b".to_string()]
);
}
#[test]
fn siblings_include_the_queried_id() {
let root = tempfile::tempdir().expect("root");
record_link(root.path(), "managed-1", "claude-a");
assert!(
linked_claude_ids(root.path(), "claude-a").contains(&"claude-a".to_string()),
"the queried id must be in its own sibling set"
);
}
#[test]
fn an_unlinked_id_has_no_siblings() {
let root = tempfile::tempdir().expect("root");
record_link(root.path(), "managed-1", "claude-a");
assert!(linked_claude_ids(root.path(), "claude-z").is_empty());
assert!(linked_claude_ids(root.path(), "").is_empty());
}
#[test]
fn an_absent_store_has_no_siblings() {
let root = tempfile::tempdir().expect("root");
assert!(linked_claude_ids(root.path(), "claude-a").is_empty());
}
struct CountingSink {
writes: Vec<usize>,
bytes: Vec<u8>,
}
impl std::io::Write for CountingSink {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.writes.push(buf.len());
self.bytes.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
#[test]
fn an_id_and_its_newline_leave_in_one_write() {
let mut sink = CountingSink {
writes: Vec::new(),
bytes: Vec::new(),
};
write_link_line(&mut sink, "3544c9e5-f90d-421f-9fb4-7a92d5c329ff").expect("write");
assert_eq!(
sink.writes.len(),
1,
"the id and its newline must reach the descriptor together, got {:?}",
sink.writes
);
let written = String::from_utf8(sink.bytes).expect("utf-8");
assert!(
written.ends_with('\n'),
"the single write must carry the terminator: {written:?}"
);
assert_eq!(
written.matches('\n').count(),
1,
"exactly one terminator: {written:?}"
);
}
#[test]
fn a_traversing_managed_id_is_refused() {
let root = tempfile::tempdir().expect("root");
for bad in ["..", ".", "", "../escape", "a/b", ".hidden"] {
assert!(
!record_link(root.path(), bad, "claude-a"),
"{bad:?} must be refused as a link-file name"
);
}
assert!(linked_claude_ids(root.path(), "claude-a").is_empty());
}
#[test]
fn link_history_maps_each_claude_id_to_its_managed_sessions() {
let root = tempfile::tempdir().expect("root");
record_link(root.path(), "managed-1", "claude-a");
record_link(root.path(), "managed-1", "claude-b");
record_link(root.path(), "managed-2", "claude-b");
std::fs::write(
root.path()
.join("usage")
.join(SESSION_LINKS_DIR)
.join(".DS_Store"),
[0xff, 0xfe],
)
.expect("a Finder dot-file");
let mut history = link_history(root.path()).expect("a readable store");
history.values_mut().for_each(|m| m.sort());
assert_eq!(
history.get("claude-a"),
Some(&vec!["managed-1".to_string()])
);
assert_eq!(
history.get("claude-b"),
Some(&vec!["managed-1".to_string(), "managed-2".to_string()])
);
assert_eq!(history.len(), 2, "the dot-file is skipped: {history:?}");
}
#[test]
fn link_history_of_an_absent_store_is_empty() {
let root = tempfile::tempdir().expect("root");
assert_eq!(link_history(root.path()), Ok(HashMap::new()));
}
#[test]
fn link_history_refuses_an_unreadable_link_file() {
let root = tempfile::tempdir().expect("root");
record_link(root.path(), "managed-1", "claude-a");
let entry = root
.path()
.join("usage")
.join(SESSION_LINKS_DIR)
.join("managed-2");
std::fs::create_dir(entry).expect("an unreadable entry");
let err = link_history(root.path()).expect_err("an unreadable entry fails the read");
assert!(err.contains("managed-2"), "{err}");
}