refactor(managers): 重构单例管理器实现并优化代码结构
feat(ws_pool): 新增 WebSocket 连接池实现 perf(json): 使用 orjson 替代标准 json 库提升性能 style: 清理未使用的导入和冗余代码 docs: 更新架构文档和开发规范 test: 添加 WebSocket 连接池测试用例 fix(plugins): 修复自动审批插件 API 调用参数格式
This commit is contained in:
@@ -10,8 +10,7 @@ from core.api.group import GroupAPI
|
||||
from core.api.media import MediaAPI
|
||||
from core.api.message import MessageAPI
|
||||
from models.objects import (
|
||||
LoginInfo, VersionInfo, Status, StrangerInfo, FriendInfo,
|
||||
GroupInfo, GroupMemberInfo, GroupHonorInfo
|
||||
LoginInfo, VersionInfo, Status
|
||||
)
|
||||
from models.message import MessageSegment
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import pytest
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
from core.managers.command_manager import CommandManager
|
||||
from models.events.message import GroupMessageEvent
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import pytest
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
from core.config_loader import Config
|
||||
from core.config_models import ConfigModel, NapCatWSModel, BotModel, RedisModel, DockerModel
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import asyncio
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch, AsyncMock
|
||||
import docker
|
||||
from core.utils.executor import CodeExecutor, initialize_executor
|
||||
from core.utils.executor import CodeExecutor
|
||||
|
||||
# Mock 配置对象
|
||||
@pytest.fixture
|
||||
@@ -136,8 +136,10 @@ async def test_worker_docker_errors(executor):
|
||||
await executor.task_queue.join()
|
||||
callback.assert_called_with(f"执行失败:沙箱基础镜像 '{executor.sandbox_image}' 不存在,请联系管理员构建。")
|
||||
worker_task.cancel()
|
||||
try: await worker_task
|
||||
except: pass
|
||||
try:
|
||||
await worker_task
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ContainerError
|
||||
executor._run_in_container = MagicMock(side_effect=docker.errors.ContainerError(
|
||||
@@ -150,8 +152,10 @@ async def test_worker_docker_errors(executor):
|
||||
await executor.task_queue.join()
|
||||
callback.assert_called_with("代码执行出错:\nError output")
|
||||
worker_task.cancel()
|
||||
try: await worker_task
|
||||
except: pass
|
||||
try:
|
||||
await worker_task
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def test_run_in_container_success(executor):
|
||||
"""测试 _run_in_container 成功"""
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import pytest
|
||||
from models.message import MessageSegment
|
||||
from models.objects import GroupInfo, StrangerInfo
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import asyncio
|
||||
import time
|
||||
import pytest
|
||||
from typing import Optional
|
||||
|
||||
# 导入性能分析工具
|
||||
from core.utils.performance import (
|
||||
@@ -66,7 +65,6 @@ class TestProfileContextManager:
|
||||
"""测试同步代码的性能分析"""
|
||||
# 捕获标准输出
|
||||
import io
|
||||
import sys
|
||||
from contextlib import redirect_stdout
|
||||
|
||||
f = io.StringIO()
|
||||
@@ -254,7 +252,7 @@ if __name__ == "__main__":
|
||||
async_result = asyncio.run(test_async())
|
||||
slow_result = slow_func()
|
||||
|
||||
print(f"\n测试结果:")
|
||||
print("\n测试结果:")
|
||||
print(f"sync_result: {sync_result}")
|
||||
print(f"async_result: {async_result}")
|
||||
print(f"slow_result: {slow_result}")
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import sys
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
import core.managers.plugin_manager as pm_module
|
||||
from core.managers.plugin_manager import PluginManager
|
||||
from core.managers.command_manager import CommandManager
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch, AsyncMock
|
||||
from unittest.mock import patch, AsyncMock
|
||||
from core.managers.redis_manager import RedisManager
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import pytest
|
||||
import asyncio
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from core.ws import WS
|
||||
from core.bot import Bot
|
||||
from models.objects import GroupInfo, StrangerInfo
|
||||
|
||||
|
||||
class TestWS:
|
||||
@@ -38,7 +36,7 @@ class TestWS:
|
||||
# 测试 WebSocket 未初始化的情况
|
||||
result = await ws.call_api("send_group_msg", {"group_id": 123456, "message": "test"})
|
||||
assert result["code"] == 2002 # WS_DISCONNECTED
|
||||
assert result["success"] == False
|
||||
assert not result["success"]
|
||||
assert "WebSocket未初始化" in result["message"]
|
||||
|
||||
# 测试 WebSocket 已初始化但未连接的情况
|
||||
@@ -47,7 +45,7 @@ class TestWS:
|
||||
ws.ws = mock_ws
|
||||
result = await ws.call_api("send_group_msg", {"group_id": 123456, "message": "test"})
|
||||
assert result["code"] == 2002 # WS_DISCONNECTED
|
||||
assert result["success"] == False
|
||||
assert not result["success"]
|
||||
assert "WebSocket连接未打开" in result["message"]
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
234
tests/test_ws_pool.py
Normal file
234
tests/test_ws_pool.py
Normal file
@@ -0,0 +1,234 @@
|
||||
"""
|
||||
WebSocket 连接池测试模块
|
||||
|
||||
该模块包含对 WebSocket 连接池的单元测试和集成测试。
|
||||
"""
|
||||
import pytest
|
||||
import asyncio
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
|
||||
from core.ws_pool import WSConnection, WSConnectionPool
|
||||
from core.utils.exceptions import WebSocketError, WebSocketConnectionError
|
||||
|
||||
|
||||
class TestWSConnection:
|
||||
"""
|
||||
WebSocket 连接包装类测试
|
||||
"""
|
||||
def test_connection_initialization(self):
|
||||
"""测试连接初始化"""
|
||||
mock_conn = Mock()
|
||||
conn_id = "test-connection-id"
|
||||
|
||||
conn = WSConnection(mock_conn, conn_id)
|
||||
|
||||
assert conn.conn == mock_conn
|
||||
assert conn.conn_id == conn_id
|
||||
assert conn.is_active
|
||||
assert conn._pending_requests == {}
|
||||
assert isinstance(conn.last_used, float)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_data(self):
|
||||
"""测试发送数据"""
|
||||
mock_conn = Mock()
|
||||
mock_conn.send = Mock(return_value=asyncio.coroutine(lambda x: None)())
|
||||
|
||||
conn = WSConnection(mock_conn, "test-id")
|
||||
data = {"action": "test", "params": {}}
|
||||
|
||||
await conn.send(data)
|
||||
mock_conn.send.assert_called_once()
|
||||
assert conn.last_used > 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_data_inactive_connection(self):
|
||||
"""测试向已关闭的连接发送数据"""
|
||||
mock_conn = Mock()
|
||||
conn = WSConnection(mock_conn, "test-id")
|
||||
conn.is_active = False
|
||||
|
||||
with pytest.raises(WebSocketError):
|
||||
await conn.send({"action": "test"})
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_recv_data(self):
|
||||
"""测试接收数据"""
|
||||
mock_conn = Mock()
|
||||
mock_conn.recv = Mock(return_value=asyncio.coroutine(lambda: "test-data")())
|
||||
|
||||
conn = WSConnection(mock_conn, "test-id")
|
||||
result = await conn.recv()
|
||||
|
||||
assert result == "test-data"
|
||||
mock_conn.recv.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_close_connection(self):
|
||||
"""测试关闭连接"""
|
||||
mock_conn = Mock()
|
||||
mock_conn.close = Mock(return_value=asyncio.coroutine(lambda: None)())
|
||||
|
||||
conn = WSConnection(mock_conn, "test-id")
|
||||
await conn.close()
|
||||
|
||||
assert not conn.is_active
|
||||
mock_conn.close.assert_called_once()
|
||||
|
||||
|
||||
class TestWSConnectionPool:
|
||||
"""
|
||||
WebSocket 连接池测试
|
||||
"""
|
||||
@pytest.mark.asyncio
|
||||
async def test_pool_initialization(self):
|
||||
"""测试连接池初始化"""
|
||||
pool = WSConnectionPool(pool_size=2, max_idle_time=300)
|
||||
assert pool.pool_size == 2
|
||||
assert pool.max_idle_time == 300
|
||||
assert not pool._closed
|
||||
assert pool.pool is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_create_connection(self, mock_connect):
|
||||
"""测试创建新连接"""
|
||||
mock_websocket = Mock()
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=1)
|
||||
conn = await pool._create_connection()
|
||||
|
||||
assert isinstance(conn, WSConnection)
|
||||
assert conn.is_active
|
||||
mock_connect.assert_called_once()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_pool_initialize(self, mock_connect):
|
||||
"""测试连接池初始化"""
|
||||
mock_websocket = Mock()
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=2)
|
||||
await pool.initialize()
|
||||
|
||||
assert pool.pool.qsize() == 2
|
||||
mock_connect.assert_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_get_connection(self, mock_connect):
|
||||
"""测试从连接池获取连接"""
|
||||
mock_websocket = Mock()
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=1)
|
||||
await pool.initialize()
|
||||
|
||||
conn = await pool.get_connection()
|
||||
assert isinstance(conn, WSConnection)
|
||||
assert conn.is_active
|
||||
assert pool.pool.qsize() == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_release_connection(self, mock_connect):
|
||||
"""测试释放连接回连接池"""
|
||||
mock_websocket = Mock()
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=1)
|
||||
await pool.initialize()
|
||||
|
||||
conn = await pool.get_connection()
|
||||
await pool.release_connection(conn)
|
||||
|
||||
assert pool.pool.qsize() == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_release_inactive_connection(self, mock_connect):
|
||||
"""测试释放已关闭的连接"""
|
||||
mock_websocket = Mock()
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=1)
|
||||
await pool.initialize()
|
||||
|
||||
conn = await pool.get_connection()
|
||||
conn.is_active = False
|
||||
|
||||
await pool.release_connection(conn)
|
||||
assert pool.pool.qsize() == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_cleanup_idle_connections(self, mock_connect):
|
||||
"""测试清理空闲连接"""
|
||||
mock_websocket = Mock()
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=2, max_idle_time=0.1)
|
||||
await pool.initialize()
|
||||
|
||||
# 等待清理任务执行
|
||||
await asyncio.sleep(0.2)
|
||||
|
||||
# 检查连接池是否为空
|
||||
assert pool.pool.qsize() == 0
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_pool_close(self, mock_connect):
|
||||
"""测试关闭连接池"""
|
||||
mock_websocket = Mock()
|
||||
mock_websocket.close = Mock(return_value=asyncio.coroutine(lambda: None)())
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=2)
|
||||
await pool.initialize()
|
||||
|
||||
await pool.close()
|
||||
|
||||
assert pool._closed
|
||||
assert pool.pool.qsize() == 0
|
||||
mock_websocket.close.assert_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_connection_from_closed_pool(self):
|
||||
"""测试从已关闭的连接池获取连接"""
|
||||
pool = WSConnectionPool(pool_size=1)
|
||||
pool._closed = True
|
||||
|
||||
with pytest.raises(WebSocketError):
|
||||
await pool.get_connection()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@patch('websockets.connect')
|
||||
async def test_pool_with_max_size(self, mock_connect):
|
||||
"""测试连接池大小限制"""
|
||||
mock_websocket = Mock()
|
||||
mock_connect.return_value = asyncio.coroutine(lambda: mock_websocket)()
|
||||
|
||||
pool = WSConnectionPool(pool_size=2)
|
||||
await pool.initialize()
|
||||
|
||||
# 获取两个连接
|
||||
conn1 = await pool.get_connection()
|
||||
conn2 = await pool.get_connection()
|
||||
|
||||
# 第三个连接会创建临时连接
|
||||
conn3 = await pool.get_connection()
|
||||
|
||||
# 释放所有连接
|
||||
await pool.release_connection(conn1)
|
||||
await pool.release_connection(conn2)
|
||||
await pool.release_connection(conn3)
|
||||
|
||||
# 连接池应保持最大大小
|
||||
assert pool.pool.qsize() == 2
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__])
|
||||
Reference in New Issue
Block a user