use crate::llm::json_parsing::*;
use serde_json::json;
fn unwrap(extracted: Extracted) -> Value {
match extracted {
Extracted::Complete(v) | Extracted::Truncated(v) => v,
}
}
#[test]
fn bare_json_object_parses_to_complete() {
let extracted = extract_json(r#"{"findings":[]}"#).expect("parses");
assert!(matches!(extracted, Extracted::Complete(_)));
assert_eq!(unwrap(extracted), json!({"findings": []}));
}
#[test]
fn json_fenced_block_parses_to_complete() {
let input = "```json\n{\"findings\":[]}\n```";
let extracted = extract_json(input).expect("parses");
assert!(matches!(extracted, Extracted::Complete(_)));
assert_eq!(unwrap(extracted), json!({"findings": []}));
}
#[test]
fn unlabelled_fenced_block_parses_to_complete() {
let input = "```\n{\"findings\":[]}\n```";
let extracted = extract_json(input).expect("parses");
assert!(matches!(extracted, Extracted::Complete(_)));
assert_eq!(unwrap(extracted), json!({"findings": []}));
}
#[test]
fn prose_around_fenced_block_still_parses() {
let input = "Sure, here you go:\n```json\n{\"a\":1}\n```\nLet me know if that helps.";
let extracted = extract_json(input).expect("parses");
assert!(matches!(extracted, Extracted::Complete(_)));
assert_eq!(unwrap(extracted), json!({"a": 1}));
}
#[test]
fn trailing_comma_is_repaired_and_returns_complete() {
let input = r#"{"findings": [], "ok": true,}"#;
let extracted = extract_json(input).expect("parses after repair");
assert!(
matches!(extracted, Extracted::Complete(_)),
"trailing-comma repair is strategy 3, which returns Complete"
);
assert_eq!(unwrap(extracted), json!({"findings": [], "ok": true}));
}
#[test]
fn unbalanced_open_brace_closes_and_returns_truncated() {
let input = r#"{"a":"b""#;
let extracted = extract_json(input).expect("parses after balance");
assert!(
matches!(extracted, Extracted::Truncated(_)),
"missing closing brace must be reported as Truncated, not Complete"
);
assert_eq!(unwrap(extracted), json!({"a": "b"}));
}
#[test]
fn unbalanced_open_bracket_closes_and_returns_truncated() {
let input = "[1, 2, 3";
let extracted = extract_json(input).expect("parses after balance");
assert!(
matches!(extracted, Extracted::Truncated(_)),
"missing closing bracket must be reported as Truncated, not Complete"
);
assert_eq!(unwrap(extracted), json!([1, 2, 3]));
}
#[test]
fn irrecoverable_garbage_returns_none() {
let input = "this is not JSON at all, it's prose.";
assert!(extract_json(input).is_none());
}
#[test]
fn empty_string_returns_none() {
assert!(extract_json("").is_none());
}
#[test]
fn apostrophe_inside_string_survives_intact() {
let input = r#"{"message":"don't worry","ok":true}"#;
let extracted = extract_json(input).expect("parses directly via strategy 2");
assert!(matches!(extracted, Extracted::Complete(_)));
assert_eq!(
unwrap(extracted),
json!({"message": "don't worry", "ok": true})
);
}
#[test]
fn complete_result_is_never_reported_as_truncated() {
let input = r#"{"a":{"b":{"c":"d"}},"e":[1,2,3]}"#;
let extracted = extract_json(input).expect("parses");
assert!(
matches!(extracted, Extracted::Complete(_)),
"well-formed nested object must be Complete, not Truncated"
);
assert_eq!(
unwrap(extracted),
json!({"a": {"b": {"c": "d"}}, "e": [1, 2, 3]})
);
}
#[test]
fn balance_unclosed_ignores_braces_inside_strings() {
let input = r#"{"text":"oops {","a":1"#;
let balanced = balance_unclosed(input).expect("needs balancing");
assert_eq!(balanced, r#"{"text":"oops {","a":1}"#);
assert!(serde_json::from_str::<Value>(&balanced).is_ok());
}
#[test]
fn trailing_comma_repair_leaves_commas_inside_strings_intact() {
let input = r#"{"a":"x, y","b":1,}"#;
let extracted = extract_json(input).expect("parses after repair");
assert!(matches!(extracted, Extracted::Complete(_)));
assert_eq!(unwrap(extracted), json!({"a": "x, y", "b": 1}));
}