"""Tests for services.llm_text — the shared free-text cleanup for model prose."""
from __future__ import annotations
from services.llm_text import clean_llm_text, sanitize_tree, to_plain_text
class TestCleanLlmText:
def test_strips_think_blocks_and_fences(self):
raw = "planning\n```markdown\n**Hi** there\n```\n(Word count: 12)"
assert clean_llm_text(raw) == "**Hi** there"
def test_unclosed_think_prefix_dropped(self):
raw = "let me reason about this\n\nThe slope is 2."
assert clean_llm_text(raw) == "The slope is 2."
def test_literal_newlines_and_tex_delimiters(self):
raw = "Step 1\\nStep 2 uses \\(x^2\\) and \\[a = b\\]"
assert clean_llm_text(raw) == "Step 1\nStep 2 uses $x^2$ and $$a = b$$"
def test_tex_commands_starting_with_n_t_r_survive(self):
raw = r"$G = r \times d$, $\text{Profit} = S - C$, $a \neq b$, $\nabla f$, $\theta \to \rho$"
assert clean_llm_text(raw) == raw
def test_literal_escapes_before_prose_still_decode(self):
assert clean_llm_text(r"Step 1\nThe next\n\nnext\t2") == "Step 1\nThe next\n\nnext 2"
def test_double_escaped_tex_unescaped(self):
assert clean_llm_text("Use $\\\\frac{1}{2}$") == "Use $\\frac{1}{2}$"
def test_inner_code_block_preserved(self):
raw = "Run:\n```python\nprint('\\n')\n```\nDone"
assert clean_llm_text(raw) == raw
def test_wrapping_quotes_and_label_removed(self):
assert clean_llm_text('"Great job today!"') == "Great job today!"
assert clean_llm_text("Message: Keep going") == "Keep going"
def test_none_and_non_string(self):
assert clean_llm_text(None) == ""
assert clean_llm_text(42) == "42"
class TestToPlainText:
def test_markdown_removed_math_kept(self):
raw = "## **Key**: the *slope* is $m = \\frac{2}{3}$ — see [link](http://x)"
assert to_plain_text(raw) == "Key: the slope is $m = \\frac{2}{3}$ — see link"
def test_option_label_stripped(self):
assert to_plain_text("A) **500**", strip_option_label=True) == "500"
assert to_plain_text("(b) x = 2", strip_option_label=True) == "x = 2"
assert to_plain_text("Always", strip_option_label=True) == "Always"
def test_subscripts_untouched(self):
assert to_plain_text("x_1 and y_2 are *points*") == "x_1 and y_2 are points"
def test_single_line(self):
assert to_plain_text("- one\n- two", single_line=True) == "one two"
class TestSanitizeTree:
def test_plain_keys_vs_markdown_keys(self):
payload = {
"question": "What is **x**?",
"options": ["**A**. 2", "B. *3*"],
"explanation": "Because \\(x=2\\)\\n- step",
"hints": ["use **PEMDAS**"],
"id": "**q1**",
"nested": {"title": "## T", "content": "xbody"},
}
out = sanitize_tree(payload)
assert out["question"] == "What is **x**?"
assert out["options"] == ["A. 2", "B. 3"]
assert out["explanation"] == "Because $x=2$\n- step"
assert out["hints"] == ["use **PEMDAS**"]
assert out["id"] == "**q1**"
assert out["nested"] == {"title": "T", "content": "body"}
def test_non_string_leaves_untouched(self):
assert sanitize_tree({"points": 3, "ok": True, "n": None}) == {"points": 3, "ok": True, "n": None}