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:
1 parent
3cf359a0cd
commit
d5c9522ad1
24 files changed
+2937
-20
No files matched your search
@@ -288,6 +288,72 @@ mod tests {
|
||||
assert!(verify(dir.path(), FIRST).unwrap());
|
||||
}
|
||||
|
||||
/// A record of `FIRST` hashed with the plan's parameters but this salt
|
||||
/// and output length, as a hand could write it.
|
||||
fn record_with(dir: &Path, salt: &[u8], out: usize) {
|
||||
let hasher = Argon2::new(
|
||||
Algorithm::Argon2id,
|
||||
Version::V0x13,
|
||||
Params::new(M_COST, T_COST, P_COST, Some(out)).unwrap(),
|
||||
);
|
||||
let owner = Owner {
|
||||
argon2id: hasher
|
||||
.hash_password_with_salt(FIRST.as_bytes(), salt)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
set: "2026-10-19T00:00:00Z".into(),
|
||||
};
|
||||
std::fs::write(dir.join(OWNER_FILE), serde_json::to_string(&owner).unwrap()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_salt_shorter_than_sixteen_bytes_or_an_output_not_thirty_two_is_refused() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
// The plan's own: a salt of 16 bytes or more, an output of 32.
|
||||
for salt in [16, 17, 32, 48] {
|
||||
record_with(dir.path(), &vec![7u8; salt], 32);
|
||||
assert_eq!(verify(dir.path(), FIRST), Ok(true), "a {salt}-byte salt");
|
||||
}
|
||||
// Each right but for the salt's length, or the output's.
|
||||
for (salt, out) in [(8, 32), (15, 32), (16, 16), (16, 64)] {
|
||||
record_with(dir.path(), &vec![7u8; salt], out);
|
||||
assert!(
|
||||
matches!(verify(dir.path(), FIRST), Err(PassError::Record(_))),
|
||||
"a {salt}-byte salt, a {out}-byte output"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_record_the_disk_wont_read_is_never_taken_for_none() {
|
||||
// A folder where the record should be: no passphrase can be set
|
||||
// as if none were, and the refusal is the disk's own.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
std::fs::create_dir(dir.path().join(OWNER_FILE)).unwrap();
|
||||
assert!(matches!(is_set(dir.path()), Err(PassError::Io(_))));
|
||||
assert!(matches!(verify(dir.path(), FIRST), Err(PassError::Io(_))));
|
||||
assert!(matches!(
|
||||
set(dir.path(), FIRST, None),
|
||||
Err(PassError::Io(_))
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
#[test]
|
||||
fn a_leftover_that_cant_be_cleared_stops_a_change_saying_why() {
|
||||
// A folder (with something in it) left where the record is written
|
||||
// aside: removing it fails, and that failure is what is reported.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let aside = dir.path().join(".owner.json.new");
|
||||
std::fs::create_dir(&aside).unwrap();
|
||||
std::fs::write(aside.join("x"), "x").unwrap();
|
||||
assert_eq!(
|
||||
set(dir.path(), FIRST, None),
|
||||
Err(PassError::Io(std::io::ErrorKind::IsADirectory))
|
||||
);
|
||||
assert!(!is_set(dir.path()).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_name_no_path_and_repeat_no_passphrase() {
|
||||
for e in [
|
||||
|
||||
Reference in new issue
Block a user