架构重构(候选 1-6): - storage/repository.py 按域拆分为包(target/scenario/run/campaign/result) - storage/db.py 按域拆分为包(eval/campaign/file/model_config/intelligent_eval) - intelligent_eval/lifecycle.py 按状态机阶段拆分为包 - services/runs.py 编排逻辑下沉 - Campaigns.tsx 拆分为 campaigns/ 子组件 测试补全(候选 7): 前端(+125 用例,107→232): - utils/ 纯函数:date/campaignTime/ruleLabels/fileTree/fileFormat/colors - stores/tabStore 状态管理 - 核心组件:FormDrawer/PageWrapper/ChatBubble/GeneratedMessages/SectionHeader/StatCard/TurnList - 业务组件:CaseBlock/CaseDetail/RuleOverview/WindowTimeline/RunList/TabBar/CampaignRunTimeline - 文件管理:FileCategoryTree/FileTable - hooks:sessionReducer/useFiles/useRunSession 后端(+38 用例,916→954): - targets API CRUD + 404 路径 - WebSocket 连接管理器 - proxy 头部重写(CSP/X-Frame-Options) - target 仓储 update 方法 - app 健康检查 + SPA 404 - scenarios 模板端点 + 404 - files API 边缘分支(404 场景 + 500 兜底) - files service update_category - 智能评估状态机迁移测试 门禁状态: - 前端:tsc 干净 + 232 passed - 后端:954 passed + ruff 全绿
76 lines
2.1 KiB
Python
76 lines
2.1 KiB
Python
"""Unit tests for the WebSocket connection manager."""
|
|
|
|
import json
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
import pytest
|
|
from agenteval.web.websocket import ConnectionManager
|
|
|
|
|
|
@pytest.fixture()
|
|
def manager():
|
|
return ConnectionManager()
|
|
|
|
|
|
async def test_connect_adds_websocket(manager):
|
|
ws = AsyncMock()
|
|
await manager.connect("run-1", ws)
|
|
ws.accept.assert_called_once()
|
|
assert ws in manager._connections["run-1"]
|
|
|
|
|
|
async def test_disconnect_removes_websocket(manager):
|
|
ws = AsyncMock()
|
|
await manager.connect("run-1", ws)
|
|
manager.disconnect("run-1", ws)
|
|
assert "run-1" not in manager._connections
|
|
|
|
|
|
async def test_disconnect_cleans_empty_group(manager):
|
|
ws1 = AsyncMock()
|
|
ws2 = AsyncMock()
|
|
await manager.connect("run-1", ws1)
|
|
await manager.connect("run-1", ws2)
|
|
manager.disconnect("run-1", ws1)
|
|
assert ws2 in manager._connections["run-1"]
|
|
manager.disconnect("run-1", ws2)
|
|
assert "run-1" not in manager._connections
|
|
|
|
|
|
async def test_broadcast_sends_to_all_connections(manager):
|
|
ws1 = AsyncMock()
|
|
ws2 = AsyncMock()
|
|
await manager.connect("run-1", ws1)
|
|
await manager.connect("run-1", ws2)
|
|
|
|
await manager.broadcast("run-1", {"event": "test", "data": "value"})
|
|
|
|
ws1.send_text.assert_called_once()
|
|
ws2.send_text.assert_called_once()
|
|
msg = json.loads(ws1.send_text.call_args[0][0])
|
|
assert msg["event"] == "test"
|
|
|
|
|
|
async def test_broadcast_removes_dead_connections(manager):
|
|
ws1 = AsyncMock()
|
|
ws1.send_text.side_effect = Exception("connection closed")
|
|
ws2 = AsyncMock()
|
|
await manager.connect("run-1", ws1)
|
|
await manager.connect("run-1", ws2)
|
|
|
|
await manager.broadcast("run-1", {"event": "test"})
|
|
|
|
assert ws1 not in manager._connections["run-1"]
|
|
assert ws2 in manager._connections["run-1"]
|
|
|
|
|
|
async def test_emit_wraps_event(manager):
|
|
ws = AsyncMock()
|
|
await manager.connect("run-1", ws)
|
|
|
|
await manager.emit("run-1", "case_start", {"case_id": "c1"})
|
|
|
|
msg = json.loads(ws.send_text.call_args[0][0])
|
|
assert msg["event"] == "case_start"
|
|
assert msg["case_id"] == "c1"
|