use std::collections::{BTreeMap, HashMap};
use boundgraph_core::{Canonical, Digest, from_canonical_cbor, to_canonical_cbor};
use proptest::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct Specimen {
name: String,
count: u64,
flag: bool,
tags: Vec<String>,
attrs: BTreeMap<String, String>,
note: Option<String>,
blob: Vec<u8>,
}
fn specimen() -> impl Strategy<Value = Specimen> {
(
".{0,24}",
any::<u64>(),
any::<bool>(),
prop::collection::vec(".{0,8}", 0..4),
prop::collection::btree_map(".{0,8}", ".{0,8}", 0..4),
prop::option::of(".{0,16}"),
prop::collection::vec(any::<u8>(), 0..24),
)
.prop_map(|(name, count, flag, tags, attrs, note, blob)| Specimen {
name,
count,
flag,
tags,
attrs,
note,
blob,
})
}
proptest! {
#[test]
fn equal_values_share_a_digest(s in specimen()) {
let clone = s.clone();
prop_assert_eq!(s.digest().unwrap(), clone.digest().unwrap());
}
#[test]
fn encoding_is_deterministic(s in specimen()) {
let first = to_canonical_cbor(&s).unwrap();
let second = to_canonical_cbor(&s).unwrap();
prop_assert_eq!(first, second);
}
#[test]
fn round_trip_preserves_value_and_digest(s in specimen()) {
let bytes = to_canonical_cbor(&s).unwrap();
let back: Specimen = from_canonical_cbor(&bytes).unwrap();
prop_assert_eq!(&s, &back);
prop_assert_eq!(s.digest().unwrap(), back.digest().unwrap());
}
#[test]
fn any_field_change_changes_the_digest(s in specimen()) {
let base = s.digest().unwrap();
let mut m = s.clone();
m.name.push('!');
prop_assert_ne!(base, m.digest().unwrap());
let mut m = s.clone();
m.count = m.count.wrapping_add(1);
prop_assert_ne!(base, m.digest().unwrap());
let mut m = s.clone();
m.flag = !m.flag;
prop_assert_ne!(base, m.digest().unwrap());
let mut m = s.clone();
m.tags.push("extra".into());
prop_assert_ne!(base, m.digest().unwrap());
let mut m = s.clone();
m.attrs.insert("added-key".into(), "v".into());
prop_assert_ne!(base, m.digest().unwrap());
let mut m = s.clone();
m.note = match m.note {
None => Some(String::new()),
Some(_) => None,
};
prop_assert_ne!(base, m.digest().unwrap());
let mut m = s.clone();
m.blob.push(0);
prop_assert_ne!(base, m.digest().unwrap());
}
#[test]
fn iteration_order_does_not_affect_identity(
entries in prop::collection::btree_map(".{1,6}", ".{0,6}", 1..6)
) {
let forward: HashMap<String, String> =
entries.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
let reverse: HashMap<String, String> =
entries.iter().rev().map(|(k, v)| (k.clone(), v.clone())).collect();
prop_assert_eq!(forward.len(), reverse.len());
prop_assert_eq!(forward.digest().unwrap(), reverse.digest().unwrap());
}
#[test]
fn digest_hex_round_trips(bytes in prop::collection::vec(any::<u8>(), 0..64)) {
let d = Digest::of(&bytes);
let hex = d.to_hex();
prop_assert_eq!(hex.len(), 64);
prop_assert_eq!(hex.parse::<Digest>().unwrap(), d);
}
#[test]
fn distinct_inputs_give_distinct_digests(
a in prop::collection::vec(any::<u8>(), 0..32),
b in prop::collection::vec(any::<u8>(), 0..32),
) {
prop_assume!(a != b);
prop_assert_ne!(Digest::of(&a), Digest::of(&b));
}
}