diff --git a/tests/builtin_plugins/auto_update/test_check_update.py b/tests/builtin_plugins/auto_update/test_check_update.py index faa378ac3..d151c95b2 100644 --- a/tests/builtin_plugins/auto_update/test_check_update.py +++ b/tests/builtin_plugins/auto_update/test_check_update.py @@ -63,10 +63,10 @@ def init_mocked_api(mocked_api: MockRouter) -> None: # 打开一个tarfile对象,写入到上面创建的BytesIO对象中 with tarfile.open(mode="w:gz", fileobj=tar_buffer) as tar: - _extracted_from_init_mocked_api_43(tar, file_paths, folders=REPLACE_FOLDERS) + add_files_and_folders_to_tar(tar, file_paths, folders=REPLACE_FOLDERS) with zipfile.ZipFile(zip_bytes, mode="w", compression=zipfile.ZIP_DEFLATED) as zipf: - _extracted_from_init_mocked_api_zip(zipf, file_paths, folders=REPLACE_FOLDERS) + add_files_and_folders_to_zip(zipf, file_paths, folders=REPLACE_FOLDERS) mocked_api.get( url="https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2", @@ -89,9 +89,22 @@ def init_mocked_api(mocked_api: MockRouter) -> None: # TODO Rename this here and in `init_mocked_api` -def _extracted_from_init_mocked_api_zip( +def add_files_and_folders_to_zip( zipf: zipfile.ZipFile, file_paths: list[str], folders: list[str] = [] ): + """Add files and folders to a zip archive. + + This function creates a directory structure within the specified zip + archive and adds the provided files to it. It also creates additional + subdirectories as specified in the folders list. + + Args: + zipf: The zip archive to which files and folders will be added. + file_paths: A list of file names to be added to the zip archive. + folders: An optional list of subdirectory names to be created + within the base folder. + """ + # 假设有一个文件夹名为 folder_name folder_name = "my_folder/" @@ -109,12 +122,25 @@ def _extracted_from_init_mocked_api_zip( # TODO Rename this here and in `init_mocked_api` -def _extracted_from_init_mocked_api_43( +def add_files_and_folders_to_tar( tar: tarfile.TarFile, file_paths: list[str], folders: list[str] = [] ): + """Add files and folders to a tar archive. + + This function creates a directory structure within the specified tar + archive and adds the provided files to it. It also creates additional + subdirectories as specified in the folders list. + + Args: + tar: The tar archive to which files and folders will be added. + file_paths: A list of file names to be added to the tar archive. + folders: An optional list of subdirectory names to be created + within the base folder. + """ + folder_name = "my_folder" tarinfo = tarfile.TarInfo(folder_name) - _extracted_from__extracted_from_init_mocked_api_43_30(tarinfo, tar) + add_directory_to_tar(tarinfo, tar) # 读取并添加指定的文件 for file_path in file_paths: # 创建TarInfo对象 @@ -130,39 +156,37 @@ def _extracted_from_init_mocked_api_43( base_folder = f"{folder_name}/zhenxun" tarinfo = tarfile.TarInfo(base_folder) - _extracted_from__extracted_from_init_mocked_api_43_30(tarinfo, tar) + add_directory_to_tar(tarinfo, tar) for folder in folders: tarinfo = tarfile.TarInfo(f"{base_folder}{folder}") - _extracted_from__extracted_from_init_mocked_api_43_30(tarinfo, tar) + add_directory_to_tar(tarinfo, tar) # TODO Rename this here and in `_extracted_from_init_mocked_api_43` -def _extracted_from__extracted_from_init_mocked_api_43_30(tarinfo, tar): +def add_directory_to_tar(tarinfo, tar): + """Add a directory entry to a tar archive. + + This function modifies the provided tarinfo object to set its type + as a directory and assigns the appropriate permissions before adding + it to the specified tar archive. + + Args: + tarinfo: The tarinfo object representing the directory. + tar: The tar archive to which the directory will be added. + """ + tarinfo.type = tarfile.DIRTYPE tarinfo.mode = 0o755 tar.addfile(tarinfo) -async def test_check_update_release( - app: App, - mocker: MockerFixture, - mocked_api: MockRouter, - create_bot: Callable, - tmp_path: Path, -) -> None: - """ - 测试检查更新(release) - """ - from zhenxun.builtin_plugins.auto_update import _matcher +def init_mocker_path(mocker: MockerFixture, tmp_path: Path): from zhenxun.builtin_plugins.auto_update.config import ( - REPLACE_FOLDERS, REQ_TXT_FILE_STRING, PYPROJECT_FILE_STRING, PYPROJECT_LOCK_FILE_STRING, ) - init_mocked_api(mocked_api=mocked_api) - mocker.patch( "zhenxun.builtin_plugins.auto_update._data_source.install_requirement", return_value=None, @@ -199,6 +223,48 @@ async def test_check_update_release( "zhenxun.builtin_plugins.auto_update._data_source.REQ_TXT_FILE", new=tmp_path / REQ_TXT_FILE_STRING, ) + return ( + mock_tmp_path, + mock_base_path, + mock_backup_path, + mock_download_gz_file, + mock_download_zip_file, + mock_pyproject_file, + mock_pyproject_lock_file, + mock_req_txt_file, + ) + + +async def test_check_update_release( + app: App, + mocker: MockerFixture, + mocked_api: MockRouter, + create_bot: Callable, + tmp_path: Path, +) -> None: + """ + 测试检查更新(release) + """ + from zhenxun.builtin_plugins.auto_update import _matcher + from zhenxun.builtin_plugins.auto_update.config import ( + REPLACE_FOLDERS, + REQ_TXT_FILE_STRING, + PYPROJECT_FILE_STRING, + PYPROJECT_LOCK_FILE_STRING, + ) + + init_mocked_api(mocked_api=mocked_api) + + ( + mock_tmp_path, + mock_base_path, + mock_backup_path, + mock_download_gz_file, + mock_download_zip_file, + mock_pyproject_file, + mock_pyproject_lock_file, + mock_req_txt_file, + ) = init_mocker_path(mocker, tmp_path) # 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名 mock_tmp_path.mkdir(parents=True, exist_ok=True) @@ -280,42 +346,16 @@ async def test_check_update_dev( init_mocked_api(mocked_api=mocked_api) - mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.install_requirement", - return_value=None, - ) - mock_tmp_path = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.TMP_PATH", - new=tmp_path / "auto_update", - ) - mock_base_path = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.BASE_PATH", - new=tmp_path / "zhenxun", - ) - mock_backup_path = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.BACKUP_PATH", - new=tmp_path / "backup", - ) - mock_download_gz_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_GZ_FILE", - new=mock_tmp_path / "download_latest_file.tar.gz", - ) - mock_download_zip_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_ZIP_FILE", - new=mock_tmp_path / "download_latest_file.zip", - ) - mock_pyproject_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_FILE", - new=tmp_path / PYPROJECT_FILE_STRING, - ) - mock_pyproject_lock_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_LOCK_FILE", - new=tmp_path / PYPROJECT_LOCK_FILE_STRING, - ) - mock_req_txt_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.REQ_TXT_FILE", - new=tmp_path / REQ_TXT_FILE_STRING, - ) + ( + mock_tmp_path, + mock_base_path, + mock_backup_path, + mock_download_gz_file, + mock_download_zip_file, + mock_pyproject_file, + mock_pyproject_lock_file, + mock_req_txt_file, + ) = init_mocker_path(mocker, tmp_path) # 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名 mock_tmp_path.mkdir(parents=True, exist_ok=True) @@ -393,42 +433,16 @@ async def test_check_update_main( init_mocked_api(mocked_api=mocked_api) - mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.install_requirement", - return_value=None, - ) - mock_tmp_path = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.TMP_PATH", - new=tmp_path / "auto_update", - ) - mock_base_path = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.BASE_PATH", - new=tmp_path / "zhenxun", - ) - mock_backup_path = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.BACKUP_PATH", - new=tmp_path / "backup", - ) - mock_download_gz_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_GZ_FILE", - new=mock_tmp_path / "download_latest_file.tar.gz", - ) - mock_download_zip_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.DOWNLOAD_ZIP_FILE", - new=mock_tmp_path / "download_latest_file.zip", - ) - mock_pyproject_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_FILE", - new=tmp_path / PYPROJECT_FILE_STRING, - ) - mock_pyproject_lock_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.PYPROJECT_LOCK_FILE", - new=tmp_path / PYPROJECT_LOCK_FILE_STRING, - ) - mock_req_txt_file = mocker.patch( - "zhenxun.builtin_plugins.auto_update._data_source.REQ_TXT_FILE", - new=tmp_path / REQ_TXT_FILE_STRING, - ) + ( + mock_tmp_path, + mock_base_path, + mock_backup_path, + mock_download_gz_file, + mock_download_zip_file, + mock_pyproject_file, + mock_pyproject_lock_file, + mock_req_txt_file, + ) = init_mocker_path(mocker, tmp_path) # 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名 mock_tmp_path.mkdir(parents=True, exist_ok=True) diff --git a/tests/builtin_plugins/plugin_store/test_add_plugin.py b/tests/builtin_plugins/plugin_store/test_add_plugin.py new file mode 100644 index 000000000..a4abb327e --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_add_plugin.py @@ -0,0 +1,258 @@ +from typing import cast +from pathlib import Path +from collections.abc import Callable + +from nonebug import App +from respx import MockRouter +from pytest_mock import MockerFixture +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.message import Message +from nonebot.adapters.onebot.v11.event import GroupMessageEvent + +from tests.utils import _v11_group_message_event +from tests.config import BotId, UserId, GroupId, MessageId +from tests.builtin_plugins.plugin_store.utils import init_mocked_api + + +async def test_add_plugin_basic( + 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) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 安装成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["zhenxun_bot_plugins_metadata"].called + assert mocked_api["search_image_plugin_file_init"].called + assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() + + +async def test_add_plugin_basic_is_not_dir( + 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) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + plugin_id = 0 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 鸡汤 安装成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["zhenxun_bot_plugins_metadata"].called + assert mocked_api["jitang_plugin_file"].called + assert (mock_base_path / "plugins" / "alapi" / "jitang.py").is_file() + + +async def test_add_plugin_extra( + 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) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + plugin_id = 3 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message: str = f"添加插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 github订阅 安装成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["github_sub_plugin_metadata"].called + assert mocked_api["github_sub_plugin_file_init"].called + assert (mock_base_path / "plugins" / "github_sub" / "__init__.py").is_file() + + +async def test_plugin_not_exist_add( + 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) + plugin_id = -1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件ID不存在..."), + result=None, + bot=bot, + ) + + +async def test_add_plugin_exist( + 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) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.1")], + ) + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"添加插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在添加插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 已安装,无需重复安装"), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/test_plugin_store.py b/tests/builtin_plugins/plugin_store/test_plugin_store.py index aaf548fdb..04629df90 100644 --- a/tests/builtin_plugins/plugin_store/test_plugin_store.py +++ b/tests/builtin_plugins/plugin_store/test_plugin_store.py @@ -1,5 +1,3 @@ -# ruff: noqa: ASYNC230 - from typing import cast from pathlib import Path from collections.abc import Callable @@ -7,56 +5,15 @@ from nonebug import App from respx import MockRouter from pytest_mock import MockerFixture -from nonebot.adapters.onebot.v11 import Bot -from nonebot.adapters.onebot.v11.message import Message +from nonebot.adapters.onebot.v11 import Bot, Message from nonebot.adapters.onebot.v11.event import GroupMessageEvent from tests.utils import _v11_group_message_event from tests.config import BotId, UserId, GroupId, MessageId -from tests.utils import get_content_bytes as _get_content_bytes -from tests.utils import get_response_json as _get_response_json - - -def get_response_json(file: str) -> dict: - return _get_response_json(Path() / "plugin_store", file=file) - - -def get_content_bytes(file: str) -> bytes: - return _get_content_bytes(Path() / "plugin_store", file) - - -def init_mocked_api(mocked_api: MockRouter) -> None: - mocked_api.get( - "https://data.jsdelivr.com/v1/packages/gh/xuanerwa/zhenxun_github_sub@main", - name="github_sub_plugin_metadata", - ).respond(json=get_response_json("github_sub_plugin_metadata.json")) - mocked_api.get( - "https://data.jsdelivr.com/v1/packages/gh/zhenxun-org/zhenxun_bot_plugins@main", - name="zhenxun_bot_plugins_metadata", - ).respond(json=get_response_json("zhenxun_bot_plugins_metadata.json")) - mocked_api.get( - "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins/plugins.json", - name="basic_plugins", - ).respond(200, json=get_response_json("basic_plugins.json")) - mocked_api.get( - "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/index/plugins.json", - name="extra_plugins", - ).respond(200, json=get_response_json("extra_plugins.json")) - mocked_api.get( - "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/search_image/__init__.py", - name="search_image_plugin_file_init", - ).respond(content=get_content_bytes("search_image.py")) - mocked_api.get( - "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/alapi/jitang.py", - name="jitang_plugin_file", - ).respond(content=get_content_bytes("jitang.py")) - mocked_api.get( - "https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/main/github_sub/__init__.py", - name="github_sub_plugin_file_init", - ).respond(content=get_content_bytes("github_sub.py")) +from tests.builtin_plugins.plugin_store.utils import init_mocked_api -async def test_add_plugin_basic( +async def test_plugin_store( app: App, mocker: MockerFixture, mocked_api: MockRouter, @@ -64,211 +21,75 @@ async def test_add_plugin_basic( 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_base_path = mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", - new=tmp_path / "zhenxun", - ) - - plugin_id = 1 - - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"添加插件 {plugin_id}" - 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, - to_me=True, - ) - ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message=f"正在添加插件 Id: {plugin_id}"), - result=None, - bot=bot, - ) - ctx.should_call_send( - event=event, - message=Message(message="插件 识图 安装成功! 重启后生效"), - result=None, - bot=bot, - ) - assert mocked_api["basic_plugins"].called - assert mocked_api["extra_plugins"].called - assert mocked_api["zhenxun_bot_plugins_metadata"].called - assert mocked_api["search_image_plugin_file_init"].called - assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() - - -async def test_add_plugin_basic_is_not_dir( - 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) - mock_base_path = mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", - new=tmp_path / "zhenxun", + 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 - plugin_id = 0 - - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"添加插件 {plugin_id}" - 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, - to_me=True, - ) - ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message=f"正在添加插件 Id: {plugin_id}"), - result=None, - bot=bot, - ) - ctx.should_call_send( - event=event, - message=Message(message="插件 鸡汤 安装成功! 重启后生效"), - result=None, - bot=bot, - ) - assert mocked_api["basic_plugins"].called - assert mocked_api["extra_plugins"].called - assert mocked_api["zhenxun_bot_plugins_metadata"].called - assert mocked_api["jitang_plugin_file"].called - assert (mock_base_path / "plugins" / "alapi" / "jitang.py").is_file() - - -async def test_add_plugin_extra( - 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) - mock_base_path = mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", - new=tmp_path / "zhenxun", + mock_build_message = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.MessageUtils.build_message" ) - - plugin_id = 3 + 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: str = f"添加插件 {plugin_id}" + 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, + message_id=MessageId.MESSAGE_ID_3, to_me=True, ) ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message=f"正在添加插件 Id: {plugin_id}"), - result=None, - bot=bot, - ) - ctx.should_call_send( - event=event, - message=Message(message="插件 github订阅 安装成功! 重启后生效"), - result=None, - bot=bot, - ) - assert mocked_api["basic_plugins"].called - assert mocked_api["extra_plugins"].called - assert mocked_api["github_sub_plugin_metadata"].called - assert mocked_api["github_sub_plugin_file_init"].called - assert (mock_base_path / "plugins" / "github_sub" / "__init__.py").is_file() - - -async def test_update_plugin_basic_need_update( - 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) - mock_base_path = mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", - new=tmp_path / "zhenxun", + mock_table_page.assert_awaited_once_with( + "插件列表", + "通过添加/移除插件 ID 来管理插件", + ["-", "ID", "名称", "简介", "作者", "版本", "类型"], + [ + ["", 0, "鸡汤", "喏,亲手为你煮的鸡汤", "HibiKier", "0.1", "普通插件"], + ["", 1, "识图", "以图搜图,看破本源", "HibiKier", "0.1", "普通插件"], + ["", 2, "网易云热评", "生了个人,我很抱歉", "HibiKier", "0.1", "普通插件"], + [ + "", + 3, + "github订阅", + "订阅github用户或仓库", + "xuanerwa", + "0.7", + "普通插件", + ], + [ + "", + 4, + "Minecraft查服", + "Minecraft服务器状态查询,支持IPv6", + "molanp", + "1.13", + "普通插件", + ], + ], + text_style=row_style, ) - mock_base_path = mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", - return_value=[("search_image", "0.0")], - ) - - plugin_id = 1 + mock_build_message.assert_called_once_with(mock_table_page_return) + mock_build_message_return.send.assert_awaited_once() - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"更新插件 {plugin_id}" - 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, - to_me=True, - ) - ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message=f"正在更新插件 Id: {plugin_id}"), - result=None, - bot=bot, - ) - ctx.should_call_send( - event=event, - message=Message(message="插件 识图 更新成功! 重启后生效"), - result=None, - bot=bot, - ) assert mocked_api["basic_plugins"].called assert mocked_api["extra_plugins"].called - assert mocked_api["zhenxun_bot_plugins_metadata"].called - assert mocked_api["search_image_plugin_file_init"].called - assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() -async def test_update_plugin_basic_is_new( +async def test_plugin_store_fail( app: App, mocker: MockerFixture, mocked_api: MockRouter, @@ -276,252 +97,35 @@ async def test_update_plugin_basic_is_new( tmp_path: Path, ) -> None: """ - 测试更新基础插件,插件是最新版 + 测试插件商店 """ from zhenxun.builtin_plugins.plugin_store import _matcher init_mocked_api(mocked_api=mocked_api) - mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", - new=tmp_path / "zhenxun", - ) - mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", - return_value=[("search_image", "0.1")], - ) - - plugin_id = 1 + mocked_api.get( + "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins/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 = f"更新插件 {plugin_id}" + 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, + message_id=MessageId.MESSAGE_ID_3, to_me=True, ) ctx.receive_event(bot=bot, event=event) ctx.should_call_send( event=event, - message=Message(message=f"正在更新插件 Id: {plugin_id}"), - result=None, - bot=bot, - ) - ctx.should_call_send( - event=event, - message=Message(message="插件 识图 已是最新版本"), + message=Message("获取插件列表失败..."), result=None, + exception=None, bot=bot, ) - assert mocked_api["basic_plugins"].called - assert mocked_api["extra_plugins"].called - - -async def test_remove_plugin( - 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) - mock_base_path = mocker.patch( - "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", - new=tmp_path / "zhenxun", - ) - - plugin_path = mock_base_path / "plugins" / "search_image" - plugin_path.mkdir(parents=True, exist_ok=True) - with open(plugin_path / "__init__.py", "wb") as f: - f.write(get_content_bytes("search_image.py")) - - plugin_id = 1 - - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"移除插件 {plugin_id}" - 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, - to_me=True, - ) - ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message="插件 识图 移除成功! 重启后生效"), - result=None, - bot=bot, - ) assert mocked_api["basic_plugins"].called - assert mocked_api["extra_plugins"].called - assert not (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() - - -async def test_plugin_not_exist_add( - 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) - plugin_id = -1 - - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"添加插件 {plugin_id}" - 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, - to_me=True, - ) - ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message=f"正在添加插件 Id: {plugin_id}"), - result=None, - bot=bot, - ) - ctx.should_call_send( - event=event, - message=Message(message="插件ID不存在..."), - result=None, - bot=bot, - ) - - -async def test_plugin_not_exist_update( - 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) - plugin_id = -1 - - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"更新插件 {plugin_id}" - 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_2, - to_me=True, - ) - ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message=f"正在更新插件 Id: {plugin_id}"), - result=None, - bot=bot, - ) - ctx.should_call_send( - event=event, - message=Message(message="插件ID不存在..."), - result=None, - bot=bot, - ) - - -async def test_plugin_not_exist_remove( - 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) - plugin_id = -1 - - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"移除插件 {plugin_id}" - 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_2, - to_me=True, - ) - ctx.receive_event(bot=bot, event=event) - ctx.should_call_send( - event=event, - message=Message(message="插件ID不存在..."), - result=None, - bot=bot, - ) - - -async def test_plugin_not_exist_search( - 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) - plugin_id = -1 - - async with app.test_matcher(_matcher) as ctx: - bot = create_bot(ctx) - bot: Bot = cast(Bot, bot) - raw_message = f"搜索插件 {plugin_id}" - 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(message="未找到相关插件..."), - result=None, - bot=bot, - ) diff --git a/tests/builtin_plugins/plugin_store/test_remove_plugin.py b/tests/builtin_plugins/plugin_store/test_remove_plugin.py new file mode 100644 index 000000000..5ebd45847 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_remove_plugin.py @@ -0,0 +1,142 @@ +# ruff: noqa: ASYNC230 + +from typing import cast +from pathlib import Path +from collections.abc import Callable + +from nonebug import App +from respx import MockRouter +from pytest_mock import MockerFixture +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.message import Message +from nonebot.adapters.onebot.v11.event import GroupMessageEvent + +from tests.utils import _v11_group_message_event +from tests.config import BotId, UserId, GroupId, MessageId +from tests.builtin_plugins.plugin_store.utils import init_mocked_api, get_content_bytes + + +async def test_remove_plugin( + 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) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + + plugin_path = mock_base_path / "plugins" / "search_image" + plugin_path.mkdir(parents=True, exist_ok=True) + + with open(plugin_path / "__init__.py", "wb") as f: + f.write(get_content_bytes("search_image.py")) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"移除插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 移除成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert not (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() + + +async def test_plugin_not_exist_remove( + 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) + plugin_id = -1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"移除插件 {plugin_id}" + 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_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="插件ID不存在..."), + result=None, + bot=bot, + ) + + +async def test_remove_plugin_not_install( + 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) + _ = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"移除插件 {plugin_id}" + 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_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 不存在..."), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/test_search_plugin.py b/tests/builtin_plugins/plugin_store/test_search_plugin.py new file mode 100644 index 000000000..60fd0232d --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_search_plugin.py @@ -0,0 +1,182 @@ +from typing import cast +from pathlib import Path +from collections.abc import Callable + +from nonebug import App +from respx import MockRouter +from pytest_mock import MockerFixture +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.message import Message +from nonebot.adapters.onebot.v11.event import GroupMessageEvent + +from tests.utils import _v11_group_message_event +from tests.config import BotId, UserId, GroupId, MessageId +from tests.builtin_plugins.plugin_store.utils import init_mocked_api + + +async def test_search_plugin_name( + 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 + + plugin_name = "github订阅" + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"搜索插件 {plugin_name}" + 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", "名称", "简介", "作者", "版本", "类型"], + [ + [ + "", + 3, + "github订阅", + "订阅github用户或仓库", + "xuanerwa", + "0.7", + "普通插件", + ] + ], + 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_search_plugin_author( + 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 + + author_name = "xuanerwa" + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"搜索插件 {author_name}" + 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", "名称", "简介", "作者", "版本", "类型"], + [ + [ + "", + 3, + "github订阅", + "订阅github用户或仓库", + "xuanerwa", + "0.7", + "普通插件", + ] + ], + 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_not_exist_search( + 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) + plugin_name = "not_exist_plugin_name" + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"搜索插件 {plugin_name}" + 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(message="未找到相关插件..."), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/test_update_plugin.py b/tests/builtin_plugins/plugin_store/test_update_plugin.py new file mode 100644 index 000000000..f2c247edd --- /dev/null +++ b/tests/builtin_plugins/plugin_store/test_update_plugin.py @@ -0,0 +1,207 @@ +from typing import cast +from pathlib import Path +from collections.abc import Callable + +from nonebug import App +from respx import MockRouter +from pytest_mock import MockerFixture +from nonebot.adapters.onebot.v11 import Bot +from nonebot.adapters.onebot.v11.message import Message +from nonebot.adapters.onebot.v11.event import GroupMessageEvent + +from tests.utils import _v11_group_message_event +from tests.config import BotId, UserId, GroupId, MessageId +from tests.builtin_plugins.plugin_store.utils import init_mocked_api + + +async def test_update_plugin_basic_need_update( + 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) + mock_base_path = mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.0")], + ) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 更新成功! 重启后生效"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + assert mocked_api["zhenxun_bot_plugins_metadata"].called + assert mocked_api["search_image_plugin_file_init"].called + assert (mock_base_path / "plugins" / "search_image" / "__init__.py").is_file() + + +async def test_update_plugin_basic_is_new( + 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) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH", + new=tmp_path / "zhenxun", + ) + mocker.patch( + "zhenxun.builtin_plugins.plugin_store.data_source.ShopManage.get_loaded_plugins", + return_value=[("search_image", "0.1")], + ) + + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + 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, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 已是最新版本"), + result=None, + bot=bot, + ) + assert mocked_api["basic_plugins"].called + assert mocked_api["extra_plugins"].called + + +async def test_plugin_not_exist_update( + 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) + plugin_id = -1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + 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_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件ID不存在..."), + result=None, + bot=bot, + ) + + +async def test_update_plugin_not_install( + 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) + plugin_id = 1 + + async with app.test_matcher(_matcher) as ctx: + bot = create_bot(ctx) + bot: Bot = cast(Bot, bot) + raw_message = f"更新插件 {plugin_id}" + 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_2, + to_me=True, + ) + ctx.receive_event(bot=bot, event=event) + ctx.should_call_send( + event=event, + message=Message(message=f"正在更新插件 Id: {plugin_id}"), + result=None, + bot=bot, + ) + ctx.should_call_send( + event=event, + message=Message(message="插件 识图 未安装,无法更新"), + result=None, + bot=bot, + ) diff --git a/tests/builtin_plugins/plugin_store/utils.py b/tests/builtin_plugins/plugin_store/utils.py new file mode 100644 index 000000000..0c981a756 --- /dev/null +++ b/tests/builtin_plugins/plugin_store/utils.py @@ -0,0 +1,47 @@ +# ruff: noqa: ASYNC230 + +from pathlib import Path + +from respx import MockRouter + +from tests.utils import get_content_bytes as _get_content_bytes +from tests.utils import get_response_json as _get_response_json + + +def get_response_json(file: str) -> dict: + return _get_response_json(Path() / "plugin_store", file=file) + + +def get_content_bytes(file: str) -> bytes: + return _get_content_bytes(Path() / "plugin_store", file) + + +def init_mocked_api(mocked_api: MockRouter) -> None: + mocked_api.get( + "https://data.jsdelivr.com/v1/packages/gh/xuanerwa/zhenxun_github_sub@main", + name="github_sub_plugin_metadata", + ).respond(json=get_response_json("github_sub_plugin_metadata.json")) + mocked_api.get( + "https://data.jsdelivr.com/v1/packages/gh/zhenxun-org/zhenxun_bot_plugins@main", + name="zhenxun_bot_plugins_metadata", + ).respond(json=get_response_json("zhenxun_bot_plugins_metadata.json")) + mocked_api.get( + "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins/plugins.json", + name="basic_plugins", + ).respond(200, json=get_response_json("basic_plugins.json")) + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/index/plugins.json", + name="extra_plugins", + ).respond(200, json=get_response_json("extra_plugins.json")) + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/search_image/__init__.py", + name="search_image_plugin_file_init", + ).respond(content=get_content_bytes("search_image.py")) + mocked_api.get( + "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/alapi/jitang.py", + name="jitang_plugin_file", + ).respond(content=get_content_bytes("jitang.py")) + mocked_api.get( + "https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/main/github_sub/__init__.py", + name="github_sub_plugin_file_init", + ).respond(content=get_content_bytes("github_sub.py")) diff --git a/tests/conftest.py b/tests/conftest.py index 0157561dc..ba7b2790d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -87,7 +87,7 @@ async def app(app: App, tmp_path: Path, mocker: MockerFixture): def create_bot() -> Callable: from nonebot.adapters.onebot.v11 import Bot, Adapter - def _create_bot(context: MatcherContext): + def _create_bot(context: MatcherContext) -> Bot: return context.create_bot( base=Bot, adapter=nonebot.get_adapter(Adapter), diff --git a/zhenxun/builtin_plugins/plugin_store/data_source.py b/zhenxun/builtin_plugins/plugin_store/data_source.py index bec20cfa1..43d6d75d1 100644 --- a/zhenxun/builtin_plugins/plugin_store/data_source.py +++ b/zhenxun/builtin_plugins/plugin_store/data_source.py @@ -180,7 +180,9 @@ def version_check(cls, plugin_info: StorePluginInfo, suc_plugin: dict[str, str]) str: 版本号 """ module = plugin_info.module - if not cls.check_version_is_new(plugin_info, suc_plugin): + if suc_plugin.get(module) and not cls.check_version_is_new( + plugin_info, suc_plugin + ): return f"{suc_plugin[module]} (有更新->{plugin_info.version})" return plugin_info.version @@ -254,7 +256,10 @@ async def add_plugin(cls, plugin_id: int) -> str: if plugin_id < 0 or plugin_id >= len(data): return "插件ID不存在..." plugin_key = list(data.keys())[plugin_id] + plugin_list = await cls.get_loaded_plugins("module") plugin_info = data[plugin_key] + if plugin_info.module in [p[0] for p in plugin_list]: + return f"插件 {plugin_key} 已安装,无需重复安装" is_external = True if plugin_info.github_url is None: plugin_info.github_url = DEFAULT_GITHUB_URL @@ -388,7 +393,7 @@ async def search_plugin(cls, plugin_name_or_author: str) -> BuildImage | str: """ data = await cls.__get_data() plugin_list = await cls.get_loaded_plugins("module", "version") - suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]} + suc_plugin = {p[0]: (p[1] or "Unknown") for p in plugin_list} filtered_data = [ (id, plugin_info) for id, plugin_info in enumerate(data.items()) @@ -436,7 +441,9 @@ async def update_plugin(cls, plugin_id: int) -> str: logger.info(f"尝试更新插件 {plugin_key}", "插件管理") plugin_info = data[plugin_key] plugin_list = await cls.get_loaded_plugins("module", "version") - suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]} + suc_plugin = {p[0]: (p[1] or "Unknown") for p in plugin_list} + if plugin_info.module not in [p[0] for p in plugin_list]: + return f"插件 {plugin_key} 未安装,无法更新" logger.debug(f"当前插件列表: {suc_plugin}", "插件管理") if cls.check_version_is_new(plugin_info, suc_plugin): return f"插件 {plugin_key} 已是最新版本"