P2: tests mutation found, three bugs fixed; plan P12 and P13

Mutation testing pauses until P12 (D-33, owner 2026-10-20), which reruns
the whole max-testing pass over everything since P1. Before the pause,
pass 1 on slices 3-6 and its triage gave tests in ledger-store,
ledger-package and ledger-mcp, and three real bugs, fixed:
- the command line took an admin answer longer than four MiB, or cut off
  before its newline, whenever the bytes that came still parsed;
- The CPA package put a twin's number on the last dot of the whole path,
  so in a folder with a dot the second file landed in another folder;
- a document or category named of dots alone ("..") could climb out of
  its folder when the CPA package is unpacked.
The plan gains P13, bookspec (D-34): a formal, sourced accounting
specification first (rules with symbolic forms and worked examples), then
its own formal validation and a conformance harness any engine can be
tested against, seeded by P3's regression tests. docs/testing.md records
slices 3-8 and what P12 must cover; plan/STATUS.md says where P2 stands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T3gXMVt7XJPi6ens1or9Dj
This commit is contained in:
swaitsandClaude Opus 5.5 committed 2026-10-20 11:14:22 -06:00
1 parent 3cf359a0cd
commit d5c9522ad1
24 files changed
+2937 -20

No files matched your search

+140
View File
@@ -296,3 +296,143 @@ fn dev_stdio_refuses_books_in_the_private_folder() {
"{out:?}"
);
}
/// `upload` and `download` say how they ended in their exit code, as
/// every mode does: 0 for their usage on request, 2 for a slip in how
/// they were called, 1 for what went wrong doing it. A file's path may be
/// a name that isn't text: it reaches the command as given.
#[cfg(unix)]
#[test]
fn the_file_commands_end_with_the_code_that_says_how() {
use std::os::unix::ffi::OsStrExt;
let home = tempfile::Builder::new()
.prefix("wb-one-files-")
.tempdir()
.unwrap();
let root = home.path().join("wanderbooks");
let run = |args: &[&std::ffi::OsStr]| {
wanderbooks()
.args(args)
.env_remove("XDG_DATA_HOME")
.stdin(Stdio::null())
.output()
.unwrap()
};
let os = |s: &'static str| std::ffi::OsStr::new(s);
for (mode, usage) in [
("upload", "usage: wanderbooks upload --book ID"),
("download", "usage: wanderbooks download --book ID"),
] {
// Its usage on request.
let out = run(&[os(mode), os("--help")]);
assert_eq!(out.status.code(), Some(0), "{mode}: {out:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains(usage),
"{out:?}"
);
// Called wrong: no file, or no books named.
let out = run(&[
os(mode),
os("--root"),
root.as_os_str(),
os("--book"),
os("acme"),
]);
assert_eq!(out.status.code(), Some(2), "{mode}: {out:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains(usage),
"{out:?}"
);
let out = run(&[os(mode), os("--root"), root.as_os_str(), os("1")]);
assert_eq!(out.status.code(), Some(2), "{mode}: {out:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains("which books? give --book ID"),
"{out:?}"
);
}
// A file that isn't there, named by a path that isn't text: what went
// wrong, said, and nothing sent.
let missing = home
.path()
.join(std::ffi::OsStr::from_bytes(b"receipt-\xff.pdf"));
let out = run(&[
os("upload"),
os("--root"),
root.as_os_str(),
os("--book"),
os("acme"),
os("up_abcdefghijklmnopabcdefghijklmnop"),
missing.as_os_str(),
]);
assert_eq!(out.status.code(), Some(1), "{out:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains("error: can't read"),
"{out:?}"
);
// A folder to save in that isn't there: the same.
let out = run(&[
os("download"),
os("--root"),
root.as_os_str(),
os("--book"),
os("acme"),
os("7"),
os("--to"),
home.path().join("no-such-folder").as_os_str(),
]);
assert_eq!(out.status.code(), Some(1), "{out:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains("isn't a folder"),
"{out:?}"
);
assert!(!root.exists(), "the books were reached for");
}
/// Where local sockets aren't built yet (P11, Windows): the modes that
/// hold the books say so and end with 2, never a success; setup makes
/// the folder and ends with 0, asking for no passphrase (nothing there
/// asks for one yet).
#[cfg(not(unix))]
#[test]
fn without_local_sockets_the_modes_that_hold_the_books_say_so() {
for mode in [
"server",
"host",
"mcp",
"serve",
"close",
"books",
"doctor",
"token",
"connect",
"passphrase",
"upload",
"download",
] {
let out = wanderbooks()
.arg(mode)
.stdin(Stdio::null())
.output()
.unwrap();
assert_eq!(out.status.code(), Some(2), "{mode}: {out:?}");
assert!(
String::from_utf8_lossy(&out.stderr).contains("to this system with P11"),
"{mode}: {out:?}"
);
}
let home = tempfile::Builder::new()
.prefix("wb-one-nosock-")
.tempdir()
.unwrap();
let out = wanderbooks()
.args(["setup", "--home"])
.arg(home.path())
.env_remove("XDG_DATA_HOME")
.stdin(Stdio::null())
.output()
.unwrap();
assert_eq!(out.status.code(), Some(0), "{out:?}");
let text = String::from_utf8_lossy(&out.stdout);
assert!(text.contains("The books' private folder: made"), "{text}");
assert!(!text.contains("passphrase"), "{text}");
}