Files
EvoScientist/tests/test_channel_manager.py
T
2026-02-15 21:07:47 +08:00

167 lines
4.7 KiB
Python

"""Tests for ChannelManager."""
import asyncio
import pytest
from EvoScientist.channels.bus.message_bus import MessageBus
from EvoScientist.channels.channel_manager import ChannelManager
from EvoScientist.channels.base import Channel, OutboundMessage
def _run(coro):
"""Run an async coroutine safely, creating a fresh event loop."""
loop = asyncio.new_event_loop()
try:
return loop.run_until_complete(coro)
finally:
loop.close()
class _FakeConfig:
text_chunk_limit = 4096
allowed_senders = None
class FakeChannel(Channel):
"""Minimal channel for testing."""
name = "fake"
def __init__(self):
super().__init__(_FakeConfig())
self._started = False
self._stopped = False
self._sent: list[OutboundMessage] = []
async def start(self):
self._started = True
async def stop(self):
self._stopped = True
async def receive(self):
while True:
try:
msg = await asyncio.wait_for(
self._queue.get(), timeout=0.5,
)
yield msg
except asyncio.TimeoutError:
return
async def send(self, message: OutboundMessage) -> bool:
self._sent.append(message)
return True
async def _send_chunk(self, chat_id, formatted_text, raw_text, reply_to, metadata):
pass
class TestChannelManagerRegister:
def test_register_channel(self):
bus = MessageBus()
mgr = ChannelManager(bus)
ch = FakeChannel()
result = mgr.register(ch)
assert "fake" in mgr.enabled_channels
assert mgr.get_channel("fake") is ch
assert result is ch
def test_duplicate_register_raises(self):
bus = MessageBus()
mgr = ChannelManager(bus)
mgr.register(FakeChannel())
with pytest.raises(ValueError, match="already registered"):
mgr.register(FakeChannel())
def test_get_status(self):
bus = MessageBus()
mgr = ChannelManager(bus)
mgr.register(FakeChannel())
status = mgr.get_status()
assert "fake" in status
assert status["fake"]["registered"] is True
class TestChannelManagerDispatch:
def test_outbound_dispatch_routes_to_channel(self):
async def _test():
bus = MessageBus()
mgr = ChannelManager(bus)
ch = FakeChannel()
mgr.register(ch)
# Start only the dispatcher (not full start_all)
dispatch = asyncio.create_task(
mgr._dispatch_outbound()
)
# Publish an outbound message
await bus.publish_outbound(OutboundMessage(
channel="fake", chat_id="u1",
content="hello from agent",
))
await asyncio.sleep(0.1)
dispatch.cancel()
try:
await dispatch
except asyncio.CancelledError:
pass
assert len(ch._sent) == 1
assert ch._sent[0].content == "hello from agent"
assert ch._sent[0].chat_id == "u1"
_run(_test())
class TestChannelManagerTracking:
def test_record_message(self):
bus = MessageBus()
mgr = ChannelManager(bus)
mgr.register(FakeChannel())
mgr.record_message("fake", "received")
mgr.record_message("fake", "received")
mgr.record_message("fake", "sent")
assert mgr._message_counts["fake"]["received"] == 2
assert mgr._message_counts["fake"]["sent"] == 1
def test_record_message_unknown_channel(self):
bus = MessageBus()
mgr = ChannelManager(bus)
# Should not raise, auto-creates entry
mgr.record_message("unknown", "received")
assert mgr._message_counts["unknown"]["received"] == 1
def test_get_detailed_status(self):
bus = MessageBus()
mgr = ChannelManager(bus)
mgr.register(FakeChannel())
# Simulate start_all setting start_times
from datetime import datetime
mgr._start_times["fake"] = datetime.now()
mgr._message_counts["fake"] = {"received": 5, "sent": 3}
status = mgr.get_detailed_status()
assert "fake" in status
assert status["fake"]["registered"] is True
assert status["fake"]["received"] == 5
assert status["fake"]["sent"] == 3
assert status["fake"]["uptime_seconds"] >= 0
assert status["fake"]["start_time"] is not None
def test_get_detailed_status_no_start_time(self):
bus = MessageBus()
mgr = ChannelManager(bus)
mgr.register(FakeChannel())
status = mgr.get_detailed_status()
assert status["fake"]["uptime_seconds"] == 0
assert status["fake"]["start_time"] is None