import pytest from nanobot.agent.tools.message import MessageTool from nanobot.bus.events import OutboundMessage @pytest.mark.asyncio 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" @pytest.mark.asyncio async def test_message_tool_marks_channel_delivery_only_when_enabled() -> None: sent: list[OutboundMessage] = [] async def _send(msg: OutboundMessage) -> None: sent.append(msg) tool = MessageTool(send_callback=_send) await tool.execute(content="normal", channel="telegram", chat_id="1") token = tool.set_record_channel_delivery(True) try: await tool.execute(content="cron", channel="telegram", chat_id="1") finally: tool.reset_record_channel_delivery(token) assert sent[0].metadata == {} assert sent[1].metadata == {"_record_channel_delivery": True} @pytest.mark.asyncio async def test_message_tool_inherits_metadata_for_same_target() -> None: sent: list[OutboundMessage] = [] async def _send(msg: OutboundMessage) -> None: sent.append(msg) tool = MessageTool(send_callback=_send) slack_meta = {"slack": {"thread_ts": "111.222", "channel_type": "channel"}} tool.set_context("slack", "C123", metadata=slack_meta) await tool.execute(content="thread reply") assert sent[0].metadata == slack_meta @pytest.mark.asyncio async def test_message_tool_does_not_inherit_metadata_for_cross_target() -> None: sent: list[OutboundMessage] = [] async def _send(msg: OutboundMessage) -> None: sent.append(msg) tool = MessageTool(send_callback=_send) tool.set_context( "slack", "C123", metadata={"slack": {"thread_ts": "111.222", "channel_type": "channel"}}, ) await tool.execute(content="channel reply", channel="slack", chat_id="C999") assert sent[0].metadata == {}