#![allow(missing_docs)]
use assert_cmd::Command;
use std::process::Command as StdCommand;
const CODEX32_ALPHABET: &[u8; 32] = b"qpzry9x8gf2tvdw0s3jn54khce6mua7l";
fn encode_chunked(template: &str) -> Vec<String> {
let out = StdCommand::new(assert_cmd::cargo::cargo_bin("md"))
.args(["encode", "--force-chunked", "--group-size", "0", template])
.output()
.expect("invoke md encode --force-chunked");
assert!(
out.status.success(),
"md encode --force-chunked {template:?} failed: stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let s = String::from_utf8(out.stdout).expect("stdout utf-8");
s.lines()
.filter(|l| l.starts_with("md1"))
.map(String::from)
.collect()
}
fn corrupt_at(chunk: &str, pos: usize, xor_mask: u8) -> String {
let hrp_len = 3; let mut chars: Vec<char> = chunk.chars().collect();
let abs_idx = hrp_len + pos;
let original_sym = CODEX32_ALPHABET
.iter()
.position(|&b| b == chars[abs_idx].to_ascii_lowercase() as u8)
.expect("char in codex32 alphabet") as u8;
let new_sym = (original_sym ^ (xor_mask & 0x1F)) & 0x1F;
chars[abs_idx] = CODEX32_ALPHABET[new_sym as usize] as char;
chars.iter().collect()
}
const DEAD_TEMPLATE: &str = "sh(sortedmulti(2,@0/<0;1>/*,@1/<0;1>/*))";
#[test]
fn untouched_dead_card_is_unrepairable_exit_2() {
let chunks = encode_chunked(DEAD_TEMPLATE);
assert_eq!(
chunks.len(),
1,
"small dead template should force-chunk to exactly 1 chunk; got {chunks:?}"
);
let out = Command::cargo_bin("md")
.unwrap()
.args(["repair", &chunks[0]])
.output()
.expect("invoke md repair");
assert_eq!(
out.status.code(),
Some(2),
"untouched dead card must stay UN-repairable (exit 2, unchanged); stderr={}",
String::from_utf8_lossy(&out.stderr)
);
let stdout = String::from_utf8(out.stdout).unwrap();
assert!(
stdout.is_empty(),
"no card output should be emitted on the exit-2 reject path; got {stdout:?}"
);
}
#[test]
fn correction_resolving_to_dead_card_is_pruned_exit_2_never_ok_or_5() {
let chunks = encode_chunked(DEAD_TEMPLATE);
assert_eq!(chunks.len(), 1);
let corrupted = corrupt_at(&chunks[0], 10, 0b10110);
let out = Command::cargo_bin("md")
.unwrap()
.args(["repair", &corrupted])
.output()
.expect("invoke md repair");
let code = out.status.code().expect("exited normally");
assert_ne!(
code, 0,
"a correction resolving to a dead card must NOT silently pass as exit 0"
);
assert_ne!(
code, 5,
"a correction resolving to a dead card must NEVER be reported as REPAIR_APPLIED (exit 5) \
— repair does not opt into partial-decode (P1.3)"
);
assert_eq!(
code,
2,
"expected exit 2 (pruned/atomic-fail, unchanged strict behavior); stderr={}",
String::from_utf8_lossy(&out.stderr)
);
}