新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
219 lines
7.4 KiB
Python
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" |