Fix/test_runwork (#2001)
Some checks are pending
Sequential Lint and Type Check / ruff-call (push) Waiting to run
Sequential Lint and Type Check / pyright-call (push) Blocked by required conditions

* fix(test): 修复测试工作流

- 修改自动更新模块中的导入路径
- 更新插件商店模块中的插件信息获取逻辑
- 优化插件添加、更新和移除流程
- 统一插件相关错误信息的格式
- 调整测试用例以适应新的插件管理逻辑

* test(builtin_plugins): 重构插件商店相关测试

- 移除 jsd 相关测试用例,只保留 gh(GitHub)的测试
- 删除了 test_plugin_store.py 文件,清理了插件商店的测试
- 更新了 test_search_plugin.py 中的插件版本号
- 调整了 test_update_plugin.py 中的已加载插件版本
- 移除了 StoreManager 类中的 is_external 变量
- 更新了 RepoFileManager 类中的文件获取逻辑,优先使用 GitHub
This commit is contained in:
molanp 2025-08-03 18:59:42 +08:00 committed by GitHub
parent 854eac68d8
commit 6d829bcb02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 58 additions and 200 deletions

View File

@ -207,7 +207,7 @@ def init_mocker_path(mocker: MockerFixture, tmp_path: Path):
)
mocker.patch(
"zhenxun.builtin_plugins.auto_update._data_source.install_requirement",
"zhenxun.utils.manager.virtual_env_package_manager.VirtualEnvPackageManager.install_requirement",
return_value=None,
)
mock_tmp_path = mocker.patch(

View File

@ -15,7 +15,7 @@ from tests.config import BotId, GroupId, MessageId, UserId
from tests.utils import _v11_group_message_event
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("package_api", ["gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_basic(
package_api: str,
@ -37,18 +37,14 @@ async def test_add_plugin_basic(
new=tmp_path / "zhenxun",
)
if package_api != "jsd":
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if package_api != "gh":
mocked_api["zhenxun_bot_plugins_tree"].respond(404)
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if not is_commit:
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
mocked_api["zhenxun_bot_plugins_commit_proxy"].respond(404)
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
plugin_id = 1
plugin_id = "search_image"
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@ -65,7 +61,7 @@ async def test_add_plugin_basic(
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
result=None,
bot=bot,
)
@ -86,7 +82,7 @@ async def test_add_plugin_basic(
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("package_api", ["gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_basic_commit_version(
package_api: str,
@ -108,17 +104,13 @@ async def test_add_plugin_basic_commit_version(
new=tmp_path / "zhenxun",
)
if package_api != "jsd":
mocked_api["zhenxun_bot_plugins_metadata_commit"].respond(404)
if package_api != "gh":
mocked_api["zhenxun_bot_plugins_tree_commit"].respond(404)
mocked_api["zhenxun_bot_plugins_metadata_commit"].respond(404)
if not is_commit:
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
mocked_api["zhenxun_bot_plugins_commit_proxy"].respond(404)
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
plugin_id = 3
plugin_id = "bilibili_sub"
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@ -135,7 +127,7 @@ async def test_add_plugin_basic_commit_version(
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
result=None,
bot=bot,
)
@ -159,7 +151,7 @@ async def test_add_plugin_basic_commit_version(
assert (mock_base_path / "plugins" / "bilibili_sub" / "__init__.py").is_file()
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("package_api", ["gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_basic_is_not_dir(
package_api: str,
@ -181,10 +173,7 @@ async def test_add_plugin_basic_is_not_dir(
new=tmp_path / "zhenxun",
)
if package_api != "jsd":
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if package_api != "gh":
mocked_api["zhenxun_bot_plugins_tree"].respond(404)
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if not is_commit:
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
@ -192,7 +181,7 @@ async def test_add_plugin_basic_is_not_dir(
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
plugin_id = 0
plugin_id = "jitang"
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@ -209,7 +198,7 @@ async def test_add_plugin_basic_is_not_dir(
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
result=None,
bot=bot,
)
@ -230,7 +219,7 @@ async def test_add_plugin_basic_is_not_dir(
assert (mock_base_path / "plugins" / "alapi" / "jitang.py").is_file()
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("package_api", ["gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_extra(
package_api: str,
@ -252,10 +241,7 @@ async def test_add_plugin_extra(
new=tmp_path / "zhenxun",
)
if package_api != "jsd":
mocked_api["zhenxun_github_sub_metadata"].respond(404)
if package_api != "gh":
mocked_api["zhenxun_github_sub_tree"].respond(404)
mocked_api["zhenxun_github_sub_metadata"].respond(404)
if not is_commit:
mocked_api["zhenxun_github_sub_commit"].respond(404)
@ -265,7 +251,7 @@ async def test_add_plugin_extra(
mocked_api["zhenxun_bot_plugins_index_commit"].respond(404)
mocked_api["zhenxun_bot_plugins_index_commit_proxy"].respond(404)
plugin_id = 4
plugin_id = "github_sub"
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@ -282,7 +268,7 @@ async def test_add_plugin_extra(
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
result=None,
bot=bot,
)
@ -339,7 +325,7 @@ async def test_plugin_not_exist_add(
)
ctx.should_call_send(
event=event,
message=Message(message="插件ID不存在..."),
message=Message(message="添加插件 Id: -1 失败 e: 插件ID不存在..."),
result=None,
bot=bot,
)
@ -385,7 +371,9 @@ async def test_add_plugin_exist(
)
ctx.should_call_send(
event=event,
message=Message(message="插件 识图 已安装,无需重复安装"),
message=Message(
message="添加插件 Id: 1 失败 e: 插件 识图 已安装,无需重复安装"
),
result=None,
bot=bot,
)

View File

@ -1,140 +0,0 @@
from collections.abc import Callable
from pathlib import Path
from typing import cast
from nonebot.adapters.onebot.v11 import Bot, Message
from nonebot.adapters.onebot.v11.event import GroupMessageEvent
from nonebug import App
from pytest_mock import MockerFixture
from respx import MockRouter
from tests.builtin_plugins.plugin_store.utils import init_mocked_api
from tests.config import BotId, GroupId, MessageId, UserId
from tests.utils import _v11_group_message_event
async def test_plugin_store(
app: App,
mocker: MockerFixture,
mocked_api: MockRouter,
create_bot: Callable,
tmp_path: Path,
) -> None:
"""
测试插件商店
"""
from zhenxun.builtin_plugins.plugin_store import _matcher
from zhenxun.builtin_plugins.plugin_store.data_source import row_style
init_mocked_api(mocked_api=mocked_api)
mock_table_page = mocker.patch(
"zhenxun.builtin_plugins.plugin_store.data_source.ImageTemplate.table_page"
)
mock_table_page_return = mocker.AsyncMock()
mock_table_page.return_value = mock_table_page_return
mock_build_message = mocker.patch(
"zhenxun.builtin_plugins.plugin_store.MessageUtils.build_message"
)
mock_build_message_return = mocker.AsyncMock()
mock_build_message.return_value = mock_build_message_return
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
bot: Bot = cast(Bot, bot)
raw_message = "插件商店"
event: GroupMessageEvent = _v11_group_message_event(
message=raw_message,
self_id=BotId.QQ_BOT,
user_id=UserId.SUPERUSER,
group_id=GroupId.GROUP_ID_LEVEL_5,
message_id=MessageId.MESSAGE_ID_3,
to_me=True,
)
ctx.receive_event(bot=bot, event=event)
mock_table_page.assert_awaited_once_with(
"插件列表",
"通过添加/移除插件 ID 来管理插件",
["-", "ID", "名称", "简介", "作者", "版本", "类型"],
[
["", 0, "鸡汤", "喏,亲手为你煮的鸡汤", "HibiKier", "0.1", "普通插件"],
["", 1, "识图", "以图搜图,看破本源", "HibiKier", "0.1", "普通插件"],
["", 2, "网易云热评", "生了个人,我很抱歉", "HibiKier", "0.1", "普通插件"],
[
"",
3,
"B站订阅",
"非常便利的B站订阅通知",
"HibiKier",
"0.3-b101fbc",
"普通插件",
],
[
"",
4,
"github订阅",
"订阅github用户或仓库",
"xuanerwa",
"0.7",
"普通插件",
],
[
"",
5,
"Minecraft查服",
"Minecraft服务器状态查询支持IPv6",
"molanp",
"1.13",
"普通插件",
],
],
text_style=row_style,
)
mock_build_message.assert_called_once_with(mock_table_page_return)
mock_build_message_return.send.assert_awaited_once()
assert mocked_api["basic_plugins"].called
assert mocked_api["extra_plugins"].called
async def test_plugin_store_fail(
app: App,
mocker: MockerFixture,
mocked_api: MockRouter,
create_bot: Callable,
tmp_path: Path,
) -> None:
"""
测试插件商店
"""
from zhenxun.builtin_plugins.plugin_store import _matcher
init_mocked_api(mocked_api=mocked_api)
mocked_api.get(
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/b101fbc/plugins.json",
name="basic_plugins",
).respond(404)
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
bot: Bot = cast(Bot, bot)
raw_message = "插件商店"
event: GroupMessageEvent = _v11_group_message_event(
message=raw_message,
self_id=BotId.QQ_BOT,
user_id=UserId.SUPERUSER,
group_id=GroupId.GROUP_ID_LEVEL_5,
message_id=MessageId.MESSAGE_ID_3,
to_me=True,
)
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message("获取插件列表失败..."),
result=None,
exception=None,
bot=bot,
)
assert mocked_api["basic_plugins"].called

View File

@ -96,7 +96,7 @@ async def test_plugin_not_exist_remove(
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message(message="插件ID不存在..."),
message=Message(message="移除插件 Id: -1 失败 e: 插件ID不存在..."),
result=None,
bot=bot,
)

View File

@ -158,7 +158,7 @@ async def test_plugin_not_exist_update(
)
ctx.should_call_send(
event=event,
message=Message(message="插件ID不存在..."),
message=Message(message="更新插件 Id: -1 失败 e: 插件ID不存在..."),
result=None,
bot=bot,
)
@ -200,7 +200,9 @@ async def test_update_plugin_not_install(
)
ctx.should_call_send(
event=event,
message=Message(message="插件 识图 未安装,无法更新"),
message=Message(
message="更新插件 Id: 1 失败 e: 插件 识图 未安装,无法更新"
),
result=None,
bot=bot,
)

View File

@ -161,7 +161,6 @@ class UpdateManager:
logger.debug(f"恢复旧的webui文件夹 {BACKUP_PATH}", COMMAND)
BACKUP_PATH.rename(WEBUI_PATH)
raise e
return ""
@classmethod
async def check_version(cls) -> str:

View File

@ -164,7 +164,7 @@ class StoreManager:
@classmethod
async def get_plugin_by_value(
cls, index_or_module: str, is_update: bool = False
) -> StorePluginInfo:
) -> tuple[StorePluginInfo, bool]:
"""获取插件信息
参数:
@ -177,19 +177,30 @@ class StoreManager:
返回:
StorePluginInfo: 插件信息
bool: 是否是外部插件
"""
plugin_list, extra_plugin_list = await cls.get_data()
all_plugin_list = plugin_list + extra_plugin_list
plugin_info = None
is_external = False
db_plugin_list = await cls.get_loaded_plugins("module")
plugin_key = await cls._resolve_plugin_key(index_or_module)
plugin_info = next((p for p in all_plugin_list if p.module == plugin_key), None)
for p in plugin_list:
if p.module == plugin_key:
is_external = False
plugin_info = p
break
for p in extra_plugin_list:
if p.module == plugin_key:
is_external = True
plugin_info = p
break
if not plugin_info:
raise PluginStoreException(f"插件不存在: {plugin_key}")
if not is_update and plugin_info.module in [p[0] for p in db_plugin_list]:
raise PluginStoreException(f"插件 {plugin_info.name} 已安装,无需重复安装")
if plugin_info.module not in [p[0] for p in db_plugin_list] and is_update:
raise PluginStoreException(f"插件 {plugin_info.name} 未安装,无法更新")
return plugin_info
return plugin_info, is_external
@classmethod
async def add_plugin(cls, index_or_module: str) -> str:
@ -201,11 +212,9 @@ class StoreManager:
返回:
str: 返回消息
"""
plugin_info = await cls.get_plugin_by_value(index_or_module)
is_external = True
plugin_info, is_external = await cls.get_plugin_by_value(index_or_module)
if plugin_info.github_url is None:
plugin_info.github_url = DEFAULT_GITHUB_URL
is_external = False
version_split = plugin_info.version.split("-")
if len(version_split) > 1:
github_url_split = plugin_info.github_url.split("/tree/")
@ -301,7 +310,7 @@ class StoreManager:
返回:
str: 返回消息
"""
plugin_info = await cls.get_plugin_by_value(index_or_module)
plugin_info, _ = await cls.get_plugin_by_value(index_or_module)
path = BASE_PATH
if plugin_info.github_url:
path = BASE_PATH / "plugins"
@ -373,17 +382,15 @@ class StoreManager:
返回:
str: 返回消息
"""
plugin_info = await cls.get_plugin_by_value(index_or_module, True)
plugin_info, is_external = await cls.get_plugin_by_value(index_or_module, True)
logger.info(f"尝试更新插件 {plugin_info.name}", LOG_COMMAND)
db_plugin_list = await cls.get_loaded_plugins("module", "version")
suc_plugin = {p[0]: (p[1] or "Unknown") for p in db_plugin_list}
logger.debug(f"当前插件列表: {suc_plugin}", LOG_COMMAND)
if cls.check_version_is_new(plugin_info, suc_plugin):
return f"插件 {plugin_info.name} 已是最新版本"
is_external = True
if plugin_info.github_url is None:
plugin_info.github_url = DEFAULT_GITHUB_URL
is_external = False
await cls.install_plugin_with_repo(
plugin_info.github_url,
plugin_info.module_path,
@ -402,8 +409,9 @@ class StoreManager:
返回:
str: 返回消息
"""
plugin_list: list[StorePluginInfo] = await cls.get_data()
plugin_name_list = [p.name for p in plugin_list]
plugin_list, extra_plugin_list = await cls.get_data()
all_plugin_list = plugin_list + extra_plugin_list
plugin_name_list = [p.name for p in all_plugin_list]
update_failed_list = []
update_success_list = []
result = "--已更新{}个插件 {}个失败 {}个成功--"

View File

@ -207,13 +207,13 @@ class RepoFileManager:
)
if repo_type is None:
try:
return await self.get_aliyun_file_content(
repo_name, file_path, branch, ignore_error
)
except Exception:
return await self.get_github_file_content(
repo_url, file_path, ignore_error
)
except Exception:
return await self.get_aliyun_file_content(
repo_name, file_path, branch, ignore_error
)
try:
if repo_type == RepoType.GITHUB:
@ -257,17 +257,17 @@ class RepoFileManager:
)
try:
if repo_type is None:
# 尝试阿里云,失败则尝试GitHub
# 尝试GitHub,失败则尝试阿里云
try:
return await self._list_aliyun_directory_files(
repo_name, directory_path, branch, recursive
return await self._list_github_directory_files(
repo_url, directory_path, branch, recursive
)
except Exception as e:
logger.warning(
"获取阿里云目录文件失败尝试GitHub", LOG_COMMAND, e=e
"获取GitHub目录文件失败尝试阿里云", LOG_COMMAND, e=e
)
return await self._list_github_directory_files(
repo_url, directory_path, branch, recursive
return await self._list_aliyun_directory_files(
repo_name, directory_path, branch, recursive
)
if repo_type == RepoType.GITHUB:
return await self._list_github_directory_files(
@ -526,6 +526,7 @@ class RepoFileManager:
content_bytes = content.encode("utf-8")
else:
content_bytes = content
logger.warning(f"写入文件: {local_path}")
async with aiofiles.open(local_path, "wb") as f:
await f.write(content_bytes)
result.success = True