use proptest::prelude::*;
use serde_json::Value;
fn arb_json() -> impl Strategy<Value = Value> {
let leaf = prop_oneof![
Just(Value::Null),
any::<bool>().prop_map(Value::Bool),
any::<i32>().prop_map(|n| Value::from(n as i64)),
"[a-zA-Z0-9_-]{0,16}".prop_map(Value::from),
];
leaf.prop_recursive(4, 16, 8, |inner| {
prop_oneof![
prop::collection::vec(inner.clone(), 0..6).prop_map(Value::Array),
prop::collection::hash_map("[a-z]{1,6}", inner, 0..6)
.prop_map(|m| { Value::Object(m.into_iter().collect()) })
]
})
}
proptest! {
#![proptest_config(ProptestConfig {
cases: 64,
.. ProptestConfig::default()
})]
#[test]
fn idempotence(v in arb_json()) {
let c1 = aitp_core::jcs::canonicalize(&v).unwrap();
let parsed: Value = serde_json::from_slice(&c1).unwrap();
let c2 = aitp_core::jcs::canonicalize(&parsed).unwrap();
prop_assert_eq!(c1, c2);
}
#[test]
fn whitespace_free(v in arb_json()) {
let c = aitp_core::jcs::canonicalize(&v).unwrap();
for &b in &c {
prop_assert!(
b != b' ' && b != b'\t' && b != b'\n' && b != b'\r',
"whitespace byte 0x{:02x} in canonical output",
b,
);
}
}
#[test]
fn order_invariance(
keys in proptest::collection::vec("[a-z]{1,4}", 1..8),
vals in proptest::collection::vec(any::<i32>(), 1..8),
) {
let n = keys.len().min(vals.len());
let pairs: Vec<(String, i64)> = keys.iter().take(n).cloned().zip(vals.iter().take(n).map(|v| *v as i64)).collect();
let dedup: std::collections::BTreeMap<String, i64> = pairs.into_iter().collect();
let mut as_vec: Vec<(String, i64)> = dedup.into_iter().collect();
let mut o1 = serde_json::Map::new();
for (k, v) in &as_vec {
o1.insert(k.clone(), Value::from(*v));
}
as_vec.reverse();
let mut o2 = serde_json::Map::new();
for (k, v) in &as_vec {
o2.insert(k.clone(), Value::from(*v));
}
let c1 = aitp_core::jcs::canonicalize(&Value::Object(o1)).unwrap();
let c2 = aitp_core::jcs::canonicalize(&Value::Object(o2)).unwrap();
prop_assert_eq!(c1, c2);
}
}