diff --git a/danding_bot/plugins/danding_sign_in/permission.py b/danding_bot/plugins/danding_sign_in/permission.py new file mode 100644 index 0000000..db4ee01 --- /dev/null +++ b/danding_bot/plugins/danding_sign_in/permission.py @@ -0,0 +1,24 @@ +"""独立签到插件 - 权限纯函数(无 nonebot 依赖,便于单元测试)。""" + +from typing import Optional + + +def is_allowed( + user_id: int, + group_id: Optional[int], + *, + allowed_group_id: int, + allowed_user_id: int, +) -> bool: + """签到权限纯函数:管理员任意场景可用;其他用户仅白名单群聊可用。 + + Args: + user_id: 消息发送者 QQ + group_id: 群聊 ID,非群聊场景为 None + allowed_group_id: 白名单群聊 + allowed_user_id: 管理员 QQ + """ + + if user_id == allowed_user_id: + return True + return group_id is not None and group_id == allowed_group_id diff --git a/danding_bot/plugins/danding_sign_in/rules.py b/danding_bot/plugins/danding_sign_in/rules.py index 86620a3..8e96ee1 100644 --- a/danding_bot/plugins/danding_sign_in/rules.py +++ b/danding_bot/plugins/danding_sign_in/rules.py @@ -1,49 +1,21 @@ -"""独立签到插件 - 权限校验。 +"""独立签到插件 - 权限校验(NoneBot Rule)。 签到命令仅在白名单群聊(或管理员)中可用;私聊非管理员不可用。 与 onmyoji_gacha 的规则不同:抽卡规则私聊放行,签到规则更严格(群聊白名单)。 + +注意:本模块必须顶层导入 nonebot(NoneBot Rule 解析依赖参数注解, +注解名需要在模块全局命名空间可见),纯函数逻辑见 permission.py。 """ -from __future__ import annotations +from nonebot.rule import Rule +from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent -from typing import TYPE_CHECKING, Optional - -if TYPE_CHECKING: - from nonebot.rule import Rule - - -def is_allowed( - user_id: int, - group_id: Optional[int], - *, - allowed_group_id: int, - allowed_user_id: int, -) -> bool: - """签到权限纯函数:管理员任意场景可用;其他用户仅白名单群聊可用。 - - Args: - user_id: 消息发送者 QQ - group_id: 群聊 ID,非群聊场景为 None - allowed_group_id: 白名单群聊 - allowed_user_id: 管理员 QQ - """ - - if user_id == allowed_user_id: - return True - return group_id is not None and group_id == allowed_group_id +from .config import Config +from .permission import is_allowed def check_permission() -> Rule: - """生成签到命令权限 Rule(白名单群聊 + 管理员)。 - - 延迟导入 nonebot(与 config.py 同模式),保证纯函数在无 nonebot 的 - 测试环境中可加载。 - """ - - from nonebot.rule import Rule - from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent - - from .config import Config + """生成签到命令权限 Rule(白名单群聊 + 管理员)。""" config = Config() diff --git a/tests/test_danding_sign_in_plugin.py b/tests/test_danding_sign_in_plugin.py index 07f2d12..b35a989 100644 --- a/tests/test_danding_sign_in_plugin.py +++ b/tests/test_danding_sign_in_plugin.py @@ -1,6 +1,6 @@ """独立签到插件(danding_sign_in)测试。 -直接加载 config/api/service/rules 子模块,避免执行 nonebot 插件入口; +直接加载 config/api/service/permission 子模块,避免执行 nonebot 插件入口; aiohttp 调用用 FakeSession/FakeResponse 替身 mock(与 test_onmyoji_gacha_http_api.py 同模式)。 """ @@ -15,7 +15,11 @@ import pytest def load_modules(): - """直接加载 danding_sign_in 子模块,避免测试环境执行 nonebot 插件入口。""" + """直接加载 danding_sign_in 子模块,避免测试环境执行 nonebot 插件入口。 + + rules.py 顶层导入 nonebot(Rule 解析要求注解全局可见),无 nonebot 的 + 测试环境不加载它;权限纯函数在 permission.py 中测试。 + """ plugin_dir = Path(__file__).resolve().parents[1] / "danding_bot" / "plugins" / "danding_sign_in" package_name = "_danding_sign_in_under_test" @@ -23,7 +27,7 @@ def load_modules(): package.__path__ = [str(plugin_dir)] sys.modules[package_name] = package - for module_name in ("config", "api", "service", "rules"): + for module_name in ("config", "api", "service", "permission"): full_name = f"{package_name}.{module_name}" spec = importlib.util.spec_from_file_location(full_name, plugin_dir / f"{module_name}.py") module = importlib.util.module_from_spec(spec) @@ -35,15 +39,15 @@ def load_modules(): sys.modules[f"{package_name}.config"], sys.modules[f"{package_name}.api"], sys.modules[f"{package_name}.service"], - sys.modules[f"{package_name}.rules"], + sys.modules[f"{package_name}.permission"], ) -config_module, api_module, service_module, rules_module = load_modules() +config_module, api_module, service_module, permission_module = load_modules() Config = config_module.Config SignInAPI = api_module.SignInAPI perform_daily_sign_in = service_module.perform_daily_sign_in -is_allowed = rules_module.is_allowed +is_allowed = permission_module.is_allowed class FakeResponse: