use super::*;
use std::io::Cursor;
use test_log::test;
fn roundtrip(payload: &[u8]) -> Vec<u8> {
let mut buf = Vec::new();
let mut scratch = Vec::new();
write_framed_record(&mut buf, &mut scratch, |out| {
out.extend_from_slice(payload);
Ok(())
})
.expect("write");
buf
}
#[test]
fn framed_record_ok_roundtrip() {
let payload = b"hello world";
let bytes = roundtrip(payload);
let mut cursor = Cursor::new(&bytes);
let mut scratch = Vec::new();
let outcome = read_framed_record(&mut cursor, u64::MAX, None, &mut scratch).expect("read");
match outcome {
FramedRecordOutcome::Ok => assert_eq!(scratch.as_slice(), payload),
other => panic!("expected Ok, got {other:?}"),
}
}
#[test]
fn framed_record_checksum_mismatch_detected() {
let payload = b"hello world";
let mut bytes = roundtrip(payload);
bytes[FRAME_HEADER_LEN] ^= 0x01;
let mut cursor = Cursor::new(&bytes);
let outcome = read_framed_record(&mut cursor, u64::MAX, None, &mut Vec::new()).expect("read");
match outcome {
FramedRecordOutcome::ChecksumMismatch {
bytes_consumed,
expected,
got,
} => {
assert_eq!(bytes_consumed, (FRAME_HEADER_LEN + payload.len()) as u64);
assert_ne!(expected, got);
}
other => panic!("expected ChecksumMismatch, got {other:?}"),
}
}
#[test]
fn framed_record_oversized_len_rejected_as_bad_header() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&(MAX_FRAME_PAYLOAD + 1).to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
let mut cursor = Cursor::new(&bytes);
let outcome = read_framed_record(&mut cursor, u64::MAX, None, &mut Vec::new()).expect("read");
assert!(
matches!(outcome, FramedRecordOutcome::BadHeader),
"expected BadHeader for oversized len, got {outcome:?}",
);
}
#[test]
fn framed_record_len_exceeding_section_bound_classified_as_tail_truncation() {
let mut bytes = Vec::new();
bytes.extend_from_slice(&100u32.to_le_bytes());
bytes.extend_from_slice(&0u64.to_le_bytes());
let mut cursor = Cursor::new(&bytes);
let outcome = read_framed_record(&mut cursor, 8 + 1, None, &mut Vec::new()).expect("read");
assert!(
matches!(outcome, FramedRecordOutcome::TailTruncation),
"expected TailTruncation for len > remaining, got {outcome:?}",
);
}
#[test]
fn framed_record_tail_truncation_at_header() {
let mut cursor = Cursor::new(Vec::<u8>::new());
let outcome = read_framed_record(&mut cursor, u64::MAX, None, &mut Vec::new()).expect("read");
assert!(
matches!(outcome, FramedRecordOutcome::TailTruncation),
"expected TailTruncation, got {outcome:?}",
);
}
#[test]
fn framed_record_tail_truncation_mid_payload() {
let payload = b"hello world";
let mut bytes = roundtrip(payload);
bytes.truncate(FRAME_HEADER_LEN + payload.len() / 2);
let mut cursor = Cursor::new(&bytes);
let outcome = read_framed_record(&mut cursor, u64::MAX, None, &mut Vec::new()).expect("read");
assert!(
matches!(outcome, FramedRecordOutcome::TailTruncation),
"expected TailTruncation, got {outcome:?}",
);
}
#[test]
fn framed_record_fixed_len_mismatch_surfaces_lenmismatch() {
let payload = b"ten-byte!!"; let bytes = roundtrip(payload);
let mut cursor = Cursor::new(&bytes);
let outcome =
read_framed_record(&mut cursor, u64::MAX, Some(33), &mut Vec::new()).expect("read");
match outcome {
FramedRecordOutcome::LenMismatch { got, expected } => {
assert_eq!(got, 10, "got should carry the on-disk len");
assert_eq!(expected, 33, "expected should carry the caller's pin");
}
other => panic!("expected LenMismatch {{ got: 10, expected: 33 }}, got {other:?}"),
}
}