2026-02-04 18:49:38 +08:00
|
|
|
"""Gateway 模块测试"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
from minenasai.gateway.protocol import (
|
|
|
|
|
ChatMessage,
|
|
|
|
|
MessageType,
|
|
|
|
|
TaskComplexity,
|
|
|
|
|
parse_message,
|
|
|
|
|
)
|
|
|
|
|
from minenasai.gateway.router import SmartRouter
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestProtocol:
|
|
|
|
|
"""协议测试"""
|
|
|
|
|
|
|
|
|
|
def test_chat_message(self):
|
|
|
|
|
"""测试聊天消息"""
|
|
|
|
|
msg = ChatMessage(content="你好")
|
|
|
|
|
|
|
|
|
|
assert msg.type == MessageType.CHAT
|
|
|
|
|
assert msg.content == "你好"
|
|
|
|
|
assert msg.id is not None
|
|
|
|
|
|
|
|
|
|
def test_parse_message(self):
|
|
|
|
|
"""测试消息解析"""
|
|
|
|
|
data = {
|
|
|
|
|
"type": "chat",
|
|
|
|
|
"content": "测试消息",
|
|
|
|
|
}
|
|
|
|
|
msg = parse_message(data)
|
|
|
|
|
|
|
|
|
|
assert isinstance(msg, ChatMessage)
|
|
|
|
|
assert msg.content == "测试消息"
|
|
|
|
|
|
|
|
|
|
def test_parse_invalid_type(self):
|
|
|
|
|
"""测试无效消息类型"""
|
|
|
|
|
data = {"type": "invalid"}
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match="未知的消息类型"):
|
|
|
|
|
parse_message(data)
|
|
|
|
|
|
|
|
|
|
def test_parse_missing_type(self):
|
|
|
|
|
"""测试缺少类型"""
|
|
|
|
|
data = {"content": "test"}
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match="缺少 type"):
|
|
|
|
|
parse_message(data)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRouter:
|
|
|
|
|
"""智能路由测试"""
|
|
|
|
|
|
|
|
|
|
def setup_method(self):
|
|
|
|
|
"""初始化路由器"""
|
|
|
|
|
self.router = SmartRouter()
|
|
|
|
|
|
|
|
|
|
def test_simple_question(self):
|
|
|
|
|
"""测试简单问题"""
|
|
|
|
|
result = self.router.evaluate("今天天气怎么样?")
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] == TaskComplexity.SIMPLE
|
|
|
|
|
assert result["suggested_handler"] == "quick_response"
|
|
|
|
|
|
|
|
|
|
def test_complex_task(self):
|
|
|
|
|
"""测试复杂任务"""
|
|
|
|
|
# 包含多个复杂关键词:重构、实现、优化
|
|
|
|
|
result = self.router.evaluate(
|
|
|
|
|
"请帮我重构这个项目的数据库模块,实现异步操作支持,"
|
|
|
|
|
"优化连接池管理,同时要保持向后兼容。这是一个架构设计任务。"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 复杂任务应该识别为 COMPLEX 或 MEDIUM
|
|
|
|
|
assert result["complexity"] in [TaskComplexity.COMPLEX, TaskComplexity.MEDIUM]
|
|
|
|
|
|
|
|
|
|
def test_medium_task(self):
|
|
|
|
|
"""测试中等任务"""
|
|
|
|
|
result = self.router.evaluate("查看当前目录下的文件列表")
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] in [TaskComplexity.SIMPLE, TaskComplexity.MEDIUM]
|
|
|
|
|
|
|
|
|
|
def test_command_override_simple(self):
|
|
|
|
|
"""测试命令覆盖 - 简单"""
|
|
|
|
|
result = self.router.evaluate("/快速 帮我重构整个项目")
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] == TaskComplexity.SIMPLE
|
|
|
|
|
assert result["confidence"] == 1.0
|
|
|
|
|
assert result["content"] == "帮我重构整个项目"
|
|
|
|
|
|
|
|
|
|
def test_command_override_complex(self):
|
|
|
|
|
"""测试命令覆盖 - 复杂"""
|
|
|
|
|
result = self.router.evaluate("/深度 你好")
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] == TaskComplexity.COMPLEX
|
|
|
|
|
assert result["confidence"] == 1.0
|
|
|
|
|
|
|
|
|
|
def test_code_detection(self):
|
|
|
|
|
"""测试代码检测"""
|
|
|
|
|
result = self.router.evaluate(
|
|
|
|
|
"请帮我实现这个函数:\n```python\ndef hello():\n pass\n```"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] == TaskComplexity.COMPLEX
|
|
|
|
|
|
|
|
|
|
def test_multi_step_detection(self):
|
|
|
|
|
"""测试多步骤检测"""
|
|
|
|
|
# 使用英文 step 模式和更多内容
|
|
|
|
|
result = self.router.evaluate(
|
|
|
|
|
"Step 1: 创建数据库表结构\n"
|
|
|
|
|
"Step 2: 实现数据导入功能\n"
|
|
|
|
|
"Step 3: 开发验证脚本\n"
|
|
|
|
|
"Step 4: 部署到生产环境"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# 多步骤任务应该识别为复杂任务
|
|
|
|
|
assert result["complexity"] in [TaskComplexity.COMPLEX, TaskComplexity.MEDIUM]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRouterEdgeCases:
|
|
|
|
|
"""路由器边界情况测试"""
|
|
|
|
|
|
|
|
|
|
def setup_method(self):
|
|
|
|
|
self.router = SmartRouter()
|
|
|
|
|
|
|
|
|
|
def test_empty_content(self):
|
|
|
|
|
"""测试空内容"""
|
|
|
|
|
result = self.router.evaluate("")
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] == TaskComplexity.SIMPLE
|
|
|
|
|
|
|
|
|
|
def test_very_long_content(self):
|
|
|
|
|
"""测试超长内容"""
|
|
|
|
|
long_content = "请帮我分析 " + "这段代码 " * 200
|
|
|
|
|
result = self.router.evaluate(long_content)
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] == TaskComplexity.COMPLEX
|
|
|
|
|
|
|
|
|
|
def test_special_characters(self):
|
|
|
|
|
"""测试特殊字符"""
|
|
|
|
|
result = self.router.evaluate("查看 /tmp/test.txt 文件内容")
|
|
|
|
|
|
|
|
|
|
assert result["complexity"] in [TaskComplexity.SIMPLE, TaskComplexity.MEDIUM]
|
2026-02-05 15:43:08 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestConnectionManager:
|
|
|
|
|
"""WebSocket 连接管理器测试"""
|
|
|
|
|
|
|
|
|
|
def test_import_manager(self):
|
|
|
|
|
"""测试导入连接管理器"""
|
|
|
|
|
from minenasai.gateway.server import ConnectionManager
|
|
|
|
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
assert manager.active_connections == {}
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_connect_and_disconnect(self):
|
|
|
|
|
"""测试连接和断开"""
|
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
|
|
|
|
|
|
from minenasai.gateway.server import ConnectionManager
|
|
|
|
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
|
|
|
|
|
# Mock WebSocket
|
|
|
|
|
mock_ws = AsyncMock()
|
|
|
|
|
mock_ws.accept = AsyncMock()
|
|
|
|
|
|
|
|
|
|
# 连接
|
|
|
|
|
await manager.connect(mock_ws, "client-1")
|
|
|
|
|
assert "client-1" in manager.active_connections
|
|
|
|
|
mock_ws.accept.assert_called_once()
|
|
|
|
|
|
|
|
|
|
# 断开
|
|
|
|
|
manager.disconnect("client-1")
|
|
|
|
|
assert "client-1" not in manager.active_connections
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_disconnect_nonexistent(self):
|
|
|
|
|
"""测试断开不存在的连接"""
|
|
|
|
|
from minenasai.gateway.server import ConnectionManager
|
|
|
|
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
# 不应该抛出异常
|
|
|
|
|
manager.disconnect("nonexistent")
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_send_message(self):
|
|
|
|
|
"""测试发送消息"""
|
|
|
|
|
from unittest.mock import AsyncMock
|
|
|
|
|
|
|
|
|
|
from minenasai.gateway.server import ConnectionManager
|
|
|
|
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
|
|
|
|
|
# Mock WebSocket
|
|
|
|
|
mock_ws = AsyncMock()
|
|
|
|
|
mock_ws.accept = AsyncMock()
|
|
|
|
|
mock_ws.send_json = AsyncMock()
|
|
|
|
|
|
|
|
|
|
# 连接
|
|
|
|
|
await manager.connect(mock_ws, "client-1")
|
|
|
|
|
|
|
|
|
|
# 发送消息
|
|
|
|
|
await manager.send_message("client-1", {"type": "test"})
|
|
|
|
|
mock_ws.send_json.assert_called_once_with({"type": "test"})
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_send_message_to_nonexistent(self):
|
|
|
|
|
"""测试发送消息给不存在的客户端"""
|
|
|
|
|
from minenasai.gateway.server import ConnectionManager
|
|
|
|
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
# 不应该抛出异常
|
|
|
|
|
await manager.send_message("nonexistent", {"type": "test"})
|
|
|
|
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
|
|
|
async def test_broadcast(self):
|
|
|
|
|
"""测试广播消息"""
|
|
|
|
|
from unittest.mock import AsyncMock
|
|
|
|
|
|
|
|
|
|
from minenasai.gateway.server import ConnectionManager
|
|
|
|
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
|
|
|
|
|
# Mock 多个 WebSocket
|
|
|
|
|
mock_ws1 = AsyncMock()
|
|
|
|
|
mock_ws1.accept = AsyncMock()
|
|
|
|
|
mock_ws1.send_json = AsyncMock()
|
|
|
|
|
|
|
|
|
|
mock_ws2 = AsyncMock()
|
|
|
|
|
mock_ws2.accept = AsyncMock()
|
|
|
|
|
mock_ws2.send_json = AsyncMock()
|
|
|
|
|
|
|
|
|
|
# 连接
|
|
|
|
|
await manager.connect(mock_ws1, "client-1")
|
|
|
|
|
await manager.connect(mock_ws2, "client-2")
|
|
|
|
|
|
|
|
|
|
# 广播
|
|
|
|
|
await manager.broadcast({"type": "broadcast"})
|
|
|
|
|
mock_ws1.send_json.assert_called_once_with({"type": "broadcast"})
|
|
|
|
|
mock_ws2.send_json.assert_called_once_with({"type": "broadcast"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestGatewayServer:
|
|
|
|
|
"""Gateway 服务器测试"""
|
|
|
|
|
|
|
|
|
|
def test_import_app(self):
|
|
|
|
|
"""测试导入应用"""
|
|
|
|
|
from minenasai.gateway.server import app
|
|
|
|
|
|
|
|
|
|
assert app is not None
|
|
|
|
|
assert app.title == "MineNASAI Gateway"
|
|
|
|
|
|
|
|
|
|
def test_import_endpoints(self):
|
|
|
|
|
"""测试导入端点函数"""
|
|
|
|
|
from minenasai.gateway.server import list_agents, list_sessions, root
|
|
|
|
|
|
|
|
|
|
assert callable(root)
|
|
|
|
|
assert callable(list_agents)
|
|
|
|
|
assert callable(list_sessions)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestMessageTypes:
|
|
|
|
|
"""消息类型测试"""
|
|
|
|
|
|
|
|
|
|
def test_status_message(self):
|
|
|
|
|
"""测试状态消息"""
|
|
|
|
|
from minenasai.gateway.protocol import StatusMessage
|
|
|
|
|
|
|
|
|
|
msg = StatusMessage(status="thinking", message="处理中...")
|
|
|
|
|
assert msg.type == MessageType.STATUS
|
|
|
|
|
assert msg.status == "thinking"
|
|
|
|
|
assert msg.message == "处理中..."
|
|
|
|
|
|
|
|
|
|
def test_response_message(self):
|
|
|
|
|
"""测试响应消息"""
|
|
|
|
|
from minenasai.gateway.protocol import ResponseMessage
|
|
|
|
|
|
|
|
|
|
msg = ResponseMessage(content="Hello!", in_reply_to="msg-123")
|
|
|
|
|
assert msg.type == MessageType.RESPONSE
|
|
|
|
|
assert msg.content == "Hello!"
|
|
|
|
|
assert msg.in_reply_to == "msg-123"
|
|
|
|
|
|
|
|
|
|
def test_error_message(self):
|
|
|
|
|
"""测试错误消息"""
|
|
|
|
|
from minenasai.gateway.protocol import ErrorMessage
|
|
|
|
|
|
|
|
|
|
msg = ErrorMessage(message="Something went wrong", code="ERR_001")
|
|
|
|
|
assert msg.type == MessageType.ERROR
|
|
|
|
|
assert msg.message == "Something went wrong"
|
|
|
|
|
assert msg.code == "ERR_001"
|
|
|
|
|
|
|
|
|
|
def test_pong_message(self):
|
|
|
|
|
"""测试心跳响应消息"""
|
|
|
|
|
from minenasai.gateway.protocol import PongMessage
|
|
|
|
|
|
|
|
|
|
msg = PongMessage()
|
|
|
|
|
assert msg.type == MessageType.PONG
|