Files
wanderbooks/crates/ledger-cli/tests/setup.rs
T
swaitsandClaude Opus 5.5 a298d154b6 P2 slice 3: opening and closing the books (plan 3.9, D-21, D-23)
One wanderbooks executable. The books are open while the viewer, an
agent session or a terminal holds them: a host process takes leases on
a socket, starts the server (the hidden mode `wanderbooks server`) with
a pipe on its input, and closes it 10 s after the last lease ends. No
background service. One host even in a race (run/host.lock).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
2026-10-20 11:14:19 -06:00

155 lines
5.0 KiB
Rust

//! `wanderbooks setup`: the books' private folder, made from the command
//! line, with its location never printed.
#![cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::process::Command;
fn wanderbooks() -> Command {
Command::new(env!("CARGO_BIN_EXE_wanderbooks"))
}
fn mode(path: &std::path::Path) -> u32 {
std::fs::metadata(path).unwrap().permissions().mode() & 0o777
}
#[test]
fn setup_makes_the_private_folder_and_never_names_it() {
let home = tempfile::Builder::new()
.prefix("wb-setup-")
.tempdir()
.unwrap();
let root = home.path().join(".local/share/wanderbooks");
let out = wanderbooks()
.args(["setup", "--home"])
.arg(home.path())
.env_remove("XDG_DATA_HOME")
.output()
.unwrap();
assert!(out.status.success(), "{out:?}");
let text = String::from_utf8_lossy(&out.stdout);
assert!(text.contains("The books' private folder: made"), "{text}");
assert!(
String::from_utf8_lossy(&out.stderr).contains("--home isn't this account's home"),
"{out:?}"
);
assert!(
!text.contains(home.path().to_str().unwrap()),
"names the folder: {text}"
);
assert!(!String::from_utf8_lossy(&out.stderr).contains(home.path().to_str().unwrap()));
assert_eq!(mode(&root), 0o700);
for sub in ["books", "run", "legacy"] {
assert_eq!(mode(&root.join(sub)), 0o700, "{sub}");
}
// Again: already there.
let out = wanderbooks()
.args(["setup", "--home"])
.arg(home.path())
.env_remove("XDG_DATA_HOME")
.output()
.unwrap();
assert!(
String::from_utf8_lossy(&out.stdout).contains("already there."),
"{out:?}"
);
// Others could open it: made private again.
std::fs::set_permissions(&root, std::fs::Permissions::from_mode(0o755)).unwrap();
let out = wanderbooks()
.args(["setup", "--home"])
.arg(home.path())
.env_remove("XDG_DATA_HOME")
.output()
.unwrap();
assert!(
String::from_utf8_lossy(&out.stdout).contains("made private again"),
"{out:?}"
);
assert_eq!(mode(&root), 0o700);
}
#[test]
fn a_root_elsewhere_is_noted_for_next_time() {
// (XDG_DATA_HOME is honoured only for this account's own home, which
// a test never touches: Root::setup's tests cover it.)
let home = tempfile::Builder::new()
.prefix("wb-setup-else-")
.tempdir()
.unwrap();
let elsewhere = home.path().join("elsewhere").join("wanderbooks");
let out = wanderbooks()
.args(["setup", "--home"])
.arg(home.path())
.arg("--root")
.arg(&elsewhere)
.env_remove("XDG_DATA_HOME")
.output()
.unwrap();
assert!(out.status.success(), "{out:?}");
assert!(
String::from_utf8_lossy(&out.stdout).contains("noted for next time"),
"{out:?}"
);
assert_eq!(mode(&elsewhere), 0o700);
let noted =
std::fs::read_to_string(home.path().join(".local/share/wanderbooks.location")).unwrap();
assert_eq!(noted.trim_end(), elsewhere.display().to_string());
}
#[test]
fn setup_refuses_what_it_does_not_know_and_unsafe_folders() {
let out = wanderbooks().args(["setup", "--bogus"]).output().unwrap();
assert_eq!(out.status.code(), Some(2));
let out = wanderbooks().args(["setup", "--root"]).output().unwrap();
assert_eq!(out.status.code(), Some(1));
assert!(String::from_utf8_lossy(&out.stderr).contains("--root needs a path"));
let home = tempfile::Builder::new()
.prefix("wb-setup-link-")
.tempdir()
.unwrap();
let real = home.path().join("real");
std::fs::create_dir(&real).unwrap();
std::os::unix::fs::symlink(&real, home.path().join("link")).unwrap();
let out = wanderbooks()
.args(["setup", "--home"])
.arg(home.path())
.arg("--root")
.arg(home.path().join("link"))
.output()
.unwrap();
assert_eq!(out.status.code(), Some(1), "{out:?}");
assert!(String::from_utf8_lossy(&out.stderr).contains("is a symlink"));
// A mode comes first.
let out = wanderbooks().args(["--force", "setup"]).output().unwrap();
assert_eq!(out.status.code(), Some(2));
}
/// With another home given (tests), this account's XDG_DATA_HOME is never
/// read: setup can't be aimed at this account's real books by it.
#[test]
fn another_home_ignores_this_accounts_data_home() {
let home = tempfile::Builder::new()
.prefix("wb-setup-xdg-")
.tempdir()
.unwrap();
let elsewhere = home.path().join("elsewhere");
std::fs::create_dir(&elsewhere).unwrap();
let out = wanderbooks()
.args(["setup", "--home"])
.arg(home.path())
.env("XDG_DATA_HOME", &elsewhere)
.output()
.unwrap();
assert!(out.status.success(), "{out:?}");
assert!(
home.path()
.join(".local/share/wanderbooks/.wanderbooks-root")
.is_file()
);
assert_eq!(
std::fs::read_dir(&elsewhere).unwrap().count(),
0,
"XDG_DATA_HOME was used"
);
}