#![cfg(feature = "derive")]
mod common;
use common::{descriptor_with_pubkeys, keyarg, multikeys, wrap};
use md_codec::Tag;
use md_codec::chunk::{decode_with_correction, reassemble, split};
use md_codec::decode::decode_md1_string;
use md_codec::encode::encode_md1_string;
fn one_chunk() -> md_codec::Descriptor {
descriptor_with_pubkeys(wrap(Tag::Wsh, keyarg(Tag::PkK, 0)))
}
fn many_chunk() -> md_codec::Descriptor {
descriptor_with_pubkeys(wrap(Tag::Wsh, multikeys(Tag::Multi, 2, (0..20).collect())))
}
const NEEDLE: &str = "mixes upper and lower case";
#[test]
fn decode_md1_string_rejects_mixed_case_data_char() {
let s = encode_md1_string(&one_chunk()).unwrap();
let mut chars: Vec<char> = s.chars().collect();
for c in chars.iter_mut().skip(3) {
if c.is_ascii_lowercase() {
*c = c.to_ascii_uppercase();
break;
}
}
let mixed: String = chars.into_iter().collect();
let err = decode_md1_string(&mixed).unwrap_err();
assert!(err.to_string().contains(NEEDLE), "got: {err}");
}
#[test]
fn decode_md1_string_rejects_mixed_hrp() {
let s = encode_md1_string(&one_chunk()).unwrap(); let mixed = format!("Md1{}", &s[3..]); let err = decode_md1_string(&mixed).unwrap_err();
assert!(err.to_string().contains(NEEDLE), "got: {err}");
}
#[test]
fn decode_md1_string_accepts_uppercase_round_trip() {
let d = one_chunk();
let upper = encode_md1_string(&d).unwrap().to_uppercase();
assert_eq!(
decode_md1_string(&upper).unwrap(),
d,
"all-upper (QR form) must round-trip"
);
}
#[test]
fn reassemble_accepts_cross_chunk_case_heterogeneity() {
let d = many_chunk();
let chunks = split(&d).unwrap();
assert!(
chunks.len() >= 2,
"fixture must span ≥2 chunks, got {}",
chunks.len()
);
let mut set = chunks.clone();
set[0] = set[0].to_uppercase();
let refs: Vec<&str> = set.iter().map(String::as_str).collect();
assert_eq!(reassemble(&refs).unwrap(), d);
}
#[test]
fn reassemble_rejects_internally_mixed_chunk() {
let d = many_chunk();
let chunks = split(&d).unwrap();
let mut set = chunks.clone();
set[0] = format!("MD1{}", &chunks[0][3..]); let refs: Vec<&str> = set.iter().map(String::as_str).collect();
let err = reassemble(&refs).unwrap_err();
assert!(err.to_string().contains(NEEDLE), "got: {err}");
}
#[test]
fn decode_with_correction_rejects_mixed_chunk() {
let d = many_chunk();
let chunks = split(&d).unwrap();
let mut set = chunks.clone();
set[0] = format!("MD1{}", &chunks[0][3..]); let refs: Vec<&str> = set.iter().map(String::as_str).collect();
let err = decode_with_correction(&refs).unwrap_err();
assert!(err.to_string().contains(NEEDLE), "got: {err}");
}
#[test]
fn decode_with_correction_accepts_all_upper() {
let d = many_chunk();
let chunks = split(&d).unwrap();
let set: Vec<String> = chunks.iter().map(|c| c.to_uppercase()).collect();
let refs: Vec<&str> = set.iter().map(String::as_str).collect();
let (back, corrections) = decode_with_correction(&refs).unwrap();
assert_eq!(back, d);
assert!(
corrections.is_empty(),
"no corrections expected for a valid card"
);
}
#[test]
fn decode_with_correction_rejects_mixed_with_symbol_error() {
const ALPHABET: &str = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
let d = many_chunk();
let chunks = split(&d).unwrap();
let mut cs: Vec<char> = chunks[0].chars().collect();
for c in cs.iter_mut().skip(3) {
if ALPHABET.contains(*c) {
*c = ALPHABET.chars().find(|&a| a != *c).unwrap();
break;
}
}
let corrupted: String = cs.into_iter().collect();
let mut set = chunks.clone();
set[0] = format!("MD1{}", &corrupted[3..]); let refs: Vec<&str> = set.iter().map(String::as_str).collect();
let err = decode_with_correction(&refs).unwrap_err();
assert!(err.to_string().contains(NEEDLE), "got: {err}");
}