use weir_core::{
DecodeError, Durability, Envelope, HEADER_LEN, Header, MAX_PAYLOAD_HARD_CAP, MessageType,
NackReason,
};
#[test]
fn push_header_round_trip() {
let h = Header::new(MessageType::Push, Durability::Durable, 0);
assert_eq!(Header::decode(&h.encode()).unwrap(), h);
}
#[test]
fn all_message_types_round_trip() {
for mt in [
MessageType::Push,
MessageType::Ack,
MessageType::Nack,
MessageType::HealthCheck,
MessageType::HealthCheckResponse,
] {
let h = Header::new(mt, Durability::Durable, 0);
assert_eq!(Header::decode(&h.encode()).unwrap().message_type(), mt);
}
}
#[test]
fn all_durability_tiers_round_trip() {
for d in [Durability::Durable, Durability::Buffered] {
let h = Header::new(MessageType::Push, d, 0);
assert_eq!(Header::decode(&h.encode()).unwrap().durability(), d);
}
}
#[test]
fn header_is_exactly_16_bytes() {
let encoded = Header::new(MessageType::Push, Durability::Durable, 0).encode();
assert_eq!(encoded.len(), HEADER_LEN);
}
#[test]
fn nonzero_flags_rejected() {
let h = Header::new(MessageType::Push, Durability::Durable, 0xAB);
assert_eq!(
Header::decode(&h.encode()),
Err(DecodeError::ReservedFlagsSet { flags: 0xAB })
);
}
#[test]
fn zero_flags_accepted() {
let h = Header::new(MessageType::Push, Durability::Durable, 0);
assert_eq!(Header::decode(&h.encode()).unwrap().flags(), 0);
}
#[test]
fn decode_rejects_bad_magic() {
let mut buf = Header::new(MessageType::Push, Durability::Durable, 0).encode();
buf[0] = 0x00;
assert_eq!(Header::decode(&buf), Err(DecodeError::BadMagic));
}
#[test]
fn decode_rejects_future_version() {
let mut buf = Header::new(MessageType::Push, Durability::Durable, 0).encode();
buf[4] = 0xFF;
let crc = crc32fast::hash(&buf[..12]).to_le_bytes();
buf[12..16].copy_from_slice(&crc);
assert!(matches!(
Header::decode(&buf),
Err(DecodeError::VersionMismatch { .. })
));
}
#[test]
fn decode_rejects_corrupted_header_crc() {
let mut buf = Header::new(MessageType::Push, Durability::Durable, 0).encode();
buf[12] ^= 0xFF;
assert!(matches!(
Header::decode(&buf),
Err(DecodeError::HeaderCrcMismatch { .. })
));
}
#[test]
fn decode_rejects_unknown_message_type() {
let mut buf = Header::new(MessageType::Push, Durability::Durable, 0).encode();
buf[5] = 0xFF;
let crc = crc32fast::hash(&buf[..12]).to_le_bytes();
buf[12..16].copy_from_slice(&crc);
assert_eq!(
Header::decode(&buf),
Err(DecodeError::UnknownMessageType(0xFF))
);
}
#[test]
fn decode_rejects_unknown_durability() {
let mut buf = Header::new(MessageType::Push, Durability::Durable, 0).encode();
buf[6] = 0xFF;
let crc = crc32fast::hash(&buf[..12]).to_le_bytes();
buf[12..16].copy_from_slice(&crc);
assert_eq!(
Header::decode(&buf),
Err(DecodeError::UnknownDurability(0xFF))
);
}
#[test]
fn envelope_round_trip_nonempty_payload() {
let payload = b"hello weir".to_vec();
let header = Header::new(MessageType::Push, Durability::Durable, 0);
let env = Envelope::new(header, payload);
assert_eq!(Envelope::decode(&env.encode()).unwrap(), env);
}
#[test]
fn envelope_round_trip_empty_payload() {
let header = Header::new(MessageType::Ack, Durability::Durable, 0);
let env = Envelope::new(header, vec![]);
assert_eq!(Envelope::decode(&env.encode()).unwrap(), env);
}
#[test]
fn envelope_round_trip_max_size_payload() {
let payload = vec![0xABu8; 1024];
let header = Header::new(MessageType::Push, Durability::Buffered, 0);
let env = Envelope::new(header, payload);
assert_eq!(Envelope::decode(&env.encode()).unwrap(), env);
}
#[test]
fn envelope_decode_rejects_truncated_header() {
let env = Envelope::new(
Header::new(MessageType::Push, Durability::Durable, 0),
b"data".to_vec(),
);
let encoded = env.encode();
assert_eq!(
Envelope::decode(&encoded[..HEADER_LEN - 1]),
Err(DecodeError::TruncatedFrame)
);
}
#[test]
fn envelope_decode_rejects_truncated_payload() {
let payload = b"hello".to_vec();
let env = Envelope::new(
Header::new(MessageType::Push, Durability::Durable, 0),
payload,
);
let encoded = env.encode();
assert_eq!(
Envelope::decode(&encoded[..encoded.len() - 1]),
Err(DecodeError::TruncatedFrame)
);
}
#[test]
fn envelope_decode_rejects_corrupted_payload_crc() {
let payload = b"hello".to_vec();
let env = Envelope::new(
Header::new(MessageType::Push, Durability::Durable, 0),
payload,
);
let mut encoded = env.encode();
let last = encoded.len() - 1;
encoded[last] ^= 0xFF;
assert!(matches!(
Envelope::decode(&encoded),
Err(DecodeError::PayloadCrcMismatch { .. })
));
}
#[test]
fn envelope_decode_rejects_oversized_payload_len() {
let frame = Envelope::new(
Header::new(MessageType::Push, Durability::Durable, 0),
vec![0u8; MAX_PAYLOAD_HARD_CAP + 1],
)
.encode();
assert_eq!(
Envelope::decode(&frame[..HEADER_LEN]),
Err(DecodeError::PayloadTooLarge {
len: MAX_PAYLOAD_HARD_CAP + 1,
cap: MAX_PAYLOAD_HARD_CAP,
})
);
}
#[test]
fn wire_tryfrom_error_structs_are_re_exported_at_crate_root() {
use weir_core::{
Durability, MessageType, UnknownDurability, UnknownMessageType, UnknownNackReason,
};
let e: UnknownMessageType = MessageType::try_from(0xFF).unwrap_err();
assert_eq!(e.0, 0xFF);
let e: UnknownDurability = Durability::try_from(0xFF).unwrap_err();
assert_eq!(e.0, 0xFF);
let e: UnknownNackReason = NackReason::try_from(0xFF).unwrap_err();
assert_eq!(e.0, 0xFF);
}
#[test]
fn nack_reason_repr_values_are_stable() {
assert_eq!(NackReason::BadMagic as u8, 0x01);
assert_eq!(NackReason::VersionMismatch as u8, 0x02);
assert_eq!(NackReason::BadHeaderCrc as u8, 0x03);
assert_eq!(NackReason::PayloadTooLarge as u8, 0x04);
assert_eq!(NackReason::BadPayloadCrc as u8, 0x05);
assert_eq!(NackReason::InternalError as u8, 0x06);
assert_eq!(NackReason::EmptyPayload as u8, 0x07);
assert_eq!(NackReason::UnknownMessage as u8, 0x08);
assert_eq!(NackReason::ReservedFlagsSet as u8, 0x09);
}
#[test]
fn nack_reason_round_trips_all_known_values() {
for r in [
NackReason::BadMagic,
NackReason::VersionMismatch,
NackReason::BadHeaderCrc,
NackReason::PayloadTooLarge,
NackReason::BadPayloadCrc,
NackReason::InternalError,
NackReason::EmptyPayload,
NackReason::UnknownMessage,
NackReason::ReservedFlagsSet,
] {
assert_eq!(NackReason::try_from(r as u8).unwrap(), r);
}
}
#[test]
fn nack_reason_rejects_unknown_byte() {
assert!(NackReason::try_from(0x00).is_err());
assert!(NackReason::try_from(0x0A).is_err());
assert!(NackReason::try_from(0xFF).is_err());
}
mod proptest_wire {
use proptest::prelude::*;
use weir_core::{DecodeError, Durability, Envelope, HEADER_LEN, Header, MessageType};
fn any_message_type() -> impl Strategy<Value = MessageType> {
prop_oneof![
Just(MessageType::Push),
Just(MessageType::Ack),
Just(MessageType::Nack),
Just(MessageType::HealthCheck),
Just(MessageType::HealthCheckResponse),
]
}
fn any_durability() -> impl Strategy<Value = Durability> {
prop_oneof![Just(Durability::Durable), Just(Durability::Buffered)]
}
proptest! {
#[test]
fn header_encode_decode_roundtrip(
mt in any_message_type(),
d in any_durability(),
flags in any::<u8>(),
) {
let h = Header::new(mt, d, flags);
let encoded = h.encode();
if flags == 0 {
prop_assert_eq!(Header::decode(&encoded), Ok(h));
} else {
prop_assert_eq!(
Header::decode(&encoded),
Err(DecodeError::ReservedFlagsSet { flags })
);
}
}
#[test]
fn envelope_encode_decode_roundtrip(
payload in proptest::collection::vec(any::<u8>(), 0..1024),
) {
let header = Header::new(
MessageType::Push,
Durability::Durable,
0);
let env = Envelope::new(header, payload);
prop_assert_eq!(Envelope::decode(&env.encode()), Ok(env));
}
#[test]
fn header_decode_never_panics(bytes: [u8; HEADER_LEN]) {
let _ = Header::decode(&bytes);
}
#[test]
fn envelope_decode_never_panics(
bytes in proptest::collection::vec(any::<u8>(), 0..2048),
) {
let _ = Envelope::decode(&bytes);
}
#[test]
fn header_single_byte_mutation_never_panics(
mt in any_message_type(),
d in any_durability(),
pos in 0usize..HEADER_LEN,
corrupt_byte in any::<u8>(),
) {
let h = Header::new(mt, d, 0);
let mut buf = h.encode();
buf[pos] = corrupt_byte;
let _ = Header::decode(&buf);
}
}
}