feat: 添加测试覆盖率并修复相关问题

refactor(redis_manager): 移除冗余的ConnectionError处理
refactor(event_handler): 优化Bot类型注解
refactor(factory): 移除未使用的GroupCardNoticeEvent

test: 添加全面的单元测试覆盖
- 添加test_import.py测试模块导入
- 添加test_debug.py测试插件加载调试
- 添加test_plugin_error.py测试错误处理
- 添加test_config_loader.py测试配置加载
- 添加test_redis_manager.py测试Redis管理
- 添加test_bot.py测试Bot功能
- 扩展test_models.py测试消息模型
- 添加test_plugin_manager_coverage.py测试插件管理
- 添加test_executor.py测试代码执行器
- 添加test_ws.py测试WebSocket
- 添加test_api.py测试API接口
- 添加test_core_managers.py测试核心管理模块

fix(plugin_manager): 修复插件加载日志变量问题

覆盖率已到达86%(忽略插件)
This commit is contained in:
2026-01-09 23:18:58 +08:00
parent ec3a1c8eac
commit 8508fc95f5
17 changed files with 2057 additions and 112 deletions

250
tests/test_api.py Normal file
View File

@@ -0,0 +1,250 @@
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
import json
# Import all API classes
from core.api.base import BaseAPI
from core.api.account import AccountAPI
from core.api.friend import FriendAPI
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
)
from models.message import MessageSegment
# Fixture for a mock websocket client
@pytest.fixture
def mock_ws():
"""模拟一个 WebSocket 客户端。"""
return AsyncMock()
# Fixture for a comprehensive API client instance
@pytest.fixture
def api_client(mock_ws):
"""
创建一个包含所有 API Mixin 的测试客户端实例。
Args:
mock_ws: 模拟的 WebSocket 客户端。
Returns:
一个功能完备的 API 客户端实例。
"""
# Combine all mixins into one class for testing
class FullAPI(AccountAPI, FriendAPI, GroupAPI, MediaAPI, MessageAPI):
def __init__(self, ws_client, self_id):
super().__init__(ws_client, self_id)
return FullAPI(mock_ws, 12345)
# --- Test BaseAPI ---
@pytest.mark.asyncio
async def test_base_api_call_success(mock_ws):
"""测试 BaseAPI 成功调用。"""
base_api = BaseAPI(mock_ws, 12345)
mock_ws.call_api.return_value = {"status": "ok", "data": {"key": "value"}}
result = await base_api.call_api("test_action", {"param": 1})
mock_ws.call_api.assert_called_once_with("test_action", {"param": 1})
assert result == {"key": "value"}
@pytest.mark.asyncio
async def test_base_api_call_failed_status(mock_ws):
"""测试 BaseAPI 调用返回失败状态。"""
base_api = BaseAPI(mock_ws, 12345)
mock_ws.call_api.return_value = {"status": "failed", "data": None}
result = await base_api.call_api("test_action")
assert result is None
@pytest.mark.asyncio
async def test_base_api_call_exception(mock_ws):
"""测试 BaseAPI 调用时发生异常。"""
base_api = BaseAPI(mock_ws, 12345)
mock_ws.call_api.side_effect = Exception("Network error")
with pytest.raises(Exception, match="Network error"):
await base_api.call_api("test_action")
# --- Test AccountAPI ---
@pytest.mark.asyncio
async def test_get_login_info_no_cache(api_client):
"""测试 get_login_info 在无缓存时能正确调用 API 并设置缓存。"""
api_client.call_api = AsyncMock(return_value={"user_id": 123, "nickname": "test"})
with patch("core.managers.redis_manager.redis_manager.get", new_callable=AsyncMock) as mock_redis_get, \
patch("core.managers.redis_manager.redis_manager.set", new_callable=AsyncMock) as mock_redis_set:
mock_redis_get.return_value = None
info = await api_client.get_login_info()
api_client.call_api.assert_called_once_with("get_login_info")
mock_redis_set.assert_called_once()
assert isinstance(info, LoginInfo)
assert info.user_id == 123
@pytest.mark.asyncio
async def test_get_login_info_with_cache(api_client):
"""测试 get_login_info 在有缓存时直接返回缓存数据。"""
cached_data = json.dumps({"user_id": 123, "nickname": "test"})
api_client.call_api = AsyncMock()
with patch("core.managers.redis_manager.redis_manager.get", new_callable=AsyncMock) as mock_redis_get:
mock_redis_get.return_value = cached_data
info = await api_client.get_login_info()
api_client.call_api.assert_not_called()
assert isinstance(info, LoginInfo)
assert info.user_id == 123
@pytest.mark.asyncio
async def test_get_version_info(api_client):
"""测试 get_version_info 能正确解析 API 返回。"""
api_client.call_api = AsyncMock(return_value={"app_name": "test_app", "app_version": "1.0", "protocol_version": "v11"})
info = await api_client.get_version_info()
assert isinstance(info, VersionInfo)
assert info.app_name == "test_app"
@pytest.mark.asyncio
async def test_get_status(api_client):
"""测试 get_status 能正确解析 API 返回。"""
api_client.call_api = AsyncMock(return_value={"online": True, "good": True})
status = await api_client.get_status()
assert isinstance(status, Status)
assert status.online is True
# --- Test FriendAPI ---
@pytest.mark.asyncio
async def test_send_like(api_client):
"""测试 send_like 方法能正确调用 API。"""
api_client.call_api = AsyncMock()
await api_client.send_like(54321, 5)
api_client.call_api.assert_called_once_with("send_like", {"user_id": 54321, "times": 5})
@pytest.mark.asyncio
async def test_set_friend_add_request(api_client):
"""测试 set_friend_add_request 方法能正确调用 API。"""
api_client.call_api = AsyncMock()
await api_client.set_friend_add_request("flag_test", approve=False)
api_client.call_api.assert_called_once_with("set_friend_add_request", {"flag": "flag_test", "approve": False, "remark": ""})
# --- Test GroupAPI ---
@pytest.mark.asyncio
async def test_set_group_kick(api_client):
"""测试 set_group_kick 方法能正确调用 API。"""
api_client.call_api = AsyncMock()
await api_client.set_group_kick(111, 222, True)
api_client.call_api.assert_called_once_with("set_group_kick", {"group_id": 111, "user_id": 222, "reject_add_request": True})
@pytest.mark.asyncio
async def test_set_group_anonymous_ban(api_client):
"""测试 set_group_anonymous_ban 方法能正确调用 API。"""
api_client.call_api = AsyncMock()
await api_client.set_group_anonymous_ban(111, flag="anon_flag")
api_client.call_api.assert_called_once_with("set_group_anonymous_ban", {"group_id": 111, "duration": 1800, "flag": "anon_flag"})
# --- Test MediaAPI ---
@pytest.mark.asyncio
async def test_can_send_image(api_client):
"""测试 can_send_image 方法能正确调用 API。"""
api_client.call_api = AsyncMock()
await api_client.can_send_image()
api_client.call_api.assert_called_once_with(action="can_send_image")
@pytest.mark.asyncio
async def test_get_image(api_client):
"""测试 get_image 方法能正确调用 API。"""
api_client.call_api = AsyncMock()
await api_client.get_image("file.jpg")
api_client.call_api.assert_called_once_with(action="get_image", params={"file": "file.jpg"})
# --- Test MessageAPI ---
@pytest.mark.asyncio
async def test_send_group_msg_str(api_client):
"""测试 send_group_msg 发送字符串消息。"""
api_client.call_api = AsyncMock()
await api_client.send_group_msg(111, "hello")
api_client.call_api.assert_called_once_with("send_group_msg", {"group_id": 111, "message": "hello", "auto_escape": False})
@pytest.mark.asyncio
async def test_send_group_msg_segment(api_client):
"""测试 send_group_msg 发送单个消息段。"""
api_client.call_api = AsyncMock()
segment = MessageSegment.text("hello")
await api_client.send_group_msg(111, segment)
api_client.call_api.assert_called_once_with("send_group_msg", {"group_id": 111, "message": [{"type": "text", "data": {"text": "hello"}}], "auto_escape": False})
@pytest.mark.asyncio
async def test_send_group_msg_list_segments(api_client):
"""测试 send_group_msg 发送消息段列表。"""
api_client.call_api = AsyncMock()
segments = [MessageSegment.text("hello"), MessageSegment.image("file.jpg")]
await api_client.send_group_msg(111, segments)
api_client.call_api.assert_called_once_with("send_group_msg", {"group_id": 111, "message": [
{"type": "text", "data": {"text": "hello"}},
{"type": "image", "data": {"file": "file.jpg", "cache": "1", "proxy": "1"}}
], "auto_escape": False})
@pytest.mark.asyncio
async def test_send_reply(api_client):
"""测试 send 方法在事件有 reply 方法时优先调用 reply。"""
mock_event = MagicMock()
mock_event.reply = AsyncMock()
# 确保没有 user_id 和 group_id以验证 reply 路径被优先选择
delattr(mock_event, "user_id")
delattr(mock_event, "group_id")
await api_client.send(mock_event, "hello reply")
mock_event.reply.assert_called_once_with("hello reply", False)
@pytest.mark.asyncio
async def test_send_auto_private(api_client):
"""测试 send 方法能根据事件自动判断并发送私聊消息。"""
mock_event = MagicMock()
mock_event.user_id = 123
delattr(mock_event, "group_id") # 确保没有 group_id
delattr(mock_event, "reply") # 确保没有 reply 方法
api_client.send_private_msg = AsyncMock()
await api_client.send(mock_event, "hello private")
api_client.send_private_msg.assert_called_once_with(123, "hello private", False)
@pytest.mark.asyncio
async def test_send_auto_group(api_client):
"""测试 send 方法能根据事件自动判断并发送群聊消息。"""
mock_event = MagicMock()
mock_event.user_id = 123
mock_event.group_id = 456
delattr(mock_event, "reply")
api_client.send_group_msg = AsyncMock()
await api_client.send(mock_event, "hello group")
api_client.send_group_msg.assert_called_once_with(456, "hello group", False)
@pytest.mark.asyncio
async def test_get_forward_msg_valid(api_client):
"""测试 get_forward_msg 能正确解析有效的合并转发消息。"""
api_client.call_api = AsyncMock(return_value={"data": [{"content": "node1"}]})
nodes = await api_client.get_forward_msg("forward_id")
assert nodes == [{"content": "node1"}]
@pytest.mark.asyncio
async def test_get_forward_msg_nested(api_client):
"""测试 get_forward_msg 能正确解析嵌套在 'messages' 键下的消息。"""
api_client.call_api = AsyncMock(return_value={"data": {"messages": [{"content": "node2"}]}})
nodes = await api_client.get_forward_msg("forward_id_nested")
assert nodes == [{"content": "node2"}]
@pytest.mark.asyncio
async def test_get_forward_msg_invalid(api_client):
"""测试 get_forward_msg 在无效数据结构下抛出异常。"""
api_client.call_api = AsyncMock(return_value={"data": "not a list or dict"})
with pytest.raises(ValueError):
await api_client.get_forward_msg("forward_id_invalid")