Feature/feishu receive file (#1608)
* feat(feishu): add channel file materialization hook for inbound messages - Introduce Channel.receive_file(msg, thread_id) as a base method for file materialization; default is no-op. - Implement FeishuChannel.receive_file to download files/images from Feishu messages, save to sandbox, and inject virtual paths into msg.text. - Update ChannelManager to call receive_file for any channel if msg.files is present, enabling downstream model access to user-uploaded files. - No impact on Slack/Telegram or other channels (they inherit the default no-op). * style(backend): format code with ruff for lint compliance - Auto-formatted packages/harness/deerflow/agents/factory.py and tests/test_create_deerflow_agent.py using `ruff format` - Ensured both files conform to project linting standards - Fixes CI lint check failures caused by code style issues * fix(feishu): handle file write operation asynchronously to prevent blocking * fix(feishu): rename GetMessageResourceRequest to _GetMessageResourceRequest and remove redundant code * test(feishu): add tests for receive_file method and placeholder replacement * fix(manager): remove unnecessary type casting for channel retrieval * fix(feishu): update logging messages to reflect resource handling instead of image * fix(feishu): sanitize filename by replacing invalid characters in file uploads * fix(feishu): improve filename sanitization and reorder image key handling in message processing * fix(feishu): add thread lock to prevent filename conflicts during file downloads * fix(test): correct bad merge in test_feishu_parser.py * chore: run ruff and apply formatting cleanup fix(feishu): preserve rich-text attachment order and improve fallback filename handling
This commit is contained in:
@@ -414,6 +414,62 @@ def _make_async_iterator(items):
|
||||
|
||||
|
||||
class TestChannelManager:
|
||||
def test_handle_chat_calls_channel_receive_file_for_inbound_files(self, monkeypatch):
|
||||
from app.channels.manager import ChannelManager
|
||||
|
||||
async def go():
|
||||
bus = MessageBus()
|
||||
store = ChannelStore(path=Path(tempfile.mkdtemp()) / "store.json")
|
||||
manager = ChannelManager(bus=bus, store=store)
|
||||
|
||||
outbound_received = []
|
||||
|
||||
async def capture_outbound(msg):
|
||||
outbound_received.append(msg)
|
||||
|
||||
bus.subscribe_outbound(capture_outbound)
|
||||
|
||||
mock_client = _make_mock_langgraph_client()
|
||||
manager._client = mock_client
|
||||
|
||||
modified_msg = InboundMessage(
|
||||
channel_name="test",
|
||||
chat_id="chat1",
|
||||
user_id="user1",
|
||||
text="with /mnt/user-data/uploads/demo.png",
|
||||
files=[{"image_key": "img_1"}],
|
||||
)
|
||||
mock_channel = MagicMock()
|
||||
mock_channel.receive_file = AsyncMock(return_value=modified_msg)
|
||||
mock_service = MagicMock()
|
||||
mock_service.get_channel.return_value = mock_channel
|
||||
monkeypatch.setattr("app.channels.service.get_channel_service", lambda: mock_service)
|
||||
|
||||
await manager.start()
|
||||
|
||||
inbound = InboundMessage(
|
||||
channel_name="test",
|
||||
chat_id="chat1",
|
||||
user_id="user1",
|
||||
text="hi [image]",
|
||||
files=[{"image_key": "img_1"}],
|
||||
)
|
||||
await bus.publish_inbound(inbound)
|
||||
await _wait_for(lambda: len(outbound_received) >= 1)
|
||||
await manager.stop()
|
||||
|
||||
mock_channel.receive_file.assert_awaited_once()
|
||||
called_msg, called_thread_id = mock_channel.receive_file.await_args.args
|
||||
assert called_msg.text == "hi [image]"
|
||||
assert isinstance(called_thread_id, str)
|
||||
assert called_thread_id
|
||||
|
||||
mock_client.runs.wait.assert_called_once()
|
||||
run_call_args = mock_client.runs.wait.call_args
|
||||
assert run_call_args[1]["input"]["messages"][0]["content"] == "with /mnt/user-data/uploads/demo.png"
|
||||
|
||||
_run(go())
|
||||
|
||||
def test_handle_chat_creates_thread(self):
|
||||
from app.channels.manager import ChannelManager
|
||||
|
||||
|
||||
Reference in New Issue
Block a user