"""Tests for Telegram inline keyboard clarify buttons. Mirrors test_telegram_approval_buttons.py for the new ``send_clarify`` and ``cl:`` callback dispatch added in feat/clarify-gateway-buttons. """ import os import sys from pathlib import Path from unittest.mock import AsyncMock, MagicMock, patch import pytest # --------------------------------------------------------------------------- # Ensure the repo root is importable # --------------------------------------------------------------------------- _repo = str(Path(__file__).resolve().parents[2]) if _repo not in sys.path: sys.path.insert(0, _repo) # --------------------------------------------------------------------------- # Minimal Telegram mock so TelegramAdapter can be imported (mirrors # test_telegram_approval_buttons.py) # --------------------------------------------------------------------------- from plugins.platforms.telegram.adapter import TelegramAdapter from gateway.config import PlatformConfig def _make_adapter(extra=None): config = PlatformConfig(enabled=True, token="test-token", extra=extra or {}) adapter = TelegramAdapter(config) adapter._bot = AsyncMock() adapter._app = MagicMock() return adapter def _clear_clarify_state(): from tools import clarify_gateway as cm with cm._lock: cm._entries.clear() cm._session_index.clear() cm._notify_cbs.clear() # =========================================================================== # send_clarify — render # =========================================================================== class TestTelegramSendClarify: """Verify the rendered prompt has buttons or none, and stores state.""" def setup_method(self): _clear_clarify_state() @pytest.mark.asyncio async def test_multi_choice_renders_buttons_and_other(self): adapter = _make_adapter() mock_msg = MagicMock() mock_msg.message_id = 100 adapter._bot.send_message = AsyncMock(return_value=mock_msg) result = await adapter.send_clarify( chat_id="12345", question="Which option?", choices=["alpha", "beta", "gamma"], clarify_id="cid1", session_key="sk1", ) assert result.success is True assert result.message_id == "100" kwargs = adapter._bot.send_message.call_args[1] assert kwargs["chat_id"] == 12345 assert "Which option?" in kwargs["text"] # Full option text rendered in the message body (not just buttons) assert "1. alpha" in kwargs["text"] assert "2. beta" in kwargs["text"] assert "3. gamma" in kwargs["text"] # InlineKeyboardMarkup with N+1 buttons (3 choices + Other) markup = kwargs["reply_markup"] assert markup is not None # Mocked InlineKeyboardMarkup — just verify it was constructed # with rows. We check state instead of poking the mock structure. assert "cid1" in adapter._clarify_state assert adapter._clarify_state["cid1"] == "sk1" # The button label should be short ("1"), not the long choice # (we can't inspect mock button labels directly, but the send # succeeded — old truncation code could raise on edge cases) @pytest.mark.asyncio async def test_html_escapes_question(self): adapter = _make_adapter() mock_msg = MagicMock() mock_msg.message_id = 103 adapter._bot.send_message = AsyncMock(return_value=mock_msg) await adapter.send_clarify( chat_id="12345", question="", choices=["x"], clarify_id="cid5", session_key="sk5", ) kwargs = adapter._bot.send_message.call_args[1] # Must NOT contain raw