341 lines
9.6 KiB
Python
341 lines
9.6 KiB
Python
"""
|
|
Neural Sync - Event Bus Unit Tests
|
|
"""
|
|
|
|
import pytest
|
|
import asyncio
|
|
from datetime import datetime
|
|
from backend.app.events import (
|
|
EventType,
|
|
Event,
|
|
EventBus,
|
|
get_event_bus,
|
|
event_bus_context,
|
|
publish_event,
|
|
subscribe_event,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def event_bus():
|
|
"""创建事件总线 fixture"""
|
|
bus = EventBus()
|
|
await bus.start()
|
|
yield bus
|
|
await bus.stop()
|
|
|
|
|
|
@pytest.fixture
|
|
async def event_bus_context_fixture():
|
|
"""创建事件总线上下文 fixture"""
|
|
async with event_bus_context() as bus:
|
|
yield bus
|
|
|
|
|
|
class TestEvent:
|
|
"""测试 Event 数据类"""
|
|
|
|
def test_create_event(self):
|
|
"""测试创建事件"""
|
|
event = Event(
|
|
type=EventType.SIGNAL_INGESTED,
|
|
data={"signal_id": "123", "content": "Test signal"},
|
|
source="test",
|
|
)
|
|
assert event.type == EventType.SIGNAL_INGESTED
|
|
assert event.data["signal_id"] == "123"
|
|
assert event.source == "test"
|
|
assert isinstance(event.timestamp, float)
|
|
|
|
def test_event_to_dict(self):
|
|
"""测试事件转字典"""
|
|
event = Event(
|
|
type=EventType.SIGNAL_ANALYZED,
|
|
data={"score": 0.95},
|
|
correlation_id="test-123",
|
|
)
|
|
event_dict = event.to_dict()
|
|
assert event_dict["type"] == "signal.analyzed"
|
|
assert event_dict["data"]["score"] == 0.95
|
|
assert event_dict["correlation_id"] == "test-123"
|
|
|
|
|
|
class TestEventBus:
|
|
"""测试 EventBus"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_start_stop(self, event_bus):
|
|
"""测试启动和停止"""
|
|
assert event_bus._running is True
|
|
await event_bus.stop()
|
|
assert event_bus._running is False
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_subscribe_and_publish(self, event_bus):
|
|
"""测试订阅和发布"""
|
|
received_events = []
|
|
|
|
async def handler(event: Event):
|
|
received_events.append(event)
|
|
|
|
# 订阅事件
|
|
unsubscribe = event_bus.subscribe(EventType.SIGNAL_INGESTED, handler)
|
|
|
|
# 发布事件
|
|
event = Event(
|
|
type=EventType.SIGNAL_INGESTED, data={"signal_id": "123"}
|
|
)
|
|
await event_bus.publish(event)
|
|
|
|
# 等待异步处理
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert len(received_events) == 1
|
|
assert received_events[0].data["signal_id"] == "123"
|
|
|
|
# 取消订阅
|
|
unsubscribe()
|
|
|
|
# 发布另一个事件
|
|
await event_bus.publish(event)
|
|
await asyncio.sleep(0.1)
|
|
|
|
# 应该没有新事件
|
|
assert len(received_events) == 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_wildcard_subscription(self, event_bus):
|
|
"""测试通配符订阅"""
|
|
received_events = []
|
|
|
|
async def handler(event: Event):
|
|
received_events.append(event.type)
|
|
|
|
event_bus.subscribe_wildcard(handler)
|
|
|
|
# 发布不同类型的事件
|
|
await event_bus.publish(
|
|
Event(type=EventType.SIGNAL_INGESTED, data={})
|
|
)
|
|
await event_bus.publish(
|
|
Event(type=EventType.TASK_ASSIGNED, data={})
|
|
)
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert len(received_events) == 2
|
|
assert EventType.SIGNAL_INGESTED in received_events
|
|
assert EventType.TASK_ASSIGNED in received_events
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_event_filter(self, event_bus):
|
|
"""测试事件过滤"""
|
|
received_events = []
|
|
|
|
async def handler(event: Event):
|
|
received_events.append(event)
|
|
|
|
# 只接收 signal_id > 100 的事件
|
|
def filter_func(event: Event):
|
|
return event.data.get("signal_id", 0) > 100
|
|
|
|
event_bus.subscribe(
|
|
EventType.SIGNAL_INGESTED, handler, filter_func=filter_func
|
|
)
|
|
|
|
# 发布事件
|
|
await event_bus.publish(
|
|
Event(type=EventType.SIGNAL_INGESTED, data={"signal_id": 50})
|
|
)
|
|
await event_bus.publish(
|
|
Event(type=EventType.SIGNAL_INGESTED, data={"signal_id": 150})
|
|
)
|
|
await asyncio.sleep(0.1)
|
|
|
|
# 只应该收到一个事件
|
|
assert len(received_events) == 1
|
|
assert received_events[0].data["signal_id"] == 150
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_publish_and_wait(self, event_bus):
|
|
"""测试发布并等待处理完成"""
|
|
results = []
|
|
|
|
async def handler1(event: Event):
|
|
results.append("handler1")
|
|
await asyncio.sleep(0.1)
|
|
return "result1"
|
|
|
|
async def handler2(event: Event):
|
|
results.append("handler2")
|
|
return "result2"
|
|
|
|
event_bus.subscribe(EventType.SIGNAL_INGESTED, handler1)
|
|
event_bus.subscribe(EventType.SIGNAL_INGESTED, handler2)
|
|
|
|
event = Event(type=EventType.SIGNAL_INGESTED, data={})
|
|
handler_results = await event_bus.publish_and_wait(event, timeout=1.0)
|
|
|
|
assert len(results) == 2
|
|
assert len(handler_results) == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_metrics(self, event_bus):
|
|
"""测试事件指标"""
|
|
async def handler(event: Event):
|
|
pass
|
|
|
|
event_bus.subscribe(EventType.SIGNAL_INGESTED, handler)
|
|
|
|
await event_bus.publish(
|
|
Event(type=EventType.SIGNAL_INGESTED, data={})
|
|
)
|
|
await asyncio.sleep(0.1)
|
|
|
|
metrics = event_bus.metrics
|
|
assert metrics["published"] >= 1
|
|
assert metrics["delivered"] >= 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_event_history(self, event_bus):
|
|
"""测试事件历史"""
|
|
event_bus._max_history = 5
|
|
|
|
# 发布超过历史限制的事件
|
|
for i in range(10):
|
|
await event_bus.publish(
|
|
Event(type=EventType.SIGNAL_INGESTED, data={"index": i})
|
|
)
|
|
|
|
history = event_bus.history
|
|
assert len(history) == 5
|
|
# 最新的事件应该在历史中
|
|
assert history[-1]["data"]["index"] == 9
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_subscriber_count(self, event_bus):
|
|
"""测试订阅者计数"""
|
|
async def handler1(event: Event):
|
|
pass
|
|
|
|
async def handler2(event: Event):
|
|
pass
|
|
|
|
# 特定事件订阅
|
|
event_bus.subscribe(EventType.SIGNAL_INGESTED, handler1)
|
|
event_bus.subscribe(EventType.SIGNAL_INGESTED, handler2)
|
|
|
|
# 通配符订阅
|
|
event_bus.subscribe_wildcard(handler1)
|
|
|
|
assert event_bus.get_subscriber_count(EventType.SIGNAL_INGESTED) == 2
|
|
assert event_bus.get_subscriber_count() == 3 # 包括通配符订阅
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_handler_error_handling(self, event_bus):
|
|
"""测试处理器错误处理"""
|
|
received_normal = []
|
|
|
|
async def failing_handler(event: Event):
|
|
raise ValueError("Test error")
|
|
|
|
async def normal_handler(event: Event):
|
|
received_normal.append(event)
|
|
|
|
event_bus.subscribe(EventType.SIGNAL_INGESTED, failing_handler)
|
|
event_bus.subscribe(EventType.SIGNAL_INGESTED, normal_handler)
|
|
|
|
await event_bus.publish(
|
|
Event(type=EventType.SIGNAL_INGESTED, data={})
|
|
)
|
|
await asyncio.sleep(0.1)
|
|
|
|
# 正常处理器应该仍然被调用
|
|
assert len(received_normal) == 1
|
|
# 错误应该被记录
|
|
assert event_bus.metrics["failed"] >= 1
|
|
|
|
|
|
class TestGlobalEventBus:
|
|
"""测试全局事件总线"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_event_bus_singleton(self):
|
|
"""测试获取单例"""
|
|
bus1 = get_event_bus()
|
|
bus2 = get_event_bus()
|
|
assert bus1 is bus2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_convenience_functions(self):
|
|
"""测试便捷函数"""
|
|
received = []
|
|
|
|
async def handler(event: Event):
|
|
received.append(event)
|
|
|
|
# 使用便捷函数订阅
|
|
unsubscribe = subscribe_event(EventType.SIGNAL_INGESTED, handler)
|
|
|
|
# 使用便捷函数发布
|
|
await publish_event(EventType.SIGNAL_INGESTED, {"test": True})
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert len(received) == 1
|
|
|
|
# 取消订阅
|
|
unsubscribe()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_event_bus_context(self):
|
|
"""测试事件总线上下文管理器"""
|
|
async with event_bus_context() as bus:
|
|
assert bus._running is True
|
|
|
|
received = []
|
|
|
|
async def handler(event: Event):
|
|
received.append(event)
|
|
|
|
bus.subscribe(EventType.SIGNAL_INGESTED, handler)
|
|
await bus.publish(Event(type=EventType.SIGNAL_INGESTED, data={}))
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert len(received) == 1
|
|
|
|
# 退出上下文后应该停止
|
|
assert bus._running is False
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_multiple_event_types():
|
|
"""测试多种事件类型"""
|
|
bus = EventBus()
|
|
await bus.start()
|
|
|
|
try:
|
|
counters = {event_type: 0 for event_type in EventType}
|
|
|
|
async def count_handler(event: Event):
|
|
counters[event.type] += 1
|
|
|
|
# 订阅所有事件类型
|
|
for event_type in EventType:
|
|
bus.subscribe(event_type, count_handler)
|
|
|
|
# 发布各种事件
|
|
await bus.publish(Event(type=EventType.SIGNAL_INGESTED, data={}))
|
|
await bus.publish(Event(type=EventType.TASK_ASSIGNED, data={}))
|
|
await bus.publish(Event(type=EventType.CODE_COMMIT, data={}))
|
|
await asyncio.sleep(0.1)
|
|
|
|
assert counters[EventType.SIGNAL_INGESTED] == 1
|
|
assert counters[EventType.TASK_ASSIGNED] == 1
|
|
assert counters[EventType.CODE_COMMIT] == 1
|
|
|
|
finally:
|
|
await bus.stop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
pytest.main([__file__, "-v"])
|