#![allow(clippy::indexing_slicing)]
use prikk_error::Result;
use prikk_object::ObjectType;
use super::{
ContainerRecordStatus, container_magic, decode_container_records, encode_container_record,
encode_container_record_for_test,
};
use crate::test_support::{signed_patch_blob_envelope, signed_patch_envelope};
#[test]
fn round_trips_a_single_record() -> Result<()> {
let envelope = signed_patch_envelope();
let bytes = encode_container_record(ObjectType::Patch, &envelope)?;
let replay = decode_container_records(ObjectType::Patch, &bytes)?;
assert!(!replay.has_item_failure());
assert_eq!(replay.trailing_partial_bytes, 0);
assert_eq!(replay.records.len(), 1);
assert_eq!(replay.records[0].envelope, envelope);
Ok(())
}
#[test]
fn every_persisted_type_has_a_distinct_magic() -> Result<()> {
let mut magics = Vec::new();
for object_type in crate::layout::persisted_object_types() {
magics.push(container_magic(object_type)?);
}
let mut deduped = magics.clone();
deduped.sort_unstable();
deduped.dedup();
assert_eq!(
deduped.len(),
magics.len(),
"expected all 6 persisted-type magics to be distinct: {magics:?}"
);
Ok(())
}
#[test]
fn a_record_framed_for_one_type_is_rejected_by_another_types_container() -> Result<()> {
let envelope = signed_patch_envelope();
let bytes = encode_container_record(ObjectType::Patch, &envelope)?;
let replay = decode_container_records(ObjectType::Blob, &bytes)?;
assert!(replay.has_item_failure());
assert!(replay.records.is_empty());
Ok(())
}
#[test]
fn isolates_a_damaged_record_and_reads_every_sound_record_around_it() -> Result<()> {
let envelopes = [
signed_patch_blob_envelope(),
signed_patch_blob_envelope(),
signed_patch_blob_envelope(),
];
let mut bytes = Vec::new();
let mut bounds = Vec::new();
for envelope in &envelopes {
let start = bytes.len();
bytes.extend(encode_container_record(ObjectType::Blob, envelope)?);
bounds.push((start, bytes.len()));
}
let (_, middle_stop) = bounds[1];
bytes[middle_stop - 1] ^= 0x01;
let replay = decode_container_records(ObjectType::Blob, &bytes)?;
assert!(replay.has_item_failure());
let failed_offsets: Vec<usize> = replay
.record_outcomes
.iter()
.filter_map(|outcome| match &outcome.status {
ContainerRecordStatus::Failed { .. } => Some(outcome.offset),
ContainerRecordStatus::Evaluated { .. } => None,
})
.collect();
assert_eq!(failed_offsets, vec![bounds[1].0]);
assert_eq!(
replay.records,
vec![
super::ContainerRecord {
envelope: envelopes[0].clone()
},
super::ContainerRecord {
envelope: envelopes[2].clone()
},
],
"the two sound records around the damaged one must still be read, in file order"
);
Ok(())
}
#[test]
fn trailing_partial_bytes_are_tolerated_not_treated_as_corruption() -> Result<()> {
let envelope = signed_patch_envelope();
let mut bytes = encode_container_record(ObjectType::Patch, &envelope)?;
bytes.extend_from_slice(&[0_u8; 10]);
let replay = decode_container_records(ObjectType::Patch, &bytes)?;
assert!(!replay.has_item_failure());
assert_eq!(replay.trailing_partial_bytes, 10);
assert_eq!(replay.records.len(), 1);
Ok(())
}
#[test]
fn non_persisted_type_has_no_container_magic() {
assert!(container_magic(ObjectType::RefUpdate).is_err());
}
#[test]
fn envelope_type_disagreeing_with_its_own_containers_type_is_rejected() -> Result<()> {
let patch_envelope = signed_patch_envelope();
assert_eq!(patch_envelope.object_type, ObjectType::Patch);
let bytes = encode_container_record(ObjectType::Blob, &patch_envelope)?;
let replay = decode_container_records(ObjectType::Blob, &bytes)?;
assert!(replay.has_item_failure());
assert!(replay.records.is_empty());
Ok(())
}
#[test]
fn encode_for_test_bypasses_strict_validation_but_still_decodes() -> Result<()> {
let mut envelope = signed_patch_blob_envelope();
envelope.signatures.clear();
let bytes = encode_container_record_for_test(ObjectType::Blob, &envelope)?;
let replay = decode_container_records(ObjectType::Blob, &bytes)?;
assert!(!replay.has_item_failure());
assert_eq!(replay.records.len(), 1);
assert!(replay.records[0].envelope.signatures.is_empty());
Ok(())
}