use crate::{
parser::types::{parse_list_with_schema, parse_map_with_schema, CqlTypeId},
parser::vint::encode_vint,
schema::CqlType,
types::Value,
};
use proptest::prelude::*;
fn arb_simple_value(type_id: CqlTypeId) -> impl Strategy<Value = Value> {
match type_id {
CqlTypeId::Int => any::<i32>().prop_map(Value::Integer).boxed(),
CqlTypeId::BigInt => any::<i64>().prop_map(Value::BigInt).boxed(),
CqlTypeId::Boolean => any::<bool>().prop_map(Value::Boolean).boxed(),
CqlTypeId::Varchar => any::<String>()
.prop_filter("non-empty strings", |s| !s.is_empty())
.prop_map(Value::Text)
.boxed(),
_ => any::<i32>().prop_map(Value::Integer).boxed(),
}
}
fn arb_list_with_nulls(max_size: usize) -> impl Strategy<Value = Value> {
prop::collection::vec(
prop_oneof![
3 => arb_simple_value(CqlTypeId::Int),
1 => Just(Value::Null)
],
0..max_size,
)
.prop_map(Value::List)
}
fn arb_map_with_nulls(max_size: usize) -> impl Strategy<Value = Value> {
prop::collection::vec(
(
prop_oneof![
3 => arb_simple_value(CqlTypeId::Int),
1 => Just(Value::Null)
],
prop_oneof![
3 => arb_simple_value(CqlTypeId::Int),
1 => Just(Value::Null)
],
),
0..max_size,
)
.prop_map(Value::Map)
}
proptest! {
#[test]
fn prop_list_roundtrip_deterministic(list in arb_list_with_nulls(10)) {
let mut serialized = Vec::new();
let elements = if let Value::List(ref els) = list {
els
} else {
panic!("Expected list");
};
serialized.extend(encode_vint(elements.len() as i64));
for element in elements {
match element {
Value::Null => {
serialized.extend(encode_vint(-1));
}
Value::Integer(i) => {
serialized.extend(encode_vint(4));
serialized.extend_from_slice(&i.to_be_bytes());
}
_ => {}
}
}
let schema = CqlType::Int;
let result = parse_list_with_schema(&serialized, &schema);
prop_assert!(result.is_ok());
let (remaining, parsed) = result.unwrap();
prop_assert_eq!(remaining.len(), 0, "Buffer not fully consumed");
prop_assert_eq!(
if let Value::List(els) = &parsed { els.len() } else { 0 },
elements.len()
);
}
#[test]
fn prop_map_roundtrip_with_nulls(map in arb_map_with_nulls(5)) {
let mut serialized = Vec::new();
let pairs = if let Value::Map(ref ps) = map {
ps
} else {
panic!("Expected map");
};
serialized.extend(encode_vint(pairs.len() as i64));
for (key, value) in pairs {
match key {
Value::Null => {
serialized.extend(encode_vint(-1));
}
Value::Integer(i) => {
serialized.extend(encode_vint(4));
serialized.extend_from_slice(&i.to_be_bytes());
}
_ => {}
}
match value {
Value::Null => {
serialized.extend(encode_vint(-1));
}
Value::Integer(i) => {
serialized.extend(encode_vint(4));
serialized.extend_from_slice(&i.to_be_bytes());
}
_ => {}
}
}
let key_schema = CqlType::Int;
let value_schema = CqlType::Int;
let result = parse_map_with_schema(&serialized, &key_schema, &value_schema);
prop_assert!(result.is_ok());
let (remaining, parsed) = result.unwrap();
prop_assert_eq!(remaining.len(), 0, "Buffer not fully consumed in map");
prop_assert_eq!(
if let Value::Map(ps) = &parsed { ps.len() } else { 0 },
pairs.len()
);
}
#[test]
fn prop_nested_list_of_lists(
outer_size in 0usize..5,
inner_size in 0usize..3,
use_nulls in any::<bool>()
) {
let mut inner_lists = Vec::new();
for _ in 0..outer_size {
if use_nulls && inner_lists.len() % 2 == 0 {
inner_lists.push(Value::Null);
} else {
let mut inner = Vec::new();
for j in 0..inner_size {
inner.push(Value::Integer(j as i32));
}
inner_lists.push(Value::List(inner));
}
}
let nested_list = Value::List(inner_lists);
if let Value::List(outer) = &nested_list {
prop_assert_eq!(outer.len(), outer_size);
for elem in outer {
match elem {
Value::Null => {
}
Value::List(inner) => {
prop_assert!(inner.len() <= inner_size);
}
_ => prop_assert!(false, "Unexpected element type"),
}
}
}
}
}
#[test]
fn test_empty_list_with_schema() {
let mut data = Vec::new();
data.extend(encode_vint(0));
let schema = CqlType::Int;
let result = parse_list_with_schema(&data, &schema);
assert!(result.is_ok());
let (remaining, parsed) = result.unwrap();
assert_eq!(remaining.len(), 0, "Buffer should be fully consumed");
assert_eq!(parsed, Value::List(Vec::new()));
}
#[test]
fn test_list_all_nulls_with_schema() {
let mut data = Vec::new();
data.extend(encode_vint(3));
data.extend(encode_vint(-1));
data.extend(encode_vint(-1));
data.extend(encode_vint(-1));
let schema = CqlType::Int;
let result = parse_list_with_schema(&data, &schema);
assert!(result.is_ok());
let (remaining, parsed) = result.unwrap();
assert_eq!(remaining.len(), 0, "Buffer should be fully consumed");
assert_eq!(
parsed,
Value::List(vec![Value::Null, Value::Null, Value::Null])
);
}
#[test]
fn test_map_empty_with_schema() {
let mut data = Vec::new();
data.extend(encode_vint(0));
let key_schema = CqlType::Int;
let value_schema = CqlType::Text;
let result = parse_map_with_schema(&data, &key_schema, &value_schema);
assert!(result.is_ok());
let (remaining, parsed) = result.unwrap();
assert_eq!(remaining.len(), 0, "Buffer should be fully consumed");
assert_eq!(parsed, Value::Map(Vec::new()));
}
#[test]
fn test_map_with_null_value() {
let mut data = Vec::new();
data.extend(encode_vint(1));
data.extend(encode_vint(4));
data.extend_from_slice(&42i32.to_be_bytes());
data.extend(encode_vint(-1));
let key_schema = CqlType::Int;
let value_schema = CqlType::Text;
let result = parse_map_with_schema(&data, &key_schema, &value_schema);
assert!(result.is_ok());
let (remaining, parsed) = result.unwrap();
assert_eq!(remaining.len(), 0, "Buffer should be fully consumed");
if let Value::Map(pairs) = parsed {
assert_eq!(pairs.len(), 1);
assert_eq!(pairs[0].0, Value::Integer(42));
assert_eq!(pairs[0].1, Value::Null);
} else {
panic!("Expected map value");
}
}
#[test]
fn test_buffer_not_fully_consumed_detected() {
let mut data = Vec::new();
data.extend(encode_vint(1));
data.extend(encode_vint(4));
data.extend_from_slice(&42i32.to_be_bytes());
data.extend_from_slice(&[0xFF, 0xFF]);
let schema = CqlType::Int;
let result = parse_list_with_schema(&data, &schema);
assert!(result.is_ok());
let (remaining, _) = result.unwrap();
assert_eq!(remaining.len(), 2, "Extra bytes should remain");
}