Compare commits

..
1 Commits
Author SHA1 Message Date
HibiKier cf3e94748d Merge 854eac68d8 into 59d72c3b3d 2025-08-01 17:49:18 +08:00
8 changed files with 200 additions and 58 deletions
@@ -207,7 +207,7 @@ def init_mocker_path(mocker: MockerFixture, tmp_path: Path):
)
mocker.patch(
"zhenxun.utils.manager.virtual_env_package_manager.VirtualEnvPackageManager.install_requirement",
"zhenxun.builtin_plugins.auto_update._data_source.install_requirement",
return_value=None,
)
mock_tmp_path = mocker.patch(
@@ -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", ["gh"])
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_basic(
package_api: str,
@@ -37,14 +37,18 @@ async def test_add_plugin_basic(
new=tmp_path / "zhenxun",
)
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if package_api != "jsd":
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if package_api != "gh":
mocked_api["zhenxun_bot_plugins_tree"].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 = "search_image"
plugin_id = 1
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@@ -61,7 +65,7 @@ async def test_add_plugin_basic(
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
result=None,
bot=bot,
)
@@ -82,7 +86,7 @@ async def test_add_plugin_basic(
assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file()
@pytest.mark.parametrize("package_api", ["gh"])
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_basic_commit_version(
package_api: str,
@@ -104,13 +108,17 @@ async def test_add_plugin_basic_commit_version(
new=tmp_path / "zhenxun",
)
mocked_api["zhenxun_bot_plugins_metadata_commit"].respond(404)
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)
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 = "bilibili_sub"
plugin_id = 3
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@@ -127,7 +135,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"正在添加插件 Module: {plugin_id}"),
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
result=None,
bot=bot,
)
@@ -151,7 +159,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", ["gh"])
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_basic_is_not_dir(
package_api: str,
@@ -173,7 +181,10 @@ async def test_add_plugin_basic_is_not_dir(
new=tmp_path / "zhenxun",
)
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if package_api != "jsd":
mocked_api["zhenxun_bot_plugins_metadata"].respond(404)
if package_api != "gh":
mocked_api["zhenxun_bot_plugins_tree"].respond(404)
if not is_commit:
mocked_api["zhenxun_bot_plugins_commit"].respond(404)
@@ -181,7 +192,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 = "jitang"
plugin_id = 0
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@@ -198,7 +209,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"正在添加插件 Module: {plugin_id}"),
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
result=None,
bot=bot,
)
@@ -219,7 +230,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", ["gh"])
@pytest.mark.parametrize("package_api", ["jsd", "gh"])
@pytest.mark.parametrize("is_commit", [True, False])
async def test_add_plugin_extra(
package_api: str,
@@ -241,7 +252,10 @@ async def test_add_plugin_extra(
new=tmp_path / "zhenxun",
)
mocked_api["zhenxun_github_sub_metadata"].respond(404)
if package_api != "jsd":
mocked_api["zhenxun_github_sub_metadata"].respond(404)
if package_api != "gh":
mocked_api["zhenxun_github_sub_tree"].respond(404)
if not is_commit:
mocked_api["zhenxun_github_sub_commit"].respond(404)
@@ -251,7 +265,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 = "github_sub"
plugin_id = 4
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@@ -268,7 +282,7 @@ async def test_add_plugin_extra(
ctx.receive_event(bot=bot, event=event)
ctx.should_call_send(
event=event,
message=Message(message=f"正在添加插件 Module: {plugin_id}"),
message=Message(message=f"正在添加插件 Id: {plugin_id}"),
result=None,
bot=bot,
)
@@ -325,7 +339,7 @@ async def test_plugin_not_exist_add(
)
ctx.should_call_send(
event=event,
message=Message(message="添加插件 Id: -1 失败 e: 插件ID不存在..."),
message=Message(message="插件ID不存在..."),
result=None,
bot=bot,
)
@@ -371,9 +385,7 @@ async def test_add_plugin_exist(
)
ctx.should_call_send(
event=event,
message=Message(
message="添加插件 Id: 1 失败 e: 插件 识图 已安装,无需重复安装"
),
message=Message(message="插件 识图 已安装,无需重复安装"),
result=None,
bot=bot,
)
@@ -0,0 +1,140 @@
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
@@ -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: -1 失败 e: 插件ID不存在..."),
message=Message(message="插件ID不存在..."),
result=None,
bot=bot,
)
@@ -158,7 +158,7 @@ async def test_plugin_not_exist_update(
)
ctx.should_call_send(
event=event,
message=Message(message="更新插件 Id: -1 失败 e: 插件ID不存在..."),
message=Message(message="插件ID不存在..."),
result=None,
bot=bot,
)
@@ -200,9 +200,7 @@ async def test_update_plugin_not_install(
)
ctx.should_call_send(
event=event,
message=Message(
message="更新插件 Id: 1 失败 e: 插件 识图 未安装,无法更新"
),
message=Message(message="插件 识图 未安装,无法更新"),
result=None,
bot=bot,
)
@@ -161,6 +161,7 @@ class UpdateManager:
logger.debug(f"恢复旧的webui文件夹 {BACKUP_PATH}", COMMAND)
BACKUP_PATH.rename(WEBUI_PATH)
raise e
return ""
@classmethod
async def check_version(cls) -> str:
@@ -164,7 +164,7 @@ class StoreManager:
@classmethod
async def get_plugin_by_value(
cls, index_or_module: str, is_update: bool = False
) -> tuple[StorePluginInfo, bool]:
) -> StorePluginInfo:
"""获取插件信息
参数:
@@ -177,30 +177,19 @@ class StoreManager:
返回:
StorePluginInfo: 插件信息
bool: 是否是外部插件
"""
plugin_list, extra_plugin_list = await cls.get_data()
plugin_info = None
is_external = False
all_plugin_list = plugin_list + extra_plugin_list
db_plugin_list = await cls.get_loaded_plugins("module")
plugin_key = await cls._resolve_plugin_key(index_or_module)
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
plugin_info = next((p for p in all_plugin_list if p.module == plugin_key), None)
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, is_external
return plugin_info
@classmethod
async def add_plugin(cls, index_or_module: str) -> str:
@@ -212,9 +201,11 @@ class StoreManager:
返回:
str: 返回消息
"""
plugin_info, is_external = await cls.get_plugin_by_value(index_or_module)
plugin_info = await cls.get_plugin_by_value(index_or_module)
is_external = True
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/")
@@ -310,7 +301,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"
@@ -382,15 +373,17 @@ class StoreManager:
返回:
str: 返回消息
"""
plugin_info, is_external = await cls.get_plugin_by_value(index_or_module, True)
plugin_info = 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,
@@ -409,9 +402,8 @@ class StoreManager:
返回:
str: 返回消息
"""
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]
plugin_list: list[StorePluginInfo] = await cls.get_data()
plugin_name_list = [p.name for p in plugin_list]
update_failed_list = []
update_success_list = []
result = "--已更新{}个插件 {}个失败 {}个成功--"
+10 -11
View File
@@ -207,13 +207,13 @@ class RepoFileManager:
)
if repo_type is None:
try:
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
)
except Exception:
return await self.get_github_file_content(
repo_url, file_path, 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_github_directory_files(
repo_url, directory_path, branch, recursive
return await self._list_aliyun_directory_files(
repo_name, directory_path, branch, recursive
)
except Exception as e:
logger.warning(
"获取GitHub目录文件失败,尝试阿里云", LOG_COMMAND, e=e
"获取阿里云目录文件失败,尝试GitHub", LOG_COMMAND, e=e
)
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
)
if repo_type == RepoType.GITHUB:
return await self._list_github_directory_files(
@@ -526,7 +526,6 @@ 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