完成渠道模块的单元测试目录搭建,新增多个领域模型、端口、中间件、事件、服务以及基础设施层的单元测试文件,同时补充了conftest.py的环境变量配置,完善测试基础环境。
230 lines
6.1 KiB
Python
230 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import pytest
|
|
from uuid import uuid4
|
|
|
|
from yuxi.channel.interfaces.websocket.manager import WsConnectionManager
|
|
from yuxi.channel.domain.port.ws_connection_port import WsConnectionPort
|
|
from yuxi.channel.domain.port.channel_adapter_port import ChannelAdapterPort
|
|
from yuxi.channel.domain.model.message.unified_message import UnifiedMessage
|
|
from yuxi.channel.domain.model.message.peer import Peer
|
|
from yuxi.channel.domain.model.shared.channel_type import ChannelType
|
|
|
|
|
|
class _FakeWsConnection(WsConnectionPort):
|
|
def __init__(self, channel_type: str, connected: bool = True):
|
|
self._channel_type = channel_type
|
|
self._connected = connected
|
|
self.started = False
|
|
self.stopped = False
|
|
|
|
@property
|
|
def channel_type(self) -> str:
|
|
return self._channel_type
|
|
|
|
@property
|
|
def is_connected(self) -> bool:
|
|
return self._connected
|
|
|
|
async def start(self, loop, on_message) -> None:
|
|
self.started = True
|
|
|
|
async def stop(self) -> None:
|
|
self.stopped = True
|
|
|
|
|
|
class _FakeAdapter(ChannelAdapterPort):
|
|
def __init__(self, channel_type: str):
|
|
self._channel_type = channel_type
|
|
|
|
@property
|
|
def channel_type(self) -> str:
|
|
return self._channel_type
|
|
|
|
@property
|
|
def ws_connection(self):
|
|
return None
|
|
|
|
async def open(self) -> None:
|
|
pass
|
|
|
|
async def close(self) -> None:
|
|
pass
|
|
|
|
async def receive_message(self, raw: dict) -> UnifiedMessage:
|
|
return UnifiedMessage(
|
|
message_id="test",
|
|
channel_type=ChannelType.WEB,
|
|
sender=Peer(id="user", name="User"),
|
|
content="hello",
|
|
)
|
|
|
|
async def send_message(self, session_id: str, content: str, *, channel_type: str, metadata: dict):
|
|
from yuxi.channel.domain.model.message.dispatch_result import SendResult
|
|
return SendResult(success=True)
|
|
|
|
async def send_typing(self, session_id: str) -> None:
|
|
pass
|
|
|
|
async def send_media(self, session_id: str, *, url: str, media_type: str, metadata: dict) -> bool:
|
|
return True
|
|
|
|
async def is_healthy(self) -> bool:
|
|
return True
|
|
|
|
@property
|
|
def capabilities(self):
|
|
from yuxi.channel.domain.model.shared.channel_capabilities import ChannelCapabilities
|
|
return ChannelCapabilities()
|
|
|
|
@classmethod
|
|
def get_default_config(cls) -> dict:
|
|
return {}
|
|
|
|
|
|
class _FakeInboundService:
|
|
def __init__(self):
|
|
self.submitted = []
|
|
|
|
async def submit(self, message, *, channel_type: str, trace_id: str = ""):
|
|
self.submitted.append({"message": message, "channel_type": channel_type, "trace_id": trace_id})
|
|
|
|
|
|
class _FakeMetrics:
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def manager():
|
|
return WsConnectionManager(metrics=_FakeMetrics())
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_register(manager):
|
|
conn = _FakeWsConnection("feishu")
|
|
manager.register(conn)
|
|
assert "feishu" in manager._connections
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_set_adapters(manager):
|
|
adapters = {"feishu": _FakeAdapter("feishu")}
|
|
manager.set_adapters(adapters)
|
|
assert manager._adapters == adapters
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_start_all(manager):
|
|
conn = _FakeWsConnection("feishu")
|
|
manager.register(conn)
|
|
|
|
inbound = _FakeInboundService()
|
|
loop = asyncio.get_event_loop()
|
|
|
|
await manager.start_all(loop, inbound)
|
|
assert conn.started is True
|
|
|
|
await manager.stop_all()
|
|
assert conn.stopped is True
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_connections_status(manager):
|
|
conn1 = _FakeWsConnection("feishu", connected=True)
|
|
conn2 = _FakeWsConnection("dingtalk", connected=False)
|
|
manager.register(conn1)
|
|
manager.register(conn2)
|
|
|
|
status = manager.connections_status
|
|
assert status["feishu"] is True
|
|
assert status["dingtalk"] is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_on_message(manager):
|
|
adapter = _FakeAdapter("feishu")
|
|
manager.set_adapters({"feishu": adapter})
|
|
|
|
inbound = _FakeInboundService()
|
|
loop = asyncio.get_event_loop()
|
|
await manager.start_all(loop, inbound)
|
|
|
|
raw = {
|
|
"channel_type": "feishu",
|
|
"header": {"event_id": "test-event-id"},
|
|
"content": "hello",
|
|
}
|
|
await manager._on_message(raw)
|
|
|
|
assert len(inbound.submitted) == 1
|
|
assert inbound.submitted[0]["channel_type"] == "feishu"
|
|
assert inbound.submitted[0]["trace_id"] == "test-event-id"
|
|
|
|
await manager.stop_all()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_on_message_no_adapter(manager):
|
|
manager.set_adapters({})
|
|
|
|
inbound = _FakeInboundService()
|
|
loop = asyncio.get_event_loop()
|
|
await manager.start_all(loop, inbound)
|
|
|
|
raw = {
|
|
"channel_type": "unknown",
|
|
"header": {"event_id": "test-event-id"},
|
|
}
|
|
await manager._on_message(raw)
|
|
|
|
assert len(inbound.submitted) == 0
|
|
|
|
await manager.stop_all()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_on_message_receive_error(manager):
|
|
class _BadAdapter(_FakeAdapter):
|
|
async def receive_message(self, raw: dict) -> UnifiedMessage:
|
|
raise ValueError("parse error")
|
|
|
|
manager.set_adapters({"feishu": _BadAdapter("feishu")})
|
|
|
|
inbound = _FakeInboundService()
|
|
loop = asyncio.get_event_loop()
|
|
await manager.start_all(loop, inbound)
|
|
|
|
raw = {
|
|
"channel_type": "feishu",
|
|
"header": {"event_id": "test-event-id"},
|
|
}
|
|
await manager._on_message(raw)
|
|
|
|
assert len(inbound.submitted) == 0
|
|
|
|
await manager.stop_all()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_ws_manager_on_message_submit_error(manager):
|
|
class _BadInboundService:
|
|
async def submit(self, message, *, channel_type: str, trace_id: str = ""):
|
|
raise ValueError("submit error")
|
|
|
|
adapter = _FakeAdapter("feishu")
|
|
manager.set_adapters({"feishu": adapter})
|
|
|
|
inbound = _BadInboundService()
|
|
loop = asyncio.get_event_loop()
|
|
await manager.start_all(loop, inbound)
|
|
|
|
raw = {
|
|
"channel_type": "feishu",
|
|
"header": {"event_id": "test-event-id"},
|
|
}
|
|
# Should not raise
|
|
await manager._on_message(raw)
|
|
|
|
await manager.stop_all()
|