use proptest::prelude::*;
use serde_json::{Map, Value};
use super::{MAX_BREADTH, MAX_DEPTH, MAX_STRING_LENGTH, normalize};
use crate::test_support::json::index_keys_first;
use crate::test_support::rules::check_cut;
use crate::test_support::strategies::json_value;
use crate::test_support::{assert_same_json_text, config_capped, vectors};
fn input_of(case: &Map<String, Value>) -> Value {
let name = vectors::name(case);
match (case.get("input"), case.get("input_text")) {
(Some(input), None) => input.clone(),
(None, Some(Value::String(text))) => serde_json::from_str(text).expect("input_text"),
_ => panic!("{name}: needs exactly one of input and input_text"),
}
}
fn limits_of(case: &Map<String, Value>) -> (usize, usize, usize) {
let mut limits = [MAX_DEPTH, MAX_BREADTH, MAX_STRING_LENGTH];
if let Some(Value::Array(given)) = case.get("limits") {
for (slot, value) in limits.iter_mut().zip(given) {
*slot = usize::try_from(value.as_u64().expect("limit")).expect("limit fits");
}
}
(limits[0], limits[1], limits[2])
}
#[test]
fn fixture_cases_hold() {
let cases = vectors::cases("normalize");
assert!(cases.len() > 60, "the file has its cases");
for case in &cases {
let name = vectors::name(case);
let (depth, breadth, max_string) = limits_of(case);
let actual = normalize(&input_of(case), depth, breadth, max_string);
assert_same_json_text(&actual, &case["output"], name);
}
}
#[test]
fn normalize_matches_normalize_cases() {
let cases = vectors::cases("normalize_cases");
assert!(cases.len() >= 100, "normalize_cases.json has its cases");
for case in &cases {
let name = vectors::name(case);
let (depth, breadth, max_string) = limits_of(case);
let actual = normalize(&input_of(case), depth, breadth, max_string);
assert_same_json_text(&actual, &case["output"], name);
}
}
const MAX_PROPERTIES: &str = "[MaxProperties ~]";
fn check_normalized(
input: &Value,
output: &Value,
depth: usize,
breadth: usize,
max_string: usize,
) -> Result<(), TestCaseError> {
match (input, output) {
(Value::String(text), Value::String(out)) if text.encode_utf16().count() > max_string => {
let kept = out.strip_suffix("...").expect("a cut string ends with ...");
check_cut(text, kept, max_string)?;
}
(Value::Array(_), _) if depth == 0 => prop_assert_eq!(output, "[Array]"),
(Value::Object(_), _) if depth == 0 => prop_assert_eq!(output, "[Object]"),
(Value::Array(items), Value::Array(out)) => {
let kept = items.len().min(breadth);
let cut = items.len() > breadth;
prop_assert_eq!(out.len(), kept + usize::from(cut));
for (item, out) in items.iter().zip(out).take(kept) {
check_normalized(item, out, depth - 1, breadth, max_string)?;
}
if cut {
prop_assert_eq!(&out[kept], MAX_PROPERTIES);
}
}
(Value::Object(map), Value::Object(out)) => {
let cut = map.len() > breadth;
let mut keys: Vec<&str> = index_keys_first(map.keys().map(String::as_str));
keys.truncate(breadth);
for key in &keys {
if cut && *key == "..." {
prop_assert_eq!(&out[*key], MAX_PROPERTIES);
} else {
check_normalized(&map[*key], &out[*key], depth - 1, breadth, max_string)?;
}
}
if cut && !keys.contains(&"...") {
prop_assert_eq!(&out["..."], MAX_PROPERTIES);
keys.push("...");
}
let out_keys: Vec<&str> = out.keys().map(String::as_str).collect();
prop_assert_eq!(out_keys, keys);
}
_ => prop_assert_eq!(output, input),
}
Ok(())
}
proptest! {
#![proptest_config(config_capped(256))]
#[test]
fn normalize_cuts_each_level_at_the_limits(
input in json_value(6, 6),
depth in 0usize..6,
breadth in 0usize..6,
max_string in 0usize..20,
) {
let output = normalize(&input, depth, breadth, max_string);
check_normalized(&input, &output, depth, breadth, max_string)?;
}
#[test]
fn values_within_the_default_limits_come_back_unchanged(input in json_value(9, 6)) {
let output = normalize(&input, MAX_DEPTH, MAX_BREADTH, MAX_STRING_LENGTH);
check_normalized(&input, &output, MAX_DEPTH, MAX_BREADTH, MAX_STRING_LENGTH)?;
prop_assert_eq!(&output, &input);
}
}