- Format multiple tool calls each on their own line - Change title from 'Tool Call' to 'Tool Calls' (plural) - Add explicit 'text' language for code block - Improves readability and supports displaying longer content - Update tests to match new formatting
117 lines
4.0 KiB
Python
117 lines
4.0 KiB
Python
"""Tests for FeishuChannel tool hint code block formatting."""
|
|
|
|
import json
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from nanobot.bus.events import OutboundMessage
|
|
from nanobot.channels.feishu import FeishuChannel
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_feishu_channel():
|
|
"""Create a FeishuChannel with mocked client."""
|
|
config = MagicMock()
|
|
config.app_id = "test_app_id"
|
|
config.app_secret = "test_app_secret"
|
|
config.encrypt_key = None
|
|
config.verification_token = None
|
|
bus = MagicMock()
|
|
channel = FeishuChannel(config, bus)
|
|
channel._client = MagicMock() # Simulate initialized client
|
|
return channel
|
|
|
|
|
|
def test_tool_hint_sends_code_message(mock_feishu_channel):
|
|
"""Tool hint messages should be sent as interactive cards with code blocks."""
|
|
msg = OutboundMessage(
|
|
channel="feishu",
|
|
chat_id="oc_123456",
|
|
content='web_search("test query")',
|
|
metadata={"_tool_hint": True}
|
|
)
|
|
|
|
with patch.object(mock_feishu_channel, '_send_message_sync') as mock_send:
|
|
# Run send in async context
|
|
import asyncio
|
|
asyncio.run(mock_feishu_channel.send(msg))
|
|
|
|
# Verify interactive message with card was sent
|
|
assert mock_send.call_count == 1
|
|
call_args = mock_send.call_args[0]
|
|
receive_id_type, receive_id, msg_type, content = call_args
|
|
|
|
assert receive_id_type == "chat_id"
|
|
assert receive_id == "oc_123456"
|
|
assert msg_type == "interactive"
|
|
|
|
# Parse content to verify card structure
|
|
card = json.loads(content)
|
|
assert card["config"]["wide_screen_mode"] is True
|
|
assert len(card["elements"]) == 1
|
|
assert card["elements"][0]["tag"] == "markdown"
|
|
# Check that code block is properly formatted with language hint
|
|
expected_md = "**Tool Calls**\n\n```text\nweb_search(\"test query\")\n```"
|
|
assert card["elements"][0]["content"] == expected_md
|
|
|
|
|
|
def test_tool_hint_empty_content_does_not_send(mock_feishu_channel):
|
|
"""Empty tool hint messages should not be sent."""
|
|
msg = OutboundMessage(
|
|
channel="feishu",
|
|
chat_id="oc_123456",
|
|
content=" ", # whitespace only
|
|
metadata={"_tool_hint": True}
|
|
)
|
|
|
|
with patch.object(mock_feishu_channel, '_send_message_sync') as mock_send:
|
|
import asyncio
|
|
asyncio.run(mock_feishu_channel.send(msg))
|
|
|
|
# Should not send any message
|
|
mock_send.assert_not_called()
|
|
|
|
|
|
def test_tool_hint_without_metadata_sends_as_normal(mock_feishu_channel):
|
|
"""Regular messages without _tool_hint should use normal formatting."""
|
|
msg = OutboundMessage(
|
|
channel="feishu",
|
|
chat_id="oc_123456",
|
|
content="Hello, world!",
|
|
metadata={}
|
|
)
|
|
|
|
with patch.object(mock_feishu_channel, '_send_message_sync') as mock_send:
|
|
import asyncio
|
|
asyncio.run(mock_feishu_channel.send(msg))
|
|
|
|
# Should send as text message (detected format)
|
|
assert mock_send.call_count == 1
|
|
call_args = mock_send.call_args[0]
|
|
_, _, msg_type, content = call_args
|
|
assert msg_type == "text"
|
|
assert json.loads(content) == {"text": "Hello, world!"}
|
|
|
|
|
|
def test_tool_hint_multiple_tools_in_one_message(mock_feishu_channel):
|
|
"""Multiple tool calls should be displayed each on its own line in a code block."""
|
|
msg = OutboundMessage(
|
|
channel="feishu",
|
|
chat_id="oc_123456",
|
|
content='web_search("query"), read_file("/path/to/file")',
|
|
metadata={"_tool_hint": True}
|
|
)
|
|
|
|
with patch.object(mock_feishu_channel, '_send_message_sync') as mock_send:
|
|
import asyncio
|
|
asyncio.run(mock_feishu_channel.send(msg))
|
|
|
|
call_args = mock_send.call_args[0]
|
|
msg_type = call_args[2]
|
|
content = json.loads(call_args[3])
|
|
assert msg_type == "interactive"
|
|
# Each tool call should be on its own line
|
|
expected_md = "**Tool Calls**\n\n```text\nweb_search(\"query\"),\nread_file(\"/path/to/file\")\n```"
|
|
assert content["elements"][0]["content"] == expected_md
|