use proptest::prelude::*;
use serde_json::{json, Value};
use sync_engine::{SyncItem, SizedItem};
fn valid_sync_item_strategy() -> impl Strategy<Value = SyncItem> {
(
"[a-z]{1,10}(\\.[a-z]{1,10}){0,5}", prop::collection::vec(any::<u8>(), 0..1000), )
.prop_map(|(id, bytes)| {
SyncItem::new(id, bytes)
})
}
fn arbitrary_json_strategy() -> impl Strategy<Value = Value> {
let leaf = prop_oneof![
Just(Value::Null),
any::<bool>().prop_map(Value::Bool),
any::<i64>().prop_map(|n| Value::Number(n.into())),
".*".prop_map(Value::String),
];
leaf.prop_recursive(
4, 64, 10, |inner| {
prop_oneof![
prop::collection::vec(inner.clone(), 0..10)
.prop_map(Value::Array),
prop::collection::hash_map(".*", inner, 0..10)
.prop_map(|m| Value::Object(m.into_iter().collect())),
]
},
)
}
proptest! {
#[test]
fn fuzz_sync_item_from_random_bytes(bytes in prop::collection::vec(any::<u8>(), 0..10000)) {
let result: Result<SyncItem, _> = serde_json::from_slice(&bytes);
let _ = result;
}
#[test]
fn fuzz_sync_item_from_arbitrary_json(json in arbitrary_json_strategy()) {
let serialized = serde_json::to_vec(&json).unwrap();
let result: Result<SyncItem, _> = serde_json::from_slice(&serialized);
let _ = result;
}
#[test]
fn fuzz_corrupted_sync_item(
item in valid_sync_item_strategy(),
corruption in prop::collection::vec(any::<u8>(), 1..50),
position in 0usize..10000,
) {
let serialized = serde_json::to_vec(&item).unwrap();
if serialized.is_empty() {
return Ok(());
}
let mut corrupted = serialized.clone();
let pos = position % corrupted.len();
for (i, b) in corruption.iter().enumerate() {
let idx = (pos + i) % corrupted.len();
corrupted[idx] ^= b; }
let result: Result<SyncItem, _> = serde_json::from_slice(&corrupted);
let _ = result;
}
}
proptest! {
#[test]
fn prop_merkle_hash_deterministic(content in ".*") {
use sha2::{Sha256, Digest};
let hash1 = Sha256::digest(content.as_bytes());
let hash2 = Sha256::digest(content.as_bytes());
prop_assert_eq!(hash1, hash2, "Same content should produce same hash");
}
#[test]
fn prop_merkle_hash_collision_resistant(
content1 in ".{1,100}",
content2 in ".{1,100}",
) {
use sha2::{Sha256, Digest};
if content1 != content2 {
let hash1 = Sha256::digest(content1.as_bytes());
let hash2 = Sha256::digest(content2.as_bytes());
prop_assert_ne!(hash1.as_slice(), hash2.as_slice(),
"Different content should produce different hashes");
}
}
}
proptest! {
#[test]
fn prop_sync_item_size_never_panics(
id in ".*",
content in arbitrary_json_strategy(),
) {
let item = SyncItem::from_json(id, content);
let size = item.size_bytes();
prop_assert!(size > 0, "Size should always be positive");
}
#[test]
fn prop_sync_item_roundtrip(item in valid_sync_item_strategy()) {
let serialized = serde_json::to_vec(&item).unwrap();
let deserialized: SyncItem = serde_json::from_slice(&serialized).unwrap();
prop_assert_eq!(item.object_id, deserialized.object_id);
prop_assert_eq!(item.version, deserialized.version);
prop_assert_eq!(item.content, deserialized.content);
}
#[test]
fn prop_object_id_special_chars(
id in "[\\x00-\\xFF]{0,255}",
) {
let item = SyncItem::new(id.clone(), b"test".to_vec());
prop_assert_eq!(&item.object_id, &id);
if let Ok(serialized) = serde_json::to_vec(&item) {
let _ = serde_json::from_slice::<SyncItem>(&serialized);
}
}
}
proptest! {
#[test]
fn prop_batch_integrity(
items in prop::collection::vec(valid_sync_item_strategy(), 0..100),
) {
let total_size: usize = items.iter().map(|i| i.size_bytes()).sum();
prop_assert!(total_size >= items.len() * std::mem::size_of::<SyncItem>());
}
}
proptest! {
#[test]
fn prop_empty_and_null_handling(
null_count in 0usize..100,
) {
let id = "\0".repeat(null_count);
let item = SyncItem::new(id.clone(), vec![]);
prop_assert_eq!(item.object_id.len(), null_count);
prop_assert!(item.size_bytes() > 0);
}
#[test]
fn prop_large_content(
size in 0usize..100_000,
) {
let large_bytes: Vec<u8> = vec![b'x'; size];
let item = SyncItem::new("test.large".into(), large_bytes);
let computed_size = item.size_bytes();
prop_assert!(computed_size >= size, "Size should include content");
}
#[test]
fn prop_deeply_nested_json(depth in 0usize..50) {
let mut value = json!({"leaf": "data"});
for i in 0..depth {
value = json!({ format!("level_{}", i): value });
}
let item = SyncItem::from_json("test.nested".into(), value);
prop_assert!(item.size_bytes() > 0);
let result = serde_json::to_vec(&item);
prop_assert!(result.is_ok());
}
}