use std::error::Error;
use super::restore_checkpoint_locator::{
CheckpointBodyKey, CheckpointLocator, CheckpointLocatorError, CheckpointSource,
advance_prefix_digest, checkpoint_conversation_route, checkpoint_prefix_seed,
};
fn locator(history: u64) -> Result<CheckpointLocator, CheckpointLocatorError> {
CheckpointLocator::new(
7,
history,
history,
[3; 32],
CheckpointBodyKey::for_body(b"body"),
)
}
#[test]
fn locator_roundtrip_preserves_route_identity_and_every_field() -> Result<(), Box<dyn Error>> {
let original = locator(1_000)?;
let bytes = original.encode_canonical();
let restored = CheckpointLocator::decode_canonical(&bytes, 7)?;
assert_eq!(original, restored);
assert_eq!(restored.covered_sequence(), 1_000);
assert_eq!(restored.extension_boundary(), 1_000);
assert_eq!(restored.prefix_digest(), &[3; 32]);
assert_eq!(
restored.body_key().routed_key(7),
original.body_key().routed_key(7)
);
assert!(matches!(
CheckpointLocator::decode_canonical(&bytes, 8),
Err(CheckpointLocatorError::ForeignConversation {
expected: 8,
actual: 7
})
));
Ok(())
}
#[test]
fn locator_has_fixed_length_at_each_history_position() -> Result<(), Box<dyn Error>> {
for history in [1_000, 42_005, 100_000, 420_000] {
assert_eq!(locator(history)?.encode_canonical().len(), 128);
}
Ok(())
}
#[test]
fn locator_refuses_every_truncation_change_and_trailing_byte() -> Result<(), Box<dyn Error>> {
let bytes = locator(1_000)?.encode_canonical();
for end in 0..bytes.len() {
assert!(CheckpointLocator::decode_canonical(&bytes[..end], 7).is_err());
}
for at in 0..bytes.len() {
let mut changed = bytes;
changed[at] ^= 1;
assert!(CheckpointLocator::decode_canonical(&changed, 7).is_err());
}
let mut trailing = bytes.to_vec();
trailing.push(0);
assert!(matches!(
CheckpointLocator::decode_canonical(&trailing, 7),
Err(CheckpointLocatorError::Length {
expected: 128,
actual: 129
})
));
Ok(())
}
#[test]
fn checksum_cannot_authorize_an_unsupported_version_or_exhausted_head() -> Result<(), Box<dyn Error>>
{
let mut version = locator(1_000)?.encode_canonical();
version[4..6].copy_from_slice(&2_u16.to_be_bytes());
let digest = *blake3::hash(&version[..96]).as_bytes();
version[96..].copy_from_slice(&digest);
assert!(matches!(
CheckpointLocator::decode_canonical(&version, 7),
Err(CheckpointLocatorError::Version { major: 2, minor: 0 })
));
assert!(matches!(
CheckpointLocator::new(
7,
u64::MAX,
0,
[3; 32],
CheckpointBodyKey::for_body(b"body")
),
Err(CheckpointLocatorError::CoveredSequenceExhausted)
));
Ok(())
}
#[test]
fn body_key_names_and_verifies_the_exact_body_bytes() {
let key = CheckpointBodyKey::for_body(b"body");
assert!(key.verify_body(b"body").is_ok());
assert!(matches!(
key.verify_body(b"changed"),
Err(CheckpointLocatorError::BodyDigest)
));
assert_ne!(key.routed_key(7), CheckpointLocator::routed_key(7));
assert_ne!(
key.routed_key(7),
CheckpointBodyKey::for_body(b"changed").routed_key(7)
);
}
#[test]
fn prefix_digest_binds_source_sequence_bytes_and_merge_order() -> Result<(), Box<dyn Error>> {
let seed = checkpoint_prefix_seed();
let base = advance_prefix_digest(seed, CheckpointSource::Base, 1, b"row")?;
assert_ne!(
base,
advance_prefix_digest(seed, CheckpointSource::Extension, 1, b"row")?
);
assert_ne!(
base,
advance_prefix_digest(seed, CheckpointSource::Base, 2, b"row")?
);
assert_ne!(
base,
advance_prefix_digest(seed, CheckpointSource::Base, 1, b"changed")?
);
let first_order = advance_prefix_digest(base, CheckpointSource::Extension, 2, b"next")?;
let reverse = advance_prefix_digest(seed, CheckpointSource::Extension, 2, b"next")?;
let second_order = advance_prefix_digest(reverse, CheckpointSource::Base, 1, b"row")?;
assert_ne!(first_order, second_order);
Ok(())
}
#[test]
fn checkpoint_route_is_the_exact_original_conversation_stream_key() {
for (conversation_id, expected) in [
(0, "liminal:participant-production:0"),
(41, "liminal:participant-production:41"),
(
u64::MAX,
"liminal:participant-production:18446744073709551615",
),
] {
assert_eq!(checkpoint_conversation_route(conversation_id), expected);
}
}
#[test]
fn locator_route_is_independent_of_covered_log_and_extension_positions()
-> Result<(), Box<dyn Error>> {
let fixture = locator(1_000)?;
let expected = "liminal:participant-production:7";
for (covered, extension) in [(0, 0), (1_000, 1_000), (100_000, 100_000)] {
let locator = CheckpointLocator::new(
7,
covered,
extension,
*fixture.prefix_digest(),
fixture.body_key(),
)?;
assert_eq!(locator.conversation_route(), expected);
let decoded = CheckpointLocator::decode_canonical(&locator.encode_canonical(), 7)?;
assert_eq!(decoded.conversation_route(), expected);
}
Ok(())
}