use crate::value::graph_entities::{Edge, Node};
use crate::value::path::Path;
use crate::value::point::Point;
use crate::value::vec32::Vec32;
use crate::{from_falkor_row, from_falkor_value, FalkorValue};
use proptest::collection::{hash_map, vec};
use proptest::prelude::*;
use serde::Deserialize;
use serde_json::{Map, Number, Value as Json};
use std::collections::HashMap;
fn json_strategy() -> impl Strategy<Value = Json> {
let leaf = prop_oneof![
Just(Json::Null),
any::<bool>().prop_map(Json::Bool),
any::<i64>().prop_map(|n| Json::Number(n.into())),
(-1.0e12f64..1.0e12f64)
.prop_map(|f| Json::Number(Number::from_f64(f).expect("bounded range is finite"))),
".*".prop_map(Json::String),
];
leaf.prop_recursive(4, 48, 6, |inner| {
prop_oneof![
vec(inner.clone(), 0..6).prop_map(Json::Array),
hash_map("[a-z]{1,8}", inner, 0..6)
.prop_map(|m| Json::Object(m.into_iter().collect::<Map<String, Json>>())),
]
})
}
fn json_to_falkor(value: &Json) -> FalkorValue {
match value {
Json::Null => FalkorValue::None,
Json::Bool(b) => FalkorValue::Bool(*b),
Json::Number(n) => n
.as_i64()
.map(FalkorValue::I64)
.unwrap_or_else(|| FalkorValue::F64(n.as_f64().expect("non-i64 number is f64"))),
Json::String(s) => FalkorValue::String(s.clone()),
Json::Array(items) => FalkorValue::Array(items.iter().map(json_to_falkor).collect()),
Json::Object(map) => FalkorValue::Map(
map.iter()
.map(|(k, v)| (k.clone(), json_to_falkor(v)))
.collect(),
),
}
}
fn falkor_strategy() -> impl Strategy<Value = FalkorValue> {
let leaf = prop_oneof![
Just(FalkorValue::None),
any::<bool>().prop_map(FalkorValue::Bool),
any::<i64>().prop_map(FalkorValue::I64),
any::<f64>().prop_map(FalkorValue::F64),
".*".prop_map(FalkorValue::String),
".*".prop_map(FalkorValue::Unparseable),
vec(any::<f32>(), 0..6).prop_map(|values| FalkorValue::Vec32(Vec32 { values })),
(any::<f64>(), any::<f64>()).prop_map(|(latitude, longitude)| {
FalkorValue::Point(Point {
latitude,
longitude,
})
}),
Just(FalkorValue::Path(Path::default())),
];
leaf.prop_recursive(4, 64, 6, |inner| {
prop_oneof![
vec(inner.clone(), 0..6).prop_map(FalkorValue::Array),
hash_map("[a-z]{1,8}", inner.clone(), 0..6).prop_map(FalkorValue::Map),
(
any::<i64>(),
vec("[a-z]{1,8}", 0..3),
hash_map("[a-z]{1,8}", inner.clone(), 0..4),
)
.prop_map(|(entity_id, labels, properties)| {
FalkorValue::Node(Node {
entity_id,
labels,
properties,
})
}),
(
any::<i64>(),
"[a-z]{1,8}",
any::<i64>(),
any::<i64>(),
hash_map("[a-z]{1,8}", inner, 0..4),
)
.prop_map(
|(entity_id, relationship_type, src_node_id, dst_node_id, properties)| {
FalkorValue::Edge(Edge {
entity_id,
relationship_type,
src_node_id,
dst_node_id,
properties,
})
}
),
]
})
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
enum Sample {
Unit,
Newtype(i64),
Struct { x: i64 },
}
proptest! {
#[test]
fn prop_agrees_with_serde_json(json in json_strategy()) {
let falkor = json_to_falkor(&json);
let round_tripped: Json =
from_falkor_value(falkor).expect("shared-model values always deserialize");
prop_assert_eq!(round_tripped, json);
}
#[test]
fn prop_never_panics(value in falkor_strategy()) {
let _: Result<Json, _> = from_falkor_value(value.clone());
let _: Result<Vec<Json>, _> = from_falkor_value(value.clone());
let _: Result<HashMap<String, Json>, _> = from_falkor_value(value.clone());
let _: Result<Sample, _> = from_falkor_value(value);
}
#[test]
fn prop_single_column_row_matches_value(column in ".*", value in falkor_strategy()) {
let header = vec![column];
let from_row: Option<Json> = from_falkor_row(&header, vec![value.clone()]).ok();
let from_value: Option<Json> = from_falkor_value(value).ok();
prop_assert_eq!(from_row, from_value);
}
#[test]
fn prop_length_mismatch_is_rejected(
(header_len, values_len) in (0usize..6, 0usize..6)
.prop_filter("mismatched lengths", |(h, v)| h != v)
) {
let header: Vec<String> = (0..header_len).map(|i| format!("c{i}")).collect();
let values: Vec<FalkorValue> = (0..values_len).map(|i| FalkorValue::I64(i as i64)).collect();
let result: Result<Json, _> = from_falkor_row(&header, values);
prop_assert!(result.is_err());
}
}