ForcePilot/backend/test/unit/channels/test_telegram_adapter.py
Kris 3264900bc9 test: 新增多渠道单元测试用例并配置测试环境变量
新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块
同时在测试配置中添加了测试用的OpenAI API密钥环境变量
2026-05-12 00:56:47 +08:00

219 lines
7.4 KiB
Python

from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from telegram import Chat, Message, MessageEntity, Update, User
from yuxi.channels.adapters.telegram.adapter import TelegramAdapter
from yuxi.channels.adapters.telegram.session import resolve_chat_type, resolve_thread_key
from yuxi.channels.models import (
Attachment,
ChannelIdentity,
ChannelMessage,
ChannelResponse,
ChannelStatus,
ChannelType,
ChatType,
DeliveryResult,
EventType,
HealthStatus,
MessageType,
)
def make_user(id=12345, username="testuser", first_name="Test", last_name="User"):
return User(id=id, is_bot=False, first_name=first_name, username=username, last_name=last_name)
def make_chat(id=-10012345, chat_type="private", username=None):
return Chat(id=id, type=chat_type, username=username)
def make_message(
message_id=1,
text="hello",
from_user=None,
chat=None,
date=None,
entities=(),
is_topic_message=False,
message_thread_id=None,
):
from datetime import datetime
return Message(
message_id=message_id,
date=date or datetime.now(),
chat=chat or make_chat(),
from_user=from_user or make_user(),
text=text,
entities=entities,
is_topic_message=is_topic_message,
message_thread_id=message_thread_id,
)
def make_update(message=None):
return Update(update_id=1, message=message)
def make_command_message(text="/start"):
from telegram import MessageEntity
entities = (MessageEntity(type=MessageEntity.BOT_COMMAND, offset=0, length=len(text)),)
return make_message(text=text, entities=entities)
class TestTelegramAdapter:
@pytest.fixture
def adapter(self):
config = {"bot_token": "123:abc", "dm_policy": "open", "reply_to_mode": "first"}
return TelegramAdapter(config=config)
def test_channel_id(self, adapter):
assert adapter.channel_id == "telegram"
def test_channel_type(self, adapter):
assert adapter.channel_type == ChannelType.TELEGRAM
def test_capabilities(self, adapter):
assert adapter.supports_streaming is True
assert adapter.text_chunk_limit == 4096
assert adapter.max_media_size_mb == 100
def test_normalize_text_message(self, adapter):
user = make_user()
chat = make_chat()
msg = make_message(text="hello world", from_user=user, chat=chat)
update = make_update(msg)
result = adapter.normalize_inbound(update)
assert isinstance(result, ChannelMessage)
assert result.content == "hello world"
assert result.identity.channel_user_id == "12345"
assert result.identity.channel_chat_id == "-10012345"
assert result.identity.channel_message_id == "1"
assert result.message_type == MessageType.TEXT
assert result.chat_type == ChatType.DIRECT
def test_normalize_command(self, adapter):
msg = make_command_message(text="/start")
update = make_update(msg)
result = adapter.normalize_inbound(update)
assert result.message_type == MessageType.COMMAND
def test_normalize_group_message(self, adapter):
chat = make_chat(id=-100999, chat_type="supergroup")
msg = make_message(text="hi group", chat=chat)
update = make_update(msg)
result = adapter.normalize_inbound(update)
assert result.chat_type == ChatType.GROUP
assert result.metadata["telegram_chat_type"] == "supergroup"
def test_normalize_with_thread_id(self, adapter):
chat = make_chat(id=-100999, chat_type="supergroup")
msg = make_message(text="topic msg", chat=chat, is_topic_message=True, message_thread_id=42)
update = make_update(msg)
result = adapter.normalize_inbound(update)
assert result.metadata["is_topic_message"] is True
assert result.metadata["thread_id"] == "42"
def test_normalize_without_message(self, adapter):
update = Update(update_id=1)
result = adapter.normalize_inbound(update)
assert result.content == "(unknown update)"
def test_normalize_edited_message(self, adapter):
msg = make_message(text="edited")
update = Update(update_id=1, edited_message=msg)
result = adapter.normalize_inbound(update)
assert result.event_type == EventType.MESSAGE_UPDATED
assert result.content == "(passthrough)"
def test_format_outbound_text(self, adapter):
identity = ChannelIdentity(
channel_id="telegram",
channel_type=ChannelType.TELEGRAM,
channel_user_id="12345",
channel_chat_id="-10012345",
)
response = ChannelResponse(identity=identity, content="hello world")
payload = adapter.format_outbound(response)
assert payload["chat_id"] == "-10012345"
assert "text" in payload
assert payload["parse_mode"] == "HTML"
assert "link_preview_options" in payload
def test_format_outbound_with_reply(self, adapter):
identity = ChannelIdentity(
channel_id="telegram",
channel_type=ChannelType.TELEGRAM,
channel_user_id="12345",
channel_chat_id="-10012345",
)
response = ChannelResponse(identity=identity, content="reply", reply_to_message_id="10")
payload = adapter.format_outbound(response)
assert payload["reply_to_message_id"] == 10
def test_format_outbound_with_thread(self, adapter):
identity = ChannelIdentity(
channel_id="telegram",
channel_type=ChannelType.TELEGRAM,
channel_user_id="12345",
channel_chat_id="-10012345",
)
response = ChannelResponse(identity=identity, content="thread msg", metadata={"thread_id": "42"})
payload = adapter.format_outbound(response)
assert payload["message_thread_id"] == "42"
@pytest.mark.asyncio
async def test_health_check_no_app(self):
config = {"bot_token": "123:abc"}
adapter = TelegramAdapter(config=config)
result = await adapter.health_check()
assert result.status == "unhealthy"
@pytest.mark.asyncio
async def test_pre_connect_invalid_token(self):
config = {"bot_token": "invalid:token"}
adapter = TelegramAdapter(config=config)
result = await adapter.pre_connect()
assert result["status"] == "error"
@pytest.mark.asyncio
async def test_pre_connect_missing_token(self):
adapter = TelegramAdapter(config={})
result = await adapter.pre_connect()
assert result["status"] == "error"
assert "Missing bot_token" in result["message"]
class TestSessionResolution:
def test_resolve_chat_type_private(self):
assert resolve_chat_type("private") == ChatType.DIRECT
def test_resolve_chat_type_group(self):
assert resolve_chat_type("group") == ChatType.GROUP
def test_resolve_chat_type_supergroup(self):
assert resolve_chat_type("supergroup") == ChatType.GROUP
def test_resolve_chat_type_channel(self):
assert resolve_chat_type("channel") == ChatType.GUILD_CHANNEL
def test_resolve_thread_key_without_thread(self):
key = resolve_thread_key("123", None)
assert key == "telegram:123"
def test_resolve_thread_key_with_thread(self):
key = resolve_thread_key("123", "42")
assert key == "telegram:123:topic:42"