use super::flush_content_sanitizer;
use crate::tool_parser::{LeakMarkers, Qwen3CoderParser, ToolCallParser};
fn sanitize_content_chunk(
text: &str,
tag_scan_buf: &mut String,
suppressing_param_leak: &mut bool,
markers: &LeakMarkers,
) -> String {
let mut inside_envelope = false;
super::sanitize_content_chunk(
text,
tag_scan_buf,
suppressing_param_leak,
&mut inside_envelope,
markers,
)
}
#[test]
fn sanitizer_envelope_open_disables_orphan_suppression() {
let markers = crate::tool_parser::MinimaxXmlParser.leak_markers();
for envelope_open in &["<minimax:tool_call>", "<minimax:_call>", "<tool_call>"] {
let envelope_close = match *envelope_open {
"<minimax:tool_call>" => "</minimax:tool_call>",
"<minimax:_call>" => "</minimax:_call>",
_ => "</tool_call>",
};
let body = format!(
"{envelope_open}\n<invoke name=\"bash\">\n<parameter name=\"command\">uname -r</parameter>\n</invoke>\n{envelope_close}"
);
let mut buf = String::new();
let mut suppress = false;
let mut env = false;
let out = super::sanitize_content_chunk(&body, &mut buf, &mut suppress, &mut env, &markers);
assert!(
out.contains("<invoke name=\"bash\">"),
"envelope {envelope_open}: <invoke> must survive: out={out:?}"
);
assert!(
out.contains("uname -r"),
"envelope {envelope_open}: command must survive: out={out:?}"
);
assert!(
out.contains("</invoke>"),
"envelope {envelope_open}: </invoke> must survive: out={out:?}"
);
assert!(
out.contains(envelope_open),
"envelope_open bytes must pass through: out={out:?}"
);
assert!(
out.contains(envelope_close),
"envelope_close bytes must pass through: out={out:?}"
);
assert!(!suppress, "envelope path must not enter orphan suppression");
assert!(!env, "envelope state cleared after close");
}
}
#[test]
fn sanitizer_orphan_invoke_outside_envelope_still_suppressed() {
let markers = crate::tool_parser::MinimaxXmlParser.leak_markers();
let body = "prefix<invoke name=\"bash\">cmd</invoke>tail";
let mut buf = String::new();
let mut suppress = false;
let mut env = false;
let out = super::sanitize_content_chunk(body, &mut buf, &mut suppress, &mut env, &markers);
assert!(
out.starts_with("prefix"),
"non-orphan prefix emits: {out:?}"
);
assert!(
!out.contains("<invoke"),
"stray <invoke> must still be suppressed: {out:?}"
);
assert!(
!out.contains("cmd"),
"suppressed body bytes must not leak: {out:?}"
);
}
#[test]
fn sanitizer_noop_for_empty_markers() {
let mut buf = String::new();
let mut suppress = false;
let out = sanitize_content_chunk(
"<parameter=foo>value</parameter>",
&mut buf,
&mut suppress,
&LeakMarkers::EMPTY,
);
assert_eq!(out, "<parameter=foo>value</parameter>");
assert!(buf.is_empty(), "no markers → no tail buffering");
assert!(!suppress);
}
#[test]
fn sanitizer_suppresses_for_qwen3_markers() {
let markers = Qwen3CoderParser.leak_markers();
let mut buf = String::new();
let mut suppress = false;
let out = sanitize_content_chunk(
"prefix<parameter=filePath>/tmp/x.txt</parameter>suffix</function>tail",
&mut buf,
&mut suppress,
&markers,
);
assert!(out.starts_with("prefix"), "got: {out:?}");
assert!(
!out.contains("<parameter="),
"orphan open must not leak: {out:?}"
);
assert!(
!out.contains("/tmp/x.txt"),
"suppressed body must not leak: {out:?}"
);
assert!(
!out.contains("</function>"),
"stray close must be stripped: {out:?}"
);
}
#[test]
fn sanitizer_fuses_tag_across_chunks() {
let markers = Qwen3CoderParser.leak_markers();
let mut buf = String::new();
let mut suppress = false;
let out1 = sanitize_content_chunk("abc<param", &mut buf, &mut suppress, &markers);
assert!(!suppress, "partial tag must not trigger suppression");
assert_eq!(out1, "abc", "prose before a partial tag emits immediately");
assert_eq!(
buf, "<param",
"the tag prefix alone stays in the tail buffer"
);
let out2 = sanitize_content_chunk(
"eter=x>body</parameter>tail",
&mut buf,
&mut suppress,
&markers,
);
assert_eq!(out2, "tail", "only the post-close prose emits: {out2:?}");
assert!(
!out2.contains("body"),
"suppressed body must not leak: {out2:?}"
);
assert!(
!out2.contains("<parameter="),
"orphan open must not leak: {out2:?}"
);
assert!(!suppress, "close tag exits suppression state");
}
#[test]
fn flush_empty_markers_emits_tail_verbatim() {
let mut buf = String::from("anything");
let mut suppress = false;
let out = flush_content_sanitizer(&mut buf, &mut suppress, &LeakMarkers::EMPTY);
assert_eq!(out, "anything");
assert!(buf.is_empty());
}
#[test]
fn flush_drops_partial_tag_prefix() {
let markers = Qwen3CoderParser.leak_markers();
let mut buf = String::from("<par");
let mut suppress = false;
let out = flush_content_sanitizer(&mut buf, &mut suppress, &markers);
assert_eq!(out, "");
}
#[test]
fn flush_envelope_markers_skips_scrub() {
let markers = crate::tool_parser::MinimaxXmlParser.leak_markers();
let tail = "</invoke>\n</minimax:tool_call>";
let mut buf = String::from(tail);
let mut suppress = false;
let out = flush_content_sanitizer(&mut buf, &mut suppress, &markers);
assert_eq!(out, tail, "envelope content must survive flush verbatim");
}
#[test]
fn flush_before_tool_boundary_recovers_from_stuck_suppression() {
let markers = Qwen3CoderParser.leak_markers();
let mut buf = String::new();
let mut suppress = false;
let prose = sanitize_content_chunk(
"Let me write it: <parameter=content>foo",
&mut buf,
&mut suppress,
&markers,
);
assert_eq!(prose, "Let me write it: ", "prefix emits: {prose:?}");
assert!(suppress, "orphan `<parameter=` enters suppression");
let pre_tool = flush_content_sanitizer(&mut buf, &mut suppress, &markers);
assert_eq!(pre_tool, "", "suppressed tail is correctly dropped");
assert!(!suppress, "flush clears the suppression flag");
assert!(buf.is_empty(), "flush clears the tail buffer");
let post_tool = sanitize_content_chunk(
"Done — here is the result.",
&mut buf,
&mut suppress,
&markers,
);
assert!(
post_tool.starts_with("Done"),
"post-tool content must reach the client: {post_tool:?}"
);
assert!(!suppress, "no new orphan, must stay out of suppression");
}
#[test]
fn marker_incompatible_first_chunk_emits_immediately() {
let markers = Qwen3CoderParser.leak_markers();
let mut buf = String::new();
let mut sup = false;
for text in ["An", "The", "A", "Certainly, here is"] {
buf.clear();
let out = sanitize_content_chunk(text, &mut buf, &mut sup, &markers);
assert_eq!(out, *text, "marker-incompatible chunk was withheld");
assert!(
buf.is_empty(),
"nothing marker-compatible to hold for {text:?}"
);
}
}
#[test]
fn marker_prefix_suffix_is_still_held_for_fusion() {
let markers = Qwen3CoderParser.leak_markers();
let mut buf = String::new();
let mut sup = false;
let out = sanitize_content_chunk("done.<tool_c", &mut buf, &mut sup, &markers);
assert_eq!(out, "done.");
assert_eq!(buf, "<tool_c");
let out2 = sanitize_content_chunk("all>leaked</tool_call>after", &mut buf, &mut sup, &markers);
assert!(
!out2.contains("leaked") && out2.ends_with("after"),
"straddled marker fusion regressed: {out2:?}"
);
}