use super::*;
fn project(owner: &str, repo: &str) -> ProjectId {
ProjectId {
owner: owner.to_string(),
repo: repo.to_string(),
}
}
#[test]
fn test20109_refs_round_trip_and_ambiguous_ones_are_refused() {
let every = [
"board:jowharshamshiri/13",
"card:machinefabric/7/PVTI_lADODw7C",
"issue:jowharshamshiri/wsb#12",
"repo:jowharshamshiri/wsb",
"repo:machine-fabric/wsb_core.rs",
"project:jowharshamshiri/names",
"worktree:jowharshamshiri/wsb/model",
"path:jowharshamshiri/wsb/wsb/src/lib.rs",
"machine:pve",
"note:20261024-213000-a1b2",
"session:claude/5b0c9a8e-1d2f",
"message:claude/5b0c9a8e-1d2f/0f1e2d3c",
"url:https://example.com/a?b=c",
];
for text in every {
let parsed: Ref = text.parse().unwrap_or_else(|e| panic!("{text}: {e:#}"));
assert_eq!(parsed.to_string(), text, "{text} did not come back as written");
let json = serde_json::to_string(&parsed).unwrap();
assert_eq!(serde_json::from_str::<Ref>(&json).unwrap(), parsed);
}
assert_eq!(
"path:o/r/a/b/c.rs".parse::<Ref>().unwrap(),
Ref::Path { project: project("o", "r"), path: "a/b/c.rs".to_string() }
);
assert_eq!(
"project:o/r".parse::<Ref>().unwrap().project(),
Some(&project("o", "r")),
"a project ref is in its own project"
);
for bad in [
"board:owner",
"board:owner/x",
"card:o/1",
"issue:o/r12",
"project:o",
"project:o/r/x",
"worktree:o/r/-bad",
"project:--all/r",
"project:-o/r",
"project:o-/r",
"project:o--p/r",
"repo:o/-r",
"repo:o/..",
"issue:o_p/r#1",
"board:own.er/1",
"path:o/r",
"path:o/r/../secret",
"path:o/r/a//b",
"machine:two words",
"note:Not_An_Id",
"url:ftp://x",
"url:https://a b",
"colour:red",
"nokind",
] {
assert!(bad.parse::<Ref>().is_err(), "{bad} was accepted");
}
}
#[test]
fn test20110_notes_round_trip_and_a_strange_header_is_refused() {
let dir = tempfile::tempdir().unwrap();
let root = dir.path();
let note = Note::new("Why: the bridge retries", "Because peers sleep.\n\nTwice.", "pve").unwrap();
assert!(note.id.len() == "20261024-213000-a1b2".len(), "{}", note.id);
let path = note.write(root).unwrap();
assert_eq!(path, root.join(".wsb/notes").join(format!("{}.md", note.id)));
let text = std::fs::read_to_string(&path).unwrap();
assert!(text.starts_with("---\ntitle: Why: the bridge retries\n"), "{text}");
assert!(text.ends_with("Twice.\n"), "{text:?}");
let read = Note::read(root, ¬e.id).unwrap().expect("the note is there");
assert_eq!(read.body, "Because peers sleep.\n\nTwice.\n");
assert_eq!((read.title.as_str(), read.machine.as_str()), ("Why: the bridge retries", "pve"));
assert_eq!(Note::all(root).unwrap().len(), 1);
let empty = Note::parse("20261024-213000-0000", "---\ntitle: t\ncreated: 2026-10-24T21:30:00Z\nmachine: m\n---\n");
assert_eq!(empty.unwrap().body, "");
let header = |lines: &str| format!("---\n{lines}\n---\nbody\n");
for (text, why) in [
(header("title: t\ncreated: 2026-10-24T21:30:00Z"), "no machine"),
(header("title: t\ncreated: yesterday\nmachine: m"), "a time that is not one"),
(header("title: t\ntitle: u\ncreated: 2026-10-24T21:30:00Z\nmachine: m"), "a key twice"),
(header("title: t\ncreated: 2026-10-24T21:30:00Z\nmachine: m\ncolour: red"), "an unknown key"),
(header("title: \ncreated: 2026-10-24T21:30:00Z\nmachine: m"), "an empty title"),
("title: t\n".to_string(), "no header"),
] {
assert!(Note::parse("20261024-213000-0000", &text).is_err(), "{why} was accepted: {text:?}");
}
assert!(Note::new("a\ntwo-line title", "", "pve").is_err());
assert!(Note::read(root, "../escape").is_err(), "an id that is a path was read");
}
#[test]
fn test20111_links_are_one_file_per_link_and_the_index_merges_worktrees() {
let workshop = tempfile::tempdir().unwrap();
let main = workshop.path().join("main");
let fix = workshop.path().join("fix");
let broken = workshop.path().join("broken");
for dir in [&main, &fix, &broken] {
std::fs::create_dir_all(dir).unwrap();
}
let card: Ref = "card:jowharshamshiri/13/PVTI_x".parse().unwrap();
let worktree: Ref = "worktree:jowharshamshiri/wsb/fix".parse().unwrap();
let link = Link::new(card.clone(), worktree.clone(), None).unwrap();
let first = link.write(&main).unwrap();
assert_eq!(link.write(&main).unwrap(), first, "the same link made twice is a second file");
assert_eq!(std::fs::read_dir(main.join(".wsb/links")).unwrap().count(), 1);
link.write(&fix).unwrap();
assert!(Link::new(card.clone(), card.clone(), None).is_err(), "a link from a thing to itself");
assert!(Link::new(card.clone(), worktree.clone(), Some("Not A Label".into())).is_err());
assert_ne!(
Link::new(card.clone(), worktree.clone(), Some("implements".into())).unwrap().id(),
link.id(),
"a label is part of what a link says"
);
let note = Note::new("Plan", "first\n", "pve").unwrap();
note.write(&main).unwrap();
note.write(&fix).unwrap();
let mut edited = note.clone();
edited.body = "second\n".to_string();
edited.write(&fix).unwrap();
let other = Link::new(card.clone(), "machine:pve".parse().unwrap(), None).unwrap();
let path = other.write(&broken).unwrap();
std::fs::write(&path, serde_json::to_string(&link).unwrap()).unwrap();
assert!(Link::all(&broken).is_err(), "a hand-edited link was read");
let place = |root: &Path, name: &str| Place {
workspace: "wsb".to_string(),
worktree: name.to_string(),
root: root.to_path_buf(),
};
let index = Index::of(vec![place(&main, "main"), place(&fix, "fix"), place(&broken, "broken")]);
assert_eq!(index.links.len(), 1, "one link held twice is one link");
assert_eq!(
index.links[0].places.iter().map(|p| p.worktree.as_str()).collect::<Vec<_>>(),
vec!["main", "fix"]
);
assert_eq!(index.links_at(&worktree).count(), 1);
assert_eq!(index.links_at(&"machine:other".parse().unwrap()).count(), 0);
let versions = index.note(¬e.id);
assert_eq!(versions.len(), 2, "two versions of a note were merged into one");
assert!(versions.iter().any(|v| v.note.body == "first\n" && v.places[0].worktree == "main"));
assert!(versions.iter().any(|v| v.note.body == "second\n" && v.places[0].worktree == "fix"));
assert_eq!(index.problems.len(), 1, "{:?}", index.problems);
assert!(index.problems[0].starts_with("wsb/broken:"), "{:?}", index.problems);
assert_eq!(
index.named_projects().into_iter().collect::<Vec<_>>(),
vec![project("jowharshamshiri", "wsb")]
);
remove_link(&main, &link).unwrap();
assert!(remove_link(&main, &link).is_err(), "a link removed twice");
remove_note(&main, ¬e.id).unwrap();
assert!(Note::read(&main, ¬e.id).unwrap().is_none());
}