ouroboros/tests/test_telegram_markdown.py
Ouroboros fedd7d9fe6 release: ship native Telegram companion in v6.85.0
Seed the combined Telegram bridge and Mini App as a native skill, add symmetric skill conflicts and managed repair routing, preserve the standalone PoC process boundary, and wire native delivery through Colab and packaged bootstrap.
2026-07-30 18:14:07 +03:00

131 lines
5.2 KiB
Python

"""Regression tests for markdown_to_telegram_html.
Bug history (v2.1.1): the italic-underscore regex `_(.*?)_` matched across
identifiers like `chat_id` inside `**bold**` spans, producing interleaved
HTML tags (`<b>...<i>...</b>...</i>...`) that Telegram's parser rejects
with HTTP 400 Bad Request. The fix requires non-word context on both
sides of italic/bold underscores so identifiers do not trigger them.
These tests check both behavioural cases (the converter still emits
italic/bold) AND structural well-formedness (XML-style tag nesting),
because the original bug had balanced tag *counts* but broken *order*.
"""
from __future__ import annotations
import importlib.util
from pathlib import Path
from xml.etree import ElementTree as ET
def _load_converter():
root = Path(__file__).resolve().parents[1] / "skills" / "telegram"
spec = importlib.util.spec_from_file_location(
"telegram_bridge_test.telegram_api",
root / "lib" / "telegram_api.py",
)
module = importlib.util.module_from_spec(spec)
assert spec.loader is not None
spec.loader.exec_module(module)
return module.markdown_to_telegram_html
def _assert_well_formed(html: str) -> None:
"""Parse Telegram HTML output as XML to confirm tag nesting is valid."""
wrapped = f"<root>{html}</root>"
try:
ET.fromstring(wrapped)
except ET.ParseError as exc:
raise AssertionError(f"Malformed nested HTML: {exc}\n{html}") from exc
def test_identifier_with_underscore_inside_bold_does_not_break_nesting():
convert = _load_converter()
# Real-world reproduction from chat: identifiers with underscores inside
# bold markdown. Two `chat_id` occurrences (one in bold, one outside)
# used to confuse the italic-underscore regex into wrapping across the
# bold close tag.
src = "**авто-захват chat_id**. Запоминаем твой chat_id в state."
html = convert(src)
_assert_well_formed(html)
# chat_id should NOT be wrapped in italic — it's an identifier, not markdown.
assert "<i>" not in html, f"Spurious italic on identifier: {html}"
assert "<b>авто-захват chat_id</b>" in html
def test_multiple_identifiers_around_bold_stay_literal():
convert = _load_converter()
src = "**OUROBOROS_MODEL** меняется в state_dir/settings.json — owner_chat.json не трогаем."
html = convert(src)
_assert_well_formed(html)
assert "<i>" not in html
assert "<b>OUROBOROS_MODEL</b>" in html
def test_legitimate_underscore_italic_still_works():
convert = _load_converter()
# Underscore italic SHOULD still match when surrounded by whitespace/punctuation.
src = "This is _italic_ text and **bold** too."
html = convert(src)
_assert_well_formed(html)
assert "<i>italic</i>" in html
assert "<b>bold</b>" in html
def test_asterisk_italic_unchanged():
convert = _load_converter()
src = "*italic-star* and **bold** with chat_id inside."
html = convert(src)
_assert_well_formed(html)
assert "<i>italic-star</i>" in html
assert "chat_id" in html
assert "<i>id</i>" not in html # no spurious italic from `_id`
def test_double_underscore_bold_with_identifier_safe():
convert = _load_converter()
src = "__BOLD__ then chat_id afterwards."
html = convert(src)
_assert_well_formed(html)
assert "<b>BOLD</b>" in html
# `_id` at end-of-word should NOT become italic.
assert "<i>" not in html
def test_inline_code_with_underscores_untouched():
convert = _load_converter()
src = "Use `OUROBOROS_MODEL` and `state_dir/owner_chat.json` here."
html = convert(src)
_assert_well_formed(html)
# Underscores inside code spans must be preserved verbatim.
assert "<code>OUROBOROS_MODEL</code>" in html
assert "<code>state_dir/owner_chat.json</code>" in html
assert "<i>" not in html
def test_bold_with_inline_code_and_identifier():
convert = _load_converter()
# The original failure case shape: bold span containing both inline code
# (with underscores) and a plain identifier with an underscore.
src = "**Запоминаем `chat_id` в state_dir** — потом подтверждаем."
html = convert(src)
_assert_well_formed(html)
assert "<b>" in html and "</b>" in html
# Order of opening/closing tags must be valid (the ElementTree parse already verifies that).
def test_tag_balance_and_nesting_on_complex_real_message():
convert = _load_converter()
# Long-form sample resembling the actual reply that triggered the 400.
src = (
"**Куда хочу прийти**\n\n"
"Развести на две оси:\n"
"1. **TELEGRAM_MIRROR_MODE** (select): `tg_replies_only` / `full_mirror`.\n"
"2. **TELEGRAM_CHAT_ID** — остаётся, но превращается в просто адрес.\n\n"
"И ещё **авто-захват chat_id**. Первый раз, когда ты пишешь боту, "
"плагин запоминает твой chat_id в `owner_chat.json` и предлагает в Settings."
)
html = convert(src)
_assert_well_formed(html)
# No italic tags should have been emitted from identifier underscores.
assert "<i>" not in html