use std::panic::catch_unwind;
use hf_chat_template::{ChatTemplate, Error, Message, RenderInput};
use minijinja::{context, Value};
#[test]
fn malformed_templates_compile_without_panicking() {
let nasty = [
"{% for x in %}", "{{ }}", "{%", "%}", "{{", "{{ unclosed", "{% if %}{% endif %}", "{% endif %}", "{{ a.b.c.d.e.f }}", "{% generation %}", "{% endgeneration %}", "héllo {% generation %}wörld", "café{%- generation -%}thé", "🦀{%generation%}🦀{%endgeneration%}🦀", "{%生成%}", "{{ '\\u{0}\\u{1}' }}", ];
for src in nasty {
let _ = ChatTemplate::from_str(src);
}
}
#[test]
fn generation_rewrite_handles_multibyte_without_panicking() {
let cases = [
"Ω{% generation %}Ω{% endgeneration %}Ω",
"{%- generation +%}日本語{%+ endgeneration -%}",
"a{%generation%}b{%endgeneration%}c",
"пример {% generation %}\n{{ x }}\n{% endgeneration %} конец",
];
let result = catch_unwind(|| {
for src in cases {
if let Ok(tmpl) = ChatTemplate::from_str(src) {
let _ = tmpl.render_value(context! { x => "ок" });
}
}
});
assert!(
result.is_ok(),
"generation-tag rewrite panicked on multibyte input"
);
}
#[test]
fn hostile_render_contexts_do_not_panic() {
let t = ChatTemplate::from_str("{{ messages[0]['content'] }}").unwrap();
let _ = t.render_value(context! { messages => Vec::<Value>::new() });
let t = ChatTemplate::from_str("{{ totally_undefined.attr }}{{ bos_token }}").unwrap();
let _ = t.render_value(context! {});
let t = ChatTemplate::from_str("{% for c in content %}{{ c }}{% endfor %}").unwrap();
let _ = t.render_value(context! { content => 42 });
let t = ChatTemplate::from_str("{{ messages[0].role }}").unwrap();
let _ = t.render(&RenderInput::default());
}
#[test]
fn user_content_is_inert_text() {
let t = ChatTemplate::from_str("{{ messages[0].content }}").unwrap();
let input = RenderInput {
messages: vec![Message::user(
"{% raise_exception('x') %}\u{0}\u{1}{{ leak }}",
)],
..Default::default()
};
let out = t.render(&input).expect("inert content renders");
assert_eq!(out, "{% raise_exception('x') %}\u{0}\u{1}{{ leak }}");
}
#[test]
fn deep_nesting_is_handled_gracefully() {
let depth = 300;
let src = format!(
"{}{}{}",
"{% if true %}".repeat(depth),
"x",
"{% endif %}".repeat(depth),
);
let result = catch_unwind(|| {
if let Ok(t) = ChatTemplate::from_str(&src) {
let _ = t.render_value(context! {});
}
});
assert!(result.is_ok(), "deep nesting panicked instead of erroring");
}
#[test]
fn real_syntax_error_still_errors() {
let err = ChatTemplate::from_str("{% for %}").unwrap_err();
assert!(matches!(err, Error::Compile(_)), "got {err:?}");
}