#![allow(clippy::unwrap_used, clippy::panic)]
use super::*;
#[test]
fn a_flat_document_is_charged_its_keys_values_and_punctuation() {
let v = serde_json::json!({"id": "1", "name": "Alice"});
assert_eq!(json_bytes(&v), 26);
}
#[test]
fn an_empty_container_is_charged_only_its_brackets() {
assert_eq!(json_bytes(&serde_json::json!({})), 2);
assert_eq!(json_bytes(&serde_json::json!([])), 2);
}
#[test]
fn nesting_is_charged_all_the_way_down() {
let flat = serde_json::json!({"a": "x"});
let nested = serde_json::json!({"a": {"b": {"c": "x"}}});
assert!(
json_bytes(&nested) > json_bytes(&flat),
"a nested document must cost more than a flat one"
);
}
#[test]
fn a_column_row_is_charged_its_payload() {
assert_eq!(column_bytes(&ColumnValue::Text("Alice".into())), 5);
assert_eq!(column_bytes(&ColumnValue::Null), 0);
assert_eq!(column_bytes(&ColumnValue::Boolean(true)), 1);
}
#[test]
fn no_declared_ceiling_yields_no_budget() {
assert!(ResponseBudget::new(None).is_none());
}
#[test]
fn charges_accumulate_and_the_error_carries_the_running_total() {
let budget = ResponseBudget::new(Some(10)).expect("a ceiling was declared");
budget.charge(4).expect("under");
budget.charge(4).expect("still under");
assert_eq!(budget.used(), 8);
let err = budget.charge(4).unwrap_err();
match err {
FraiseQLError::ResponseTooLarge { bytes, limit } => {
assert_eq!(bytes, 12, "4 + 4 + 4, not the 4 that crossed it");
assert_eq!(limit, 10);
},
other => panic!("expected ResponseTooLarge, got {other:?}"),
}
}
#[test]
fn two_shared_handles_charge_one_running_total() {
let budget = ResponseBudget::new(Some(10)).expect("a ceiling was declared");
let first: &ResponseBudget = &budget;
let second: &ResponseBudget = &budget;
first.charge(6).expect("under on its own");
let err = second.charge(6).unwrap_err();
match err {
FraiseQLError::ResponseTooLarge { bytes, limit } => {
assert_eq!(bytes, 12, "both handles charged the same total");
assert_eq!(limit, 10);
},
other => panic!("expected ResponseTooLarge, got {other:?}"),
}
assert_eq!(budget.used(), 12, "and the total is visible through the owner");
}
#[test]
fn exactly_at_the_ceiling_is_allowed() {
let budget = ResponseBudget::new(Some(10)).expect("a ceiling was declared");
budget.charge(10).expect("10 is not over 10");
}
#[test]
fn an_overflowing_charge_saturates_rather_than_wrapping() {
let budget = ResponseBudget::new(Some(u64::MAX - 1)).expect("declared");
budget.charge(u64::MAX).expect_err("saturates to MAX, which is over MAX - 1");
}
#[test]
fn a_row_read_as_text_is_charged_as_the_value_it_parses_to() {
for text in [
r#"{"id" : "aaaa", "name" : "Alice", "n" : 12, "ok" : true, "gone" : null}"#,
r#"{"profile" : {"tier" : "gold", "address" : {"city" : "Paris"}}}"#,
r#"{"tags": ["a", "b", ""], "nested": [[1, 2.5], [], [{"k": "naïve 漢字"}]]}"#,
r#"{"quote" : "a \"b\" \\ c\n", "empty" : {}}"#,
"[]",
"7",
] {
let mut rows = fraiseql_db::RawRows::new();
rows.push(text.as_bytes());
let value: serde_json::Value = serde_json::from_str(text).unwrap();
assert_eq!(tally_bytes(&rows.rows()[0].tally), json_bytes(&value), "{text}");
}
}
#[test]
fn rows_read_as_text_are_refused_where_the_parsed_rows_are() {
let texts = [
r#"{"a" : "xxxxxxxxxx"}"#,
r#"{"b" : "yyyyyyyyyy"}"#,
r#"{"c" : 1}"#,
];
let mut raw = fraiseql_db::RawRows::new();
let mut parsed = Vec::new();
for text in texts {
raw.push(text.as_bytes());
parsed.push(JsonbValue::new(serde_json::from_str(text).unwrap()));
}
let limit = json_bytes(parsed[0].as_value()) + json_bytes(parsed[1].as_value());
let refused = |result: Result<()>| match result {
Err(FraiseQLError::ResponseTooLarge { bytes, limit }) => Some((bytes, limit)),
_ => None,
};
let by_text = refused(ResponseBudget::new(Some(limit)).unwrap().charge_raw_rows(&raw));
let by_value = refused(ResponseBudget::new(Some(limit)).unwrap().charge_jsonb_rows(&parsed));
assert!(by_value.is_some());
assert_eq!(by_text, by_value);
}