新增了Twitch、Telegram、Discord、Slack、Mattermost、WeChat、Zalo等多渠道的单元测试用例,覆盖了令牌处理、速率限制、消息去重、会话解析、格式转换、安全策略等模块 同时在测试配置中添加了测试用的OpenAI API密钥环境变量
180 lines
6.1 KiB
Python
180 lines
6.1 KiB
Python
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from yuxi.channels.infra.broadcast import EventBroadcaster
|
|
|
|
|
|
class TestEventBroadcaster:
|
|
@pytest.mark.asyncio
|
|
async def test_broadcast_delivers_to_subscriber(self):
|
|
broadcaster = EventBroadcaster()
|
|
queue = broadcaster.subscribe("test.event")
|
|
|
|
await broadcaster.broadcast("test.event", {"key": "value"})
|
|
|
|
received = await asyncio.wait_for(queue.get(), timeout=1.0)
|
|
assert received == {"event": "test.event", "payload": {"key": "value"}}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_broadcast_does_not_deliver_to_other_events(self):
|
|
broadcaster = EventBroadcaster()
|
|
queue = broadcaster.subscribe("event.a")
|
|
|
|
await broadcaster.broadcast("event.b", {"key": "value"})
|
|
|
|
with pytest.raises(asyncio.TimeoutError):
|
|
await asyncio.wait_for(queue.get(), timeout=0.1)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wildcard_subscriber_receives_all(self):
|
|
broadcaster = EventBroadcaster()
|
|
queue = broadcaster.subscribe("*")
|
|
|
|
await broadcaster.broadcast("any.event", {"key": "value"})
|
|
|
|
received = await asyncio.wait_for(queue.get(), timeout=1.0)
|
|
assert received == {"event": "any.event", "payload": {"key": "value"}}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unsubscribe_stops_delivery(self):
|
|
broadcaster = EventBroadcaster()
|
|
queue = broadcaster.subscribe("test.event")
|
|
broadcaster.unsubscribe("test.event", queue)
|
|
|
|
await broadcaster.broadcast("test.event", {})
|
|
|
|
with pytest.raises(asyncio.TimeoutError):
|
|
await asyncio.wait_for(queue.get(), timeout=0.1)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unsubscribe_nonexistent_does_not_raise(self):
|
|
broadcaster = EventBroadcaster()
|
|
queue: asyncio.Queue = asyncio.Queue()
|
|
broadcaster.unsubscribe("nonexistent", queue)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_node_send_to_session(self):
|
|
broadcaster = EventBroadcaster()
|
|
queue = broadcaster.subscribe("session:sess-1")
|
|
|
|
await broadcaster.node_send_to_session("sess-1", "custom.event", {"data": 1})
|
|
|
|
received = await asyncio.wait_for(queue.get(), timeout=1.0)
|
|
assert received == {"event": "custom.event", "payload": {"data": 1}}
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_multiple_subscribers_same_event(self):
|
|
broadcaster = EventBroadcaster()
|
|
q1 = broadcaster.subscribe("multi")
|
|
q2 = broadcaster.subscribe("multi")
|
|
|
|
await broadcaster.broadcast("multi", {"x": 1})
|
|
|
|
r1 = await asyncio.wait_for(q1.get(), timeout=1.0)
|
|
r2 = await asyncio.wait_for(q2.get(), timeout=1.0)
|
|
assert r1["payload"] == {"x": 1}
|
|
assert r2["payload"] == {"x": 1}
|
|
|
|
def test_subscriber_count(self):
|
|
broadcaster = EventBroadcaster()
|
|
assert broadcaster.subscriber_count() == 0
|
|
|
|
broadcaster.subscribe("e1")
|
|
broadcaster.subscribe("e1")
|
|
broadcaster.subscribe("e2")
|
|
assert broadcaster.subscriber_count("e1") == 2
|
|
assert broadcaster.subscriber_count("e2") == 1
|
|
assert broadcaster.subscriber_count() == 3
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_callback_subscription_receives_broadcast(self):
|
|
broadcaster = EventBroadcaster()
|
|
received = []
|
|
|
|
def handler(event, payload):
|
|
received.append((event, payload))
|
|
|
|
broadcaster.subscribe_callback("test.event", handler)
|
|
await broadcaster.broadcast("test.event", {"data": 1})
|
|
|
|
assert received == [("test.event", {"data": 1})]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_async_callback_subscription(self):
|
|
broadcaster = EventBroadcaster()
|
|
received = []
|
|
|
|
async def handler(event, payload):
|
|
received.append((event, payload))
|
|
|
|
broadcaster.subscribe_callback("test.event", handler)
|
|
await broadcaster.broadcast("test.event", {"data": 1})
|
|
|
|
assert received == [("test.event", {"data": 1})]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wildcard_callback_receives_all(self):
|
|
broadcaster = EventBroadcaster()
|
|
received = []
|
|
|
|
def handler(event, payload):
|
|
received.append(event)
|
|
|
|
broadcaster.subscribe_callback("*", handler)
|
|
await broadcaster.broadcast("event.a", {})
|
|
await broadcaster.broadcast("event.b", {})
|
|
|
|
assert received == ["event.a", "event.b"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unsubscribe_callback_stops_delivery(self):
|
|
broadcaster = EventBroadcaster()
|
|
received = []
|
|
|
|
def handler(event, payload):
|
|
received.append(event)
|
|
|
|
broadcaster.subscribe_callback("test.event", handler)
|
|
broadcaster.unsubscribe_callback("test.event", handler)
|
|
await broadcaster.broadcast("test.event", {})
|
|
|
|
assert received == []
|
|
|
|
def test_subscriber_count_includes_callbacks(self):
|
|
broadcaster = EventBroadcaster()
|
|
broadcaster.subscribe("e1")
|
|
broadcaster.subscribe_callback("e1", lambda e, p: None)
|
|
broadcaster.subscribe_callback("e2", lambda e, p: None)
|
|
assert broadcaster.subscriber_count("e1") == 2
|
|
assert broadcaster.subscriber_count("e2") == 1
|
|
assert broadcaster.subscriber_count() == 3
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_callback_error_does_not_block_broadcast(self):
|
|
broadcaster = EventBroadcaster()
|
|
received = []
|
|
|
|
def good_handler(event, payload):
|
|
received.append(event)
|
|
|
|
def bad_handler(event, payload):
|
|
raise RuntimeError("boom")
|
|
|
|
broadcaster.subscribe_callback("test.event", bad_handler)
|
|
broadcaster.subscribe_callback("test.event", good_handler)
|
|
await broadcaster.broadcast("test.event", {})
|
|
|
|
assert received == ["test.event"]
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_broadcast_without_payload(self):
|
|
broadcaster = EventBroadcaster()
|
|
queue = broadcaster.subscribe("test.event")
|
|
|
|
await broadcaster.broadcast("test.event")
|
|
|
|
received = await asyncio.wait_for(queue.get(), timeout=1.0)
|
|
assert received == {"event": "test.event", "payload": None} |