Refactor message sending architecture and implement legacy driver support
- Removed UniversalMessageSender from group_generator.py and private_generator.py. - Updated PlatformIOManager to manage legacy send drivers and ensure send pipeline readiness. - Enhanced LegacyPlatformDriver to utilize prepared messages for sending. - Refactored send_service to unify message sending logic and integrate with Platform IO. - Added regression tests for Platform IO legacy driver and send service functionality.
This commit is contained in:
124
pytests/test_platform_io_legacy_driver.py
Normal file
124
pytests/test_platform_io_legacy_driver.py
Normal file
@@ -0,0 +1,124 @@
|
||||
"""Platform IO legacy driver 回归测试。"""
|
||||
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
from src.chat.utils import utils as chat_utils
|
||||
from src.chat.message_receive import uni_message_sender
|
||||
from src.platform_io.drivers.base import PlatformIODriver
|
||||
from src.platform_io.drivers.legacy_driver import LegacyPlatformDriver
|
||||
from src.platform_io.manager import PlatformIOManager
|
||||
from src.platform_io.types import DeliveryReceipt, DeliveryStatus, DriverDescriptor, DriverKind, RouteBinding, RouteKey
|
||||
|
||||
|
||||
class _PluginDriver(PlatformIODriver):
|
||||
"""测试用插件发送驱动。"""
|
||||
|
||||
def __init__(self, driver_id: str, platform: str) -> None:
|
||||
"""初始化测试驱动。
|
||||
|
||||
Args:
|
||||
driver_id: 驱动 ID。
|
||||
platform: 负责的平台名称。
|
||||
"""
|
||||
super().__init__(
|
||||
DriverDescriptor(
|
||||
driver_id=driver_id,
|
||||
kind=DriverKind.PLUGIN,
|
||||
platform=platform,
|
||||
plugin_id="test.plugin",
|
||||
)
|
||||
)
|
||||
|
||||
async def send_message(
|
||||
self,
|
||||
message: Any,
|
||||
route_key: RouteKey,
|
||||
metadata: Optional[Dict[str, Any]] = None,
|
||||
) -> DeliveryReceipt:
|
||||
"""返回一个固定成功回执。
|
||||
|
||||
Args:
|
||||
message: 待发送消息。
|
||||
route_key: 当前路由键。
|
||||
metadata: 发送元数据。
|
||||
|
||||
Returns:
|
||||
DeliveryReceipt: 固定成功回执。
|
||||
"""
|
||||
del metadata
|
||||
return DeliveryReceipt(
|
||||
internal_message_id=str(message.message_id),
|
||||
route_key=route_key,
|
||||
status=DeliveryStatus.SENT,
|
||||
driver_id=self.driver_id,
|
||||
driver_kind=self.descriptor.kind,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_platform_io_uses_legacy_driver_when_no_explicit_send_route(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""没有显式发送路由时,应由 Platform IO 回退到 legacy driver。"""
|
||||
manager = PlatformIOManager()
|
||||
monkeypatch.setattr(chat_utils, "get_all_bot_accounts", lambda: {"qq": "bot-qq"})
|
||||
|
||||
try:
|
||||
await manager.ensure_send_pipeline_ready()
|
||||
|
||||
fallback_drivers = manager.resolve_drivers(RouteKey(platform="qq"))
|
||||
assert [driver.driver_id for driver in fallback_drivers] == ["legacy.send.qq"]
|
||||
|
||||
plugin_driver = _PluginDriver(driver_id="plugin.qq.sender", platform="qq")
|
||||
await manager.add_driver(plugin_driver)
|
||||
manager.bind_send_route(
|
||||
RouteBinding(
|
||||
route_key=RouteKey(platform="qq"),
|
||||
driver_id=plugin_driver.driver_id,
|
||||
driver_kind=plugin_driver.descriptor.kind,
|
||||
)
|
||||
)
|
||||
|
||||
explicit_drivers = manager.resolve_drivers(RouteKey(platform="qq"))
|
||||
assert [driver.driver_id for driver in explicit_drivers] == ["plugin.qq.sender"]
|
||||
finally:
|
||||
await manager.stop()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_legacy_platform_driver_uses_prepared_universal_sender(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""legacy driver 应复用已预处理消息的旧链发送函数。"""
|
||||
calls: list[dict[str, Any]] = []
|
||||
|
||||
async def _fake_send_prepared_message_to_platform(message: Any, show_log: bool = True) -> bool:
|
||||
"""记录 legacy driver 调用。"""
|
||||
calls.append({"message": message, "show_log": show_log})
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(
|
||||
uni_message_sender,
|
||||
"send_prepared_message_to_platform",
|
||||
_fake_send_prepared_message_to_platform,
|
||||
)
|
||||
|
||||
driver = LegacyPlatformDriver(
|
||||
driver_id="legacy.send.qq",
|
||||
platform="qq",
|
||||
account_id="bot-qq",
|
||||
)
|
||||
message = type("FakeMessage", (), {"message_id": "message-1"})()
|
||||
receipt = await driver.send_message(
|
||||
message=message,
|
||||
route_key=RouteKey(platform="qq"),
|
||||
metadata={"show_log": False},
|
||||
)
|
||||
|
||||
assert len(calls) == 1
|
||||
assert calls[0]["message"] is message
|
||||
assert calls[0]["show_log"] is False
|
||||
assert receipt.status == DeliveryStatus.SENT
|
||||
assert receipt.driver_id == "legacy.send.qq"
|
||||
141
pytests/test_send_service.py
Normal file
141
pytests/test_send_service.py
Normal file
@@ -0,0 +1,141 @@
|
||||
"""发送服务回归测试。"""
|
||||
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import pytest
|
||||
|
||||
from src.chat.message_receive.chat_manager import BotChatSession
|
||||
from src.services import send_service
|
||||
|
||||
|
||||
class _FakePlatformIOManager:
|
||||
"""用于测试的 Platform IO 管理器假对象。"""
|
||||
|
||||
def __init__(self, delivery_batch: Any) -> None:
|
||||
"""初始化假 Platform IO 管理器。
|
||||
|
||||
Args:
|
||||
delivery_batch: 发送时返回的批量回执。
|
||||
"""
|
||||
self._delivery_batch = delivery_batch
|
||||
self.ensure_calls = 0
|
||||
self.sent_messages: List[Dict[str, Any]] = []
|
||||
|
||||
async def ensure_send_pipeline_ready(self) -> None:
|
||||
"""记录发送管线准备调用次数。"""
|
||||
self.ensure_calls += 1
|
||||
|
||||
def build_route_key_from_message(self, message: Any) -> Any:
|
||||
"""根据消息构造假的路由键。
|
||||
|
||||
Args:
|
||||
message: 待发送的内部消息对象。
|
||||
|
||||
Returns:
|
||||
Any: 简化后的路由键对象。
|
||||
"""
|
||||
del message
|
||||
return SimpleNamespace(platform="qq")
|
||||
|
||||
async def send_message(self, message: Any, route_key: Any, metadata: Dict[str, Any]) -> Any:
|
||||
"""记录发送请求并返回预设回执。
|
||||
|
||||
Args:
|
||||
message: 待发送的内部消息对象。
|
||||
route_key: 本次发送使用的路由键。
|
||||
metadata: 发送元数据。
|
||||
|
||||
Returns:
|
||||
Any: 预设的批量发送回执。
|
||||
"""
|
||||
self.sent_messages.append(
|
||||
{
|
||||
"message": message,
|
||||
"route_key": route_key,
|
||||
"metadata": metadata,
|
||||
}
|
||||
)
|
||||
return self._delivery_batch
|
||||
|
||||
|
||||
def _build_target_stream() -> BotChatSession:
|
||||
"""构造一个最小可用的目标会话对象。
|
||||
|
||||
Returns:
|
||||
BotChatSession: 测试用会话对象。
|
||||
"""
|
||||
return BotChatSession(
|
||||
session_id="test-session",
|
||||
platform="qq",
|
||||
user_id="target-user",
|
||||
group_id=None,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_to_stream_delegates_to_platform_io(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""send service 应将发送职责统一交给 Platform IO。"""
|
||||
fake_manager = _FakePlatformIOManager(
|
||||
delivery_batch=SimpleNamespace(
|
||||
has_success=True,
|
||||
sent_receipts=[SimpleNamespace(driver_id="plugin.qq.sender")],
|
||||
failed_receipts=[],
|
||||
route_key=SimpleNamespace(platform="qq"),
|
||||
)
|
||||
)
|
||||
stored_messages: List[Any] = []
|
||||
|
||||
monkeypatch.setattr(send_service, "get_platform_io_manager", lambda: fake_manager)
|
||||
monkeypatch.setattr(send_service, "get_bot_account", lambda platform: "bot-qq")
|
||||
monkeypatch.setattr(
|
||||
send_service._chat_manager,
|
||||
"get_session_by_session_id",
|
||||
lambda stream_id: _build_target_stream() if stream_id == "test-session" else None,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
send_service.MessageUtils,
|
||||
"store_message_to_db",
|
||||
lambda message: stored_messages.append(message),
|
||||
)
|
||||
|
||||
result = await send_service.text_to_stream(text="你好", stream_id="test-session")
|
||||
|
||||
assert result is True
|
||||
assert fake_manager.ensure_calls == 1
|
||||
assert len(fake_manager.sent_messages) == 1
|
||||
assert fake_manager.sent_messages[0]["metadata"] == {"show_log": False}
|
||||
assert len(stored_messages) == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_to_stream_returns_false_when_platform_io_fails(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Platform IO 批量发送全部失败时,应直接向上返回失败。"""
|
||||
fake_manager = _FakePlatformIOManager(
|
||||
delivery_batch=SimpleNamespace(
|
||||
has_success=False,
|
||||
sent_receipts=[],
|
||||
failed_receipts=[
|
||||
SimpleNamespace(
|
||||
driver_id="plugin.qq.sender",
|
||||
status="failed",
|
||||
error="network error",
|
||||
)
|
||||
],
|
||||
route_key=SimpleNamespace(platform="qq"),
|
||||
)
|
||||
)
|
||||
|
||||
monkeypatch.setattr(send_service, "get_platform_io_manager", lambda: fake_manager)
|
||||
monkeypatch.setattr(send_service, "get_bot_account", lambda platform: "bot-qq")
|
||||
monkeypatch.setattr(
|
||||
send_service._chat_manager,
|
||||
"get_session_by_session_id",
|
||||
lambda stream_id: _build_target_stream() if stream_id == "test-session" else None,
|
||||
)
|
||||
|
||||
result = await send_service.text_to_stream(text="发送失败", stream_id="test-session")
|
||||
|
||||
assert result is False
|
||||
assert fake_manager.ensure_calls == 1
|
||||
assert len(fake_manager.sent_messages) == 1
|
||||
Reference in New Issue
Block a user