🐛修复添加插件返回403的问题 (#1595)

* 完善测试方法
* vscode测试配置
* 重构插件安装过程
This commit is contained in:
AkashiCoin
2024-09-02 01:50:39 +08:00
committed by GitHub
parent 8fe061738a
commit 29c48765ae
18 changed files with 1090 additions and 452 deletions
@@ -1,3 +1,7 @@
import io
import os
import tarfile
import zipfile
from typing import cast
from pathlib import Path
from collections.abc import Callable
@@ -9,18 +13,19 @@ from nonebot.adapters.onebot.v11 import Bot
from nonebot.adapters.onebot.v11.message import Message
from tests.config import BotId, UserId, GroupId, MessageId
from tests.utils import (
get_response_json,
_v11_group_message_event,
_v11_private_message_send,
)
from tests.utils import get_response_json as _get_response_json
from tests.utils import _v11_group_message_event, _v11_private_message_send
def get_response_json(file: str) -> dict:
return _get_response_json(Path() / "auto_update", file)
def init_mocked_api(mocked_api: MockRouter) -> None:
mocked_api.get(
url="https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest",
name="release_latest",
).respond(json=get_response_json(path="release_latest.json"))
).respond(json=get_response_json("release_latest.json"))
mocked_api.get(
url="https://raw.githubusercontent.com/HibiKier/zhenxun_bot/dev/__version__",
name="dev_branch_version",
@@ -38,62 +43,105 @@ def init_mocked_api(mocked_api: MockRouter) -> None:
"Location": "https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2"
},
)
import io
import tarfile
tar_buffer = io.BytesIO()
zip_bytes = io.BytesIO()
from zhenxun.builtin_plugins.auto_update.config import (
REQ_TXT_FILE,
PYPROJECT_FILE,
PYPROJECT_LOCK_FILE,
REPLACE_FOLDERS,
REQ_TXT_FILE_STRING,
PYPROJECT_FILE_STRING,
PYPROJECT_LOCK_FILE_STRING,
)
# 指定要添加到压缩文件中的文件路径列表
file_paths: list[Path] = [
PYPROJECT_FILE,
PYPROJECT_LOCK_FILE,
REQ_TXT_FILE,
file_paths: list[str] = [
PYPROJECT_FILE_STRING,
PYPROJECT_LOCK_FILE_STRING,
REQ_TXT_FILE_STRING,
]
# 打开一个tarfile对象,写入到上面创建的BytesIO对象中
with tarfile.open(mode="w:gz", fileobj=tar_buffer) as tar:
_extracted_from_init_mocked_api_43(tarfile, tar, file_paths, io)
_extracted_from_init_mocked_api_43(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)
mocked_api.get(
url="https://codeload.github.com/HibiKier/zhenxun_bot/legacy.tar.gz/refs/tags/v0.2.2",
name="release_download_url_redirect",
).respond(
content=tar_buffer.getvalue(),
)
mocked_api.get(
url="https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/dev.zip",
name="dev_download_url",
).respond(
content=zip_bytes.getvalue(),
)
mocked_api.get(
url="https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip",
name="main_download_url",
).respond(
content=zip_bytes.getvalue(),
)
# TODO Rename this here and in `init_mocked_api`
def _extracted_from_init_mocked_api_43(tarfile, tar, file_paths, io):
def _extracted_from_init_mocked_api_zip(
zipf: zipfile.ZipFile, file_paths: list[str], folders: list[str] = []
):
# 假设有一个文件夹名为 folder_name
folder_name = "my_folder/"
# 添加文件夹到 ZIP 中,注意 ZIP 中文件夹路径应以 '/' 结尾
zipf.writestr(folder_name, "") # 空内容表示这是一个文件夹
for file_path in file_paths:
# 将文件添加到 ZIP 中,路径为 folder_name + file_name
zipf.writestr(f"{folder_name}{os.path.basename(file_path)}", b"new")
base_folder = f"{folder_name}zhenxun/"
zipf.writestr(base_folder, "")
for folder in folders:
zipf.writestr(f"{base_folder}{folder}/", "")
# TODO Rename this here and in `init_mocked_api`
def _extracted_from_init_mocked_api_43(
tar: tarfile.TarFile, file_paths: list[str], folders: list[str] = []
):
folder_name = "my_folder"
tarinfo = tarfile.TarInfo(folder_name)
_extracted_from__extracted_from_init_mocked_api_43_30(tarinfo, tar)
# 读取并添加指定的文件
for file_path in file_paths:
# 创建TarInfo对象
tar_buffer = io.BytesIO(b"new")
tarinfo = tarfile.TarInfo(
f"{folder_name}/{file_path}"
) # 使用文件名作为tar中的名字
tarinfo.mode = 0o644 # 设置文件夹权限
tarinfo.size = len(tar_buffer.getvalue()) # 设置文件大小
# 添加文件
tar.addfile(tarinfo, fileobj=tar_buffer)
base_folder = f"{folder_name}/zhenxun"
tarinfo = tarfile.TarInfo(base_folder)
_extracted_from__extracted_from_init_mocked_api_43_30(tarinfo, tar)
for folder in folders:
tarinfo = tarfile.TarInfo(f"{base_folder}{folder}")
_extracted_from__extracted_from_init_mocked_api_43_30(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):
tarinfo.type = tarfile.DIRTYPE
tarinfo.mode = 0o755
tar.addfile(tarinfo)
# 读取并添加指定的文件
for file_path in file_paths:
# 读取文件内容
with open(file_path, "rb") as file:
file_content = file.read()
# 使用BytesIO创建文件内容
file_buffer = io.BytesIO(file_content)
# 创建TarInfo对象
tarinfo = tarfile.TarInfo(
f"{folder_name}/{file_path.name}"
) # 使用文件名作为tar中的名字
tarinfo.mode = 0o644 # 设置文件夹权限
tarinfo.size = len(file_content)
# 添加文件
tar.addfile(tarinfo, fileobj=file_buffer)
async def test_check_update_release(
app: App,
@@ -103,20 +151,64 @@ async def test_check_update_release(
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)
mocker.patch(
"zhenxun.builtin_plugins.auto_update._data_source.REPLACE_FOLDERS",
return_value=[],
)
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,
)
# 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名
mock_tmp_path.mkdir(parents=True, exist_ok=True)
for folder in REPLACE_FOLDERS:
(mock_base_path / folder).mkdir(parents=True, exist_ok=True)
mock_pyproject_file.write_bytes(b"")
mock_pyproject_lock_file.write_bytes(b"")
mock_req_txt_file.write_bytes(b"")
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
@@ -150,3 +242,245 @@ async def test_check_update_release(
assert mocked_api["release_latest"].called
assert mocked_api["release_download_url"].called
assert mocked_api["release_download_url_redirect"].called
assert (mock_backup_path / PYPROJECT_FILE_STRING).exists()
assert (mock_backup_path / PYPROJECT_LOCK_FILE_STRING).exists()
assert (mock_backup_path / REQ_TXT_FILE_STRING).exists()
assert not mock_download_gz_file.exists()
assert not mock_download_zip_file.exists()
assert mock_pyproject_file.read_bytes() == b"new"
assert mock_pyproject_lock_file.read_bytes() == b"new"
assert mock_req_txt_file.read_bytes() == b"new"
for folder in REPLACE_FOLDERS:
assert not (mock_base_path / folder).exists()
for folder in REPLACE_FOLDERS:
assert (mock_backup_path / folder).exists()
async def test_check_update_dev(
app: App,
mocker: MockerFixture,
mocked_api: MockRouter,
create_bot: Callable,
tmp_path: Path,
) -> None:
"""
测试检查更新(开发环境)
"""
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)
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,
)
# 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名
mock_tmp_path.mkdir(parents=True, exist_ok=True)
for folder in REPLACE_FOLDERS:
(mock_base_path / folder).mkdir(parents=True, exist_ok=True)
mock_pyproject_file.write_bytes(b"")
mock_pyproject_lock_file.write_bytes(b"")
mock_req_txt_file.write_bytes(b"")
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
bot = cast(Bot, bot)
raw_message = "检查更新 dev"
event = _v11_group_message_event(
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, event)
ctx.should_call_api(
"send_msg",
_v11_private_message_send(
message="检测真寻已更新,版本更新:v0.2.2 -> v0.2.2\n开始更新...",
user_id=UserId.SUPERUSER,
),
)
ctx.should_call_send(
event=event,
message=Message(
"版本更新完成\n" "版本: v0.2.2 -> v0.2.2\n" "请重新启动真寻以完成更新!"
),
result=None,
bot=bot,
)
ctx.should_finished(_matcher)
assert mocked_api["dev_download_url"].called
assert (mock_backup_path / PYPROJECT_FILE_STRING).exists()
assert (mock_backup_path / PYPROJECT_LOCK_FILE_STRING).exists()
assert (mock_backup_path / REQ_TXT_FILE_STRING).exists()
assert not mock_download_gz_file.exists()
assert not mock_download_zip_file.exists()
assert mock_pyproject_file.read_bytes() == b"new"
assert mock_pyproject_lock_file.read_bytes() == b"new"
assert mock_req_txt_file.read_bytes() == b"new"
for folder in REPLACE_FOLDERS:
assert (mock_base_path / folder).exists()
for folder in REPLACE_FOLDERS:
assert (mock_backup_path / folder).exists()
async def test_check_update_main(
app: App,
mocker: MockerFixture,
mocked_api: MockRouter,
create_bot: Callable,
tmp_path: Path,
) -> None:
"""
测试检查更新(正式环境)
"""
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)
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,
)
# 确保目录下有一个子目录,以便 os.listdir() 能返回一个目录名
mock_tmp_path.mkdir(parents=True, exist_ok=True)
for folder in REPLACE_FOLDERS:
(mock_base_path / folder).mkdir(parents=True, exist_ok=True)
mock_pyproject_file.write_bytes(b"")
mock_pyproject_lock_file.write_bytes(b"")
mock_req_txt_file.write_bytes(b"")
async with app.test_matcher(_matcher) as ctx:
bot = create_bot(ctx)
bot = cast(Bot, bot)
raw_message = "检查更新 main"
event = _v11_group_message_event(
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, event)
ctx.should_call_api(
"send_msg",
_v11_private_message_send(
message="检测真寻已更新,版本更新:v0.2.2 -> v0.2.2\n开始更新...",
user_id=UserId.SUPERUSER,
),
)
ctx.should_call_send(
event=event,
message=Message(
"版本更新完成\n" "版本: v0.2.2 -> v0.2.2\n" "请重新启动真寻以完成更新!"
),
result=None,
bot=bot,
)
ctx.should_finished(_matcher)
assert mocked_api["main_download_url"].called
assert (mock_backup_path / PYPROJECT_FILE_STRING).exists()
assert (mock_backup_path / PYPROJECT_LOCK_FILE_STRING).exists()
assert (mock_backup_path / REQ_TXT_FILE_STRING).exists()
assert not mock_download_gz_file.exists()
assert not mock_download_zip_file.exists()
assert mock_pyproject_file.read_bytes() == b"new"
assert mock_pyproject_lock_file.read_bytes() == b"new"
assert mock_req_txt_file.read_bytes() == b"new"
for folder in REPLACE_FOLDERS:
assert (mock_base_path / folder).exists()
for folder in REPLACE_FOLDERS:
assert (mock_backup_path / folder).exists()
@@ -1,147 +0,0 @@
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.config import BotId, UserId, GroupId, MessageId
from tests.utils import get_response_json, _v11_group_message_event
def init_mocked_api(mocked_api: MockRouter) -> None:
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://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/contents/plugins/search_image?ref=main",
name="search_image_plugin_api",
).respond(200, json=get_response_json("search_image_plugin_api.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=b"")
mocked_api.get(
"https://api.github.com/repos/xuanerwa/zhenxun_github_sub/contents/",
name="github_sub_plugin_contents",
).respond(json=get_response_json("github_sub_plugin_contents.json"))
mocked_api.get(
"https://api.github.com/repos/xuanerwa/zhenxun_github_sub/contents/github_sub?ref=main",
name="github_sub_plugin_api",
).respond(json=get_response_json("github_sub_plugin_api.json"))
mocked_api.get(
"https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/main/github_sub/__init__.py",
name="github_sub_plugin_file_init",
).respond(content=b"")
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)
mocker.patch(
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
return_value=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["search_image_plugin_api"].called
assert mocked_api["search_image_plugin_file_init"].called
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)
mocker.patch(
"zhenxun.builtin_plugins.plugin_store.data_source.BASE_PATH",
return_value=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_contents"].called
assert mocked_api["github_sub_plugin_api"].called
assert mocked_api["github_sub_plugin_file_init"].called
@@ -0,0 +1,309 @@
# 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.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 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=b"")
mocked_api.get(
"https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/alapi/jitang.py",
name="jitang_plugin_file",
).respond(content=b"")
mocked_api.get(
"https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/main/github_sub/__init__.py",
name="github_sub_plugin_file_init",
).respond(content=b"")
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_update_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_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", "w") as f:
f.write("")
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()
+4 -1
View File
@@ -73,10 +73,13 @@ async def app(app: App, tmp_path: Path, mocker: MockerFixture):
mock_config_path.TEMP_PATH = tmp_path / "resources" / "temp"
# mock_config_path.TEMP_PATH.mkdir(parents=True, exist_ok=True)
mocker.patch("zhenxun.configs.path_config", return_value=mock_config_path)
mocker.patch("zhenxun.configs.path_config", new=mock_config_path)
await init()
# await driver._lifespan.startup()
yield app
# await driver._lifespan.shutdown()
await disconnect()
Binary file not shown.
-18
View File
@@ -1,18 +0,0 @@
[
{
"name": "__init__.py",
"path": "github_sub/__init__.py",
"sha": "7d17fd49fe82fa3897afcef61b2c694ed93a4ba3",
"size": 7551,
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/contents/github_sub/__init__.py?ref=main",
"html_url": "https://github.com/xuanerwa/zhenxun_github_sub/blob/main/github_sub/__init__.py",
"git_url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/7d17fd49fe82fa3897afcef61b2c694ed93a4ba3",
"download_url": "https://raw.githubusercontent.com/xuanerwa/zhenxun_github_sub/main/github_sub/__init__.py",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/contents/github_sub/__init__.py?ref=main",
"git": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/blobs/7d17fd49fe82fa3897afcef61b2c694ed93a4ba3",
"html": "https://github.com/xuanerwa/zhenxun_github_sub/blob/main/github_sub/__init__.py"
}
}
]
@@ -1,18 +0,0 @@
[
{
"name": "github_sub",
"path": "github_sub",
"sha": "0f7d76bcf472e2ab0610fa542b067633d6e3ae7e",
"size": 0,
"url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/contents/github_sub?ref=main",
"html_url": "https://github.com/xuanerwa/zhenxun_github_sub/tree/main/github_sub",
"git_url": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/0f7d76bcf472e2ab0610fa542b067633d6e3ae7e",
"download_url": null,
"type": "dir",
"_links": {
"self": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/contents/github_sub?ref=main",
"git": "https://api.github.com/repos/xuanerwa/zhenxun_github_sub/git/trees/0f7d76bcf472e2ab0610fa542b067633d6e3ae7e",
"html": "https://github.com/xuanerwa/zhenxun_github_sub/tree/main/github_sub"
}
}
]
@@ -0,0 +1,23 @@
{
"type": "gh",
"name": "xuanerwa/zhenxun_github_sub",
"version": "main",
"default": null,
"files": [
{
"type": "directory",
"name": "github_sub",
"files": [
{
"type": "file",
"name": "__init__.py",
"hash": "z1C5BBK0+atbDghbyRlF2xIDwk0HQdHM1yXQZkF7/t8=",
"size": 7551
}
]
}
],
"links": {
"stats": "https://data.jsdelivr.com/v1/stats/packages/gh/xuanerwa/zhenxun_github_sub@main"
}
}
@@ -0,0 +1,71 @@
{
"type": "gh",
"name": "zhenxun-org/zhenxun_bot_plugins",
"version": "main",
"default": null,
"files": [
{
"type": "directory",
"name": "plugins",
"files": [
{
"type": "directory",
"name": "search_image",
"files": [
{
"type": "file",
"name": "__init__.py",
"hash": "a4Yp9HPoBzMwvnQDT495u0yYqTQWofkOyHxEi1FdVb0=",
"size": 3010
}
]
},
{
"type": "directory",
"name": "alapi",
"files": [
{
"type": "file",
"name": "__init__.py",
"hash": "ndDxtO0pAq3ZTb4RdqW7FTDgOGC/RjS1dnwdaQfT0uQ=",
"size": 284
},
{
"type": "file",
"name": "_data_source.py",
"hash": "KOLqtj4TQWWQco5bA4tWFc7A0z1ruMyDk1RiKeqJHRA=",
"size": 919
},
{
"type": "file",
"name": "comments_163.py",
"hash": "Q5pZsj1Pj+EJMdKYcPtLqejcXAWUQIoXVQG49PZPaSI=",
"size": 1593
},
{
"type": "file",
"name": "cover.py",
"hash": "QSjtcy0oVrjaRiAWZKmUJlp0L4DQqEcdYNmExNo9mgc=",
"size": 1438
},
{
"type": "file",
"name": "jitang.py",
"hash": "xh43Osxt0xogTH448gUMC+/DaSGmCFme8DWUqC25IbU=",
"size": 1411
},
{
"type": "file",
"name": "poetry.py",
"hash": "Aj2unoNQboj3/0LhIrYU+dCa5jvMdpjMYXYUayhjuz4=",
"size": 1530
}
]
}
]
}
],
"links": {
"stats": "https://data.jsdelivr.com/v1/stats/packages/gh/zhenxun-org/zhenxun_bot_plugins@main"
}
}
@@ -1,18 +0,0 @@
[
{
"name": "__init__.py",
"path": "plugins/search_image/__init__.py",
"sha": "38e86de0caafe6c3e88d973fb5c4bc9d1430d213",
"size": 3010,
"url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/contents/plugins/search_image/__init__.py?ref=main",
"html_url": "https://github.com/zhenxun-org/zhenxun_bot_plugins/blob/main/plugins/search_image/__init__.py",
"git_url": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/38e86de0caafe6c3e88d973fb5c4bc9d1430d213",
"download_url": "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins/main/plugins/search_image/__init__.py",
"type": "file",
"_links": {
"self": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/contents/plugins/search_image/__init__.py?ref=main",
"git": "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/git/blobs/38e86de0caafe6c3e88d973fb5c4bc9d1430d213",
"html": "https://github.com/zhenxun-org/zhenxun_bot_plugins/blob/main/plugins/search_image/__init__.py"
}
}
]
+6 -4
View File
@@ -5,18 +5,20 @@ from nonebot.adapters.onebot.v11.event import Sender
from nonebot.adapters.onebot.v11 import Message, MessageSegment, GroupMessageEvent
def get_response_json(path: str) -> dict:
def get_response_json(base_path: Path, file: str) -> dict:
try:
return json.loads(
(Path(__file__).parent / "response" / path).read_text(encoding="utf8")
(Path(__file__).parent / "response" / base_path / file).read_text(
encoding="utf8"
)
)
except (FileNotFoundError, json.JSONDecodeError) as e:
raise ValueError(f"Error reading or parsing JSON file: {e}") from e
def get_content_bytes(path: str) -> bytes:
def get_content_bytes(base_path: Path, path: str) -> bytes:
try:
return (Path(__file__).parent / "content" / path).read_bytes()
return (Path(__file__).parent / "content" / base_path / path).read_bytes()
except FileNotFoundError as e:
raise ValueError(f"Error reading file: {e}") from e