use serde::{Deserialize, Serialize};
use postbag::{
Error,
cfg::{Full, Slim, Version},
from_full_slice, from_slice, from_slim_slice, to_full_vec, to_slim_vec, to_vec,
};
#[derive(Debug, PartialEq, Serialize, Deserialize)]
struct Value {
a: u32,
b: String,
}
fn value() -> Value {
Value { a: 7, b: "hi".into() }
}
#[test]
fn the_header_is_two_bytes() {
let full = to_full_vec(&value()).unwrap();
let slim = to_slim_vec(&value()).unwrap();
assert_eq!(full[..2], [0xba, 0b1011_0001]);
assert_eq!(slim[..2], [0xba, 0b1010_0001]);
assert_eq!(full[1] ^ slim[1], 0b0001_0000);
}
#[test]
fn without_a_header_the_value_stands_alone() {
let with = to_full_vec(&value()).unwrap();
let without = to_vec(Full::new().with_header(false), &value()).unwrap();
assert_eq!(with.len(), without.len() + 2);
assert_eq!(with[2..], without[..]);
}
#[test]
fn a_header_round_trips() {
assert_eq!(from_full_slice::<Value>(&to_full_vec(&value()).unwrap()).unwrap(), value());
assert_eq!(from_slim_slice::<Value>(&to_slim_vec(&value()).unwrap()).unwrap(), value());
let cfg = Full::new().with_header(false);
assert_eq!(from_slice::<Value, _>(cfg, &to_vec(cfg, &value()).unwrap()).unwrap(), value());
}
#[test]
fn a_swapped_configuration_is_refused() {
let full = to_full_vec(&value()).unwrap();
let slim = to_slim_vec(&value()).unwrap();
assert!(matches!(from_slim_slice::<Value>(&full), Err(Error::WithIdentsMismatch(true))));
assert!(matches!(from_full_slice::<Value>(&slim), Err(Error::WithIdentsMismatch(false))));
}
#[test]
fn what_is_not_a_header_is_refused() {
let headerless = to_vec(Full::new().with_header(false), &value()).unwrap();
assert!(matches!(from_full_slice::<Value>(&headerless), Err(Error::BadHeader)));
for text in ["{\"a\":7}", "a,b,c", "<xml/>", ""] {
let res = from_full_slice::<Value>(text.as_bytes());
assert!(matches!(res, Err(Error::BadHeader) | Err(Error::Io(_))), "{text:?} gave {res:?}");
}
}
#[test]
fn an_unknown_version_is_refused() {
let mut bytes = to_full_vec(&value()).unwrap();
for version in [0u8, 2, 7, 15] {
bytes[1] = (bytes[1] & !0b0000_1111) | version;
let res = from_full_slice::<Value>(&bytes);
assert!(
matches!(res, Err(Error::UnsupportedVersion(v)) if v == version),
"version {version} gave {res:?}"
);
}
}
#[test]
fn a_wrong_fixed_pattern_is_refused() {
let bytes = to_full_vec(&value()).unwrap();
for pattern in [0b0000_0000, 0b0100_0000, 0b1100_0000, 0b1110_0000] {
let mut bytes = bytes.clone();
bytes[1] = (bytes[1] & !0b1110_0000) | pattern;
assert!(matches!(from_full_slice::<Value>(&bytes), Err(Error::BadHeader)), "pattern {pattern:#010b}");
}
for second in [0x00, 0xff] {
let mut bytes = bytes.clone();
bytes[1] = second;
assert!(from_full_slice::<Value>(&bytes).is_err(), "second byte {second:#04x}");
}
}
#[test]
fn the_legacy_version_never_carries_a_header() {
let legacy = Full::new().with_version(Version::Postbag0_4);
assert!(!legacy.header());
assert!(!legacy.with_header(true).header());
let bytes = to_vec(legacy.with_header(true), &value()).unwrap();
assert_ne!(bytes[0], 0xba, "a header was written for a version that knows none");
assert_eq!(from_slice::<Value, _>(legacy, &bytes).unwrap(), value());
}
#[test]
fn the_setting_does_not_depend_on_order() {
let a = Full::new().with_header(true).with_version(Version::Postbag0_4);
let b = Full::new().with_version(Version::Postbag0_4).with_header(true);
assert_eq!(a.header(), b.header());
let c = Slim::new().with_header(false).with_version(Version::Postbag1);
let d = Slim::new().with_version(Version::Postbag1).with_header(false);
assert_eq!(c.header(), d.header());
}
#[test]
fn a_truncated_header_is_refused() {
let bytes = to_full_vec(&value()).unwrap();
for len in 0..2 {
assert!(from_full_slice::<Value>(&bytes[..len]).is_err(), "truncated to {len} bytes");
}
}