use crate::brain::provider::json_repair::*;
#[test]
fn passes_through_valid_json() {
let v = parse_or_repair(r#"{"a":1,"b":"x"}"#);
assert_eq!(v["a"], 1);
assert_eq!(v["b"], "x");
}
#[test]
fn empty_returns_object() {
let v = parse_or_repair("");
assert!(v.is_object());
}
#[test]
fn closes_open_string() {
let v = parse_or_repair(r#"{"command":"git status"#);
assert_eq!(v["command"], "git status");
}
#[test]
fn closes_missing_brace() {
let v = parse_or_repair(r#"{"a":1,"b":2"#);
assert_eq!(v["a"], 1);
assert_eq!(v["b"], 2);
}
#[test]
fn drops_trailing_key_without_value() {
let v = parse_or_repair(r#"{"a":1,"b":"#);
assert_eq!(v["a"], 1);
assert!(v.get("b").is_none());
}
#[test]
fn closes_nested_array() {
let v = parse_or_repair(r#"{"items":[1,2,3"#);
assert_eq!(v["items"][0], 1);
assert_eq!(v["items"][2], 3);
}
#[test]
fn closes_string_inside_array() {
let v = parse_or_repair(r#"{"items":["a","b"#);
assert_eq!(v["items"][0], "a");
assert_eq!(v["items"][1], "b");
}
#[test]
fn unrecoverable_returns_partial_envelope() {
let v = parse_or_repair(r#"this is not json"#);
assert!(v["_repair_failed"].as_bool().unwrap_or(false));
assert_eq!(v["_partial"], "this is not json");
}
#[test]
fn handles_escaped_quote_in_string() {
let v = parse_or_repair(r#"{"msg":"he said \"hi"#);
assert_eq!(v["msg"], "he said \"hi");
}
#[test]
fn strips_trailing_comma() {
let v = parse_or_repair(r#"{"a":1,"#);
assert_eq!(v["a"], 1);
}
use crate::brain::provider::types::ContentBlock;
#[test]
fn each_call_shape_is_detected_as_a_leak() {
for text in [
r#"{"name": "bash", "arguments": {"command": "ls"}}"#,
r#"{"command": "ls -la"}"#,
r#"{"function": {"name": "bash", "arguments": "{}"}}"#,
] {
assert!(
!find_call_shaped_json_spans(text).is_empty(),
"should be a leak: {text}"
);
}
}
#[test]
fn fenced_json_is_never_a_leak() {
let fence = "```";
let text = format!(
"Here is how a call looks:\n{fence}json\n{{\"name\": \"bash\", \"arguments\": {{\"command\": \"ls\"}}}}\n{fence}\nThat is the shape."
);
assert!(find_call_shaped_json_spans(&text).is_empty());
let (cleaned, stripped) = strip_call_shaped_json(&text);
assert!(!stripped, "fenced example must survive untouched");
assert_eq!(cleaned, text);
}
#[test]
fn prose_about_tool_calls_is_not_a_leak() {
for text in [
"The function takes arguments and returns a result.",
"Set arguments on the function field, not as text.",
"I would call {name: bash, arguments: broken",
] {
assert!(
find_call_shaped_json_spans(text).is_empty(),
"prose must not trip the detector: {text}"
);
}
}
#[test]
fn non_call_shaped_json_is_left_alone() {
for text in [
r#"{"result": 42, "ok": true}"#,
r#"{"name": "opencrabs"}"#,
r#"{"arguments": ["a", "b"]}"#,
] {
assert!(
find_call_shaped_json_spans(text).is_empty(),
"not call-shaped: {text}"
);
}
}
#[test]
fn the_bare_command_shape_matches_on_the_key_alone() {
assert!(!find_call_shaped_json_spans(r#"{"command": "anything"}"#).is_empty());
let fence = "```";
let fenced = format!("{fence}\n{{\"command\": \"anything\"}}\n{fence}");
assert!(
find_call_shaped_json_spans(&fenced).is_empty(),
"fencing is the escape hatch for this shape"
);
}
#[test]
fn stripping_removes_the_leak_and_keeps_the_prose() {
let text = "Let me check.\n{\"name\": \"bash\", \"arguments\": {\"command\": \"ls\"}}\nDone.";
let (cleaned, stripped) = strip_call_shaped_json(text);
assert!(stripped);
assert!(!cleaned.contains("arguments"), "leak survived: {cleaned}");
assert!(cleaned.contains("Let me check."));
assert!(cleaned.contains("Done."));
}
#[test]
fn stripping_clean_text_reports_no_leak() {
let (cleaned, stripped) = strip_call_shaped_json("just an answer");
assert!(!stripped);
assert_eq!(cleaned, "just an answer");
}
#[test]
fn structured_tool_use_blocks_suppress_the_leak_verdict() {
let blocks = vec![
ContentBlock::Text {
text: r#"{"command": "ls"}"#.to_string(),
},
ContentBlock::ToolUse {
id: "1".to_string(),
name: "bash".to_string(),
input: serde_json::json!({"command": "ls"}),
},
];
assert!(!content_has_unrecovered_tool_text(&blocks));
}
#[test]
fn text_only_content_with_call_shaped_json_is_a_leak() {
let blocks = vec![ContentBlock::Text {
text: r#"{"name": "bash", "arguments": {"command": "ls"}}"#.to_string(),
}];
assert!(content_has_unrecovered_tool_text(&blocks));
}
#[test]
fn ordinary_answers_are_not_leaks() {
let blocks = vec![ContentBlock::Text {
text: "The build passed.".to_string(),
}];
assert!(!content_has_unrecovered_tool_text(&blocks));
}