fix: avoid treating Feishu file paths as commands (#1654)

Feishu channel classified any slash-prefixed text (including absolute
paths such as /mnt/user-data/...) as a COMMAND, causing them to be
misrouted through the command pipeline instead of the chat pipeline.

Fix by introducing a shared KNOWN_CHANNEL_COMMANDS frozenset in
app/channels/commands.py — the single authoritative source for the set
of supported slash commands.  Both the Feishu inbound parser and the
ChannelManager's unknown-command reply now derive from it, so adding
or removing a command requires only one edit.

Changes:
- app/channels/commands.py (new): defines KNOWN_CHANNEL_COMMANDS
- app/channels/feishu.py: replace local KNOWN_FEISHU_COMMANDS with the
  shared constant; _is_feishu_command() now gates on it
- app/channels/manager.py: import KNOWN_CHANNEL_COMMANDS and use it in
  the unknown-command fallback reply so the displayed list stays in sync
- tests/test_feishu_parser.py: parametrize over every entry in
  KNOWN_CHANNEL_COMMANDS (each must yield msg_type=command) and add
  parametrized chat cases for /unknown, absolute paths, etc.

Made with Cursor

Made-with: Cursor

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
This commit is contained in:
LYU Yichen
2026-04-01 23:23:00 +08:00
committed by GitHub
parent 1fb5acee39
commit 0a379602b8
4 changed files with 88 additions and 3 deletions
+55
View File
@@ -3,6 +3,7 @@ from unittest.mock import MagicMock
import pytest
from app.channels.commands import KNOWN_CHANNEL_COMMANDS
from app.channels.feishu import FeishuChannel
from app.channels.message_bus import MessageBus
@@ -68,3 +69,57 @@ def test_feishu_on_message_rich_text():
assert "Paragraph 1, part 1. Paragraph 1, part 2." in parsed_text
assert "@bot Paragraph 2." in parsed_text
assert "\n\n" in parsed_text
@pytest.mark.parametrize("command", sorted(KNOWN_CHANNEL_COMMANDS))
def test_feishu_recognizes_all_known_slash_commands(command):
"""Every entry in KNOWN_CHANNEL_COMMANDS must be classified as a command."""
bus = MessageBus()
config = {"app_id": "test", "app_secret": "test"}
channel = FeishuChannel(bus, config)
event = MagicMock()
event.event.message.chat_id = "chat_1"
event.event.message.message_id = "msg_1"
event.event.message.root_id = None
event.event.sender.sender_id.open_id = "user_1"
event.event.message.content = json.dumps({"text": command})
with pytest.MonkeyPatch.context() as m:
mock_make_inbound = MagicMock()
m.setattr(channel, "_make_inbound", mock_make_inbound)
channel._on_message(event)
mock_make_inbound.assert_called_once()
assert mock_make_inbound.call_args[1]["msg_type"].value == "command", f"{command!r} should be classified as COMMAND"
@pytest.mark.parametrize(
"text",
[
"/unknown",
"/mnt/user-data/outputs/prd/technical-design.md",
"/etc/passwd",
"/not-a-command at all",
],
)
def test_feishu_treats_unknown_slash_text_as_chat(text):
"""Slash-prefixed text that is not a known command must be classified as CHAT."""
bus = MessageBus()
config = {"app_id": "test", "app_secret": "test"}
channel = FeishuChannel(bus, config)
event = MagicMock()
event.event.message.chat_id = "chat_1"
event.event.message.message_id = "msg_1"
event.event.message.root_id = None
event.event.sender.sender_id.open_id = "user_1"
event.event.message.content = json.dumps({"text": text})
with pytest.MonkeyPatch.context() as m:
mock_make_inbound = MagicMock()
m.setattr(channel, "_make_inbound", mock_make_inbound)
channel._on_message(event)
mock_make_inbound.assert_called_once()
assert mock_make_inbound.call_args[1]["msg_type"].value == "chat", f"{text!r} should be classified as CHAT"