diff --git a/tests/channels/test_telegram_channel.py b/tests/channels/test_telegram_channel.py index 4a69d31a..5480b878 100644 --- a/tests/channels/test_telegram_channel.py +++ b/tests/channels/test_telegram_channel.py @@ -1591,3 +1591,29 @@ async def test_send_delta_mid_stream_strips_markdown() -> None: assert "**" not in edited_text assert "Title" in edited_text assert "1. step" in edited_text + + +def test_build_keyboard_respects_inline_keyboards_flag() -> None: + """``_build_keyboard`` returns ``None`` whenever the feature flag is off, + regardless of whether buttons are provided; returns a proper Markup only + when the flag is explicitly enabled. Pins the kill-switch so accidentally + flipping the default doesn't silently expose callback handlers.""" + from telegram import InlineKeyboardMarkup + + off = TelegramChannel( + TelegramConfig(enabled=True, token="123:abc", inline_keyboards=False), + MessageBus(), + ) + assert off._build_keyboard([["A", "B"]]) is None + + on = TelegramChannel( + TelegramConfig(enabled=True, token="123:abc", inline_keyboards=True), + MessageBus(), + ) + assert on._build_keyboard([]) is None # empty still no-op + markup = on._build_keyboard([["Yes", "No"], ["Cancel"]]) + assert isinstance(markup, InlineKeyboardMarkup) + rows = markup.inline_keyboard + assert [[b.text for b in row] for row in rows] == [["Yes", "No"], ["Cancel"]] + # callback_data mirrors label so _on_callback_query can echo the tap back. + assert rows[0][0].callback_data == "Yes" diff --git a/tests/tools/test_message_tool.py b/tests/tools/test_message_tool.py index dc8e11d5..b65b5cd8 100644 --- a/tests/tools/test_message_tool.py +++ b/tests/tools/test_message_tool.py @@ -8,3 +8,24 @@ async def test_message_tool_returns_error_when_no_target_context() -> None: tool = MessageTool() result = await tool.execute(content="test") assert result == "Error: No target channel/chat specified" + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "bad", + [ + "not a list", + [["ok"], "row-not-a-list"], + [["ok", 42]], + [[None]], + ], +) +async def test_message_tool_rejects_malformed_buttons(bad) -> None: + """``buttons`` must be ``list[list[str]]``; the tool validates the shape + up front so a malformed LLM payload errors visibly instead of slipping + into the channel layer where Telegram would silently reject the frame.""" + tool = MessageTool() + result = await tool.execute( + content="hi", channel="telegram", chat_id="1", buttons=bad, + ) + assert result == "Error: buttons must be a list of list of strings"