use amaters_core::{CipherBlob, Key, Query, col};
use amaters_sdk_rust::{Row, SdkError, query};
use proptest::prelude::*;
fn arb_bytes() -> impl Strategy<Value = Vec<u8>> {
prop::collection::vec(any::<u8>(), 0..=256)
}
fn arb_name() -> impl Strategy<Value = String> {
"[a-zA-Z][a-zA-Z0-9_:]{0,63}".prop_map(|s| s)
}
proptest! {
#[test]
fn proptest_query_builder_roundtrip(
collection in arb_name(),
key_bytes in arb_bytes(),
value_bytes in arb_bytes(),
) {
let key = Key::from_slice(&key_bytes);
let q = query(&collection).get(key.clone());
match &q {
Query::Get { collection: c, key: k } => {
prop_assert_eq!(c, &collection);
prop_assert_eq!(k.as_bytes(), key.as_bytes());
}
other => prop_assert!(false, "expected Get, got {:?}", other),
}
let value = CipherBlob::new(value_bytes.clone());
let q = query(&collection).set(key.clone(), value.clone());
match &q {
Query::Set { collection: c, key: k, value: v } => {
prop_assert_eq!(c, &collection);
prop_assert_eq!(k.as_bytes(), key.as_bytes());
prop_assert_eq!(v.to_vec(), value_bytes);
}
other => prop_assert!(false, "expected Set, got {:?}", other),
}
let q = query(&collection).delete(key.clone());
match &q {
Query::Delete { collection: c, key: k } => {
prop_assert_eq!(c, &collection);
prop_assert_eq!(k.as_bytes(), key.as_bytes());
}
other => prop_assert!(false, "expected Delete, got {:?}", other),
}
let start = Key::from_slice(&key_bytes);
let end_bytes: Vec<u8> = key_bytes.iter().map(|b| b.saturating_add(1)).collect();
let end = Key::from_slice(&end_bytes);
let q = query(&collection).range(start.clone(), end.clone());
match &q {
Query::Range { collection: c, start: s, end: e } => {
prop_assert_eq!(c, &collection);
prop_assert_eq!(s.as_bytes(), start.as_bytes());
prop_assert_eq!(e.as_bytes(), end.as_bytes());
}
other => prop_assert!(false, "expected Range, got {:?}", other),
}
}
}
proptest! {
#[test]
fn proptest_error_display_not_empty(msg in ".*") {
let variants: &[SdkError] = &[
SdkError::Connection(msg.clone()),
SdkError::Timeout(msg.clone()),
SdkError::Configuration(msg.clone()),
SdkError::Serialization(msg.clone()),
SdkError::Fhe(msg.clone()),
SdkError::InvalidArgument(msg.clone()),
SdkError::NotFound(msg.clone()),
SdkError::OperationFailed(msg.clone()),
SdkError::Other(msg.clone()),
];
for err in variants {
let display = format!("{err}");
prop_assert!(
!display.is_empty(),
"Display output for {:?} was empty",
err
);
}
}
}
proptest! {
#[test]
fn proptest_row_bytes_roundtrip(
key_bytes in arb_bytes(),
value_bytes in arb_bytes(),
) {
let row = Row::new(key_bytes.clone(), value_bytes.clone());
prop_assert_eq!(&row.key, &key_bytes, "key bytes should be preserved");
prop_assert_eq!(&row.value, &value_bytes, "value bytes should be preserved");
}
}
proptest! {
#[test]
fn proptest_filter_builder_roundtrip(
collection in arb_name(),
col_name in "[a-z][a-z_]{0,15}",
value_bytes in arb_bytes(),
) {
let value = CipherBlob::new(value_bytes);
let q = query(&collection)
.where_clause()
.eq(col(&col_name), value)
.build();
match &q {
Query::Filter { collection: c, predicate } => {
prop_assert_eq!(c, &collection);
prop_assert!(
matches!(predicate, amaters_core::Predicate::Eq(_, _)),
"expected Eq predicate"
);
}
other => prop_assert!(false, "expected Filter, got {:?}", other),
}
}
}
#[cfg(feature = "serialization")]
proptest! {
#[test]
fn proptest_codec_roundtrip(data in arb_bytes()) {
use oxicode::serde::{decode_serde, encode_serde};
let encoded: Vec<u8> = encode_serde(&data)
.expect("encode should succeed");
let decoded: Vec<u8> = decode_serde(&encoded)
.expect("decode should succeed");
prop_assert_eq!(decoded, data, "round-trip should preserve bytes");
}
}