Files
AstrBot/tests_v4/test_decorators.py
united_pooh f865b2b7e9 chore(runtime): 使用 ruff format 并为 peer 补充中文文档
- 运行 ruff format 统一代码格式
- 说明 Peer 在协议层中的命名含义
- 为 Peer 类及其所有方法补充中文注释型文档
2026-03-13 01:39:22 +08:00

334 lines
9.1 KiB
Python

"""
Tests for decorators.py - Handler decorator infrastructure.
"""
from __future__ import annotations
from astrbot_sdk.decorators import (
HANDLER_META_ATTR,
HandlerMeta,
get_handler_meta,
on_command,
on_event,
on_message,
on_schedule,
require_admin,
)
from astrbot_sdk.protocol.descriptors import (
CommandTrigger,
EventTrigger,
MessageTrigger,
Permissions,
ScheduleTrigger,
)
class TestHandlerMeta:
"""Tests for HandlerMeta dataclass."""
def test_default_values(self):
"""HandlerMeta should have default values."""
meta = HandlerMeta()
assert meta.trigger is None
assert meta.priority == 0
assert isinstance(meta.permissions, Permissions)
def test_trigger_assignment(self):
"""HandlerMeta should accept trigger assignment."""
trigger = CommandTrigger(command="test")
meta = HandlerMeta(trigger=trigger)
assert meta.trigger is trigger
def test_priority_assignment(self):
"""HandlerMeta should accept priority assignment."""
meta = HandlerMeta(priority=10)
assert meta.priority == 10
def test_permissions_assignment(self):
"""HandlerMeta should accept permissions assignment."""
permissions = Permissions(require_admin=True)
meta = HandlerMeta(permissions=permissions)
assert meta.permissions.require_admin is True
class TestGetHandlerMeta:
"""Tests for get_handler_meta function."""
def test_returns_none_for_undecorated_function(self):
"""get_handler_meta should return None for undecorated functions."""
async def plain_function():
pass
assert get_handler_meta(plain_function) is None
def test_returns_meta_for_decorated_function(self):
"""get_handler_meta should return meta for decorated functions."""
@on_command("test")
async def decorated():
pass
meta = get_handler_meta(decorated)
assert meta is not None
assert isinstance(meta, HandlerMeta)
class TestOnCommandDecorator:
"""Tests for @on_command decorator."""
def test_sets_handler_meta_attribute(self):
"""@on_command should set __astrbot_handler_meta__ attribute."""
@on_command("hello")
async def handler():
pass
assert hasattr(handler, HANDLER_META_ATTR)
def test_creates_command_trigger(self):
"""@on_command should create CommandTrigger."""
@on_command("hello")
async def handler():
pass
meta = get_handler_meta(handler)
assert isinstance(meta.trigger, CommandTrigger)
assert meta.trigger.command == "hello"
def test_supports_aliases(self):
"""@on_command should store aliases."""
@on_command("hello", aliases=["hi", "hey"])
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.aliases == ["hi", "hey"]
def test_supports_description(self):
"""@on_command should store description."""
@on_command("hello", description="Say hello")
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.description == "Say hello"
def test_default_empty_aliases(self):
"""@on_command should default to empty aliases."""
@on_command("test")
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.aliases == []
def test_preserves_function(self):
"""@on_command should preserve the original function."""
@on_command("test")
async def handler():
return "result"
assert handler.__name__ == "handler"
class TestOnMessageDecorator:
"""Tests for @on_message decorator."""
def test_creates_message_trigger(self):
"""@on_message should create MessageTrigger."""
@on_message()
async def handler():
pass
meta = get_handler_meta(handler)
assert isinstance(meta.trigger, MessageTrigger)
def test_supports_regex(self):
"""@on_message should store regex pattern."""
@on_message(regex=r"\d+")
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.regex == r"\d+"
def test_supports_keywords(self):
"""@on_message should store keywords."""
@on_message(keywords=["hello", "hi"])
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.keywords == ["hello", "hi"]
def test_supports_platforms(self):
"""@on_message should store platforms."""
@on_message(platforms=["telegram", "discord"])
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.platforms == ["telegram", "discord"]
def test_defaults_empty_lists(self):
"""@on_message should default to empty lists."""
@on_message()
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.keywords == []
assert meta.trigger.platforms == []
class TestOnEventDecorator:
"""Tests for @on_event decorator."""
def test_creates_event_trigger(self):
"""@on_event should create EventTrigger."""
@on_event("message_received")
async def handler():
pass
meta = get_handler_meta(handler)
assert isinstance(meta.trigger, EventTrigger)
assert meta.trigger.event_type == "message_received"
def test_various_event_types(self):
"""@on_event should handle various event types."""
event_types = ["message_received", "user_joined", "custom_event"]
for event_type in event_types:
@on_event(event_type)
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.event_type == event_type
class TestOnScheduleDecorator:
"""Tests for @on_schedule decorator."""
def test_creates_cron_trigger(self):
"""@on_schedule with cron should create ScheduleTrigger."""
@on_schedule(cron="* * * * *")
async def handler():
pass
meta = get_handler_meta(handler)
assert isinstance(meta.trigger, ScheduleTrigger)
assert meta.trigger.cron == "* * * * *"
def test_creates_interval_trigger(self):
"""@on_schedule with interval should create ScheduleTrigger."""
@on_schedule(interval_seconds=60)
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.trigger.interval_seconds == 60
class TestRequireAdminDecorator:
"""Tests for @require_admin decorator."""
def test_sets_require_admin_permission(self):
"""@require_admin should set require_admin in permissions."""
@require_admin
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.permissions.require_admin is True
def test_can_combine_with_other_decorators(self):
"""@require_admin can be combined with other decorators."""
@on_command("admin")
@require_admin
async def handler():
pass
meta = get_handler_meta(handler)
assert isinstance(meta.trigger, CommandTrigger)
assert meta.trigger.command == "admin"
assert meta.permissions.require_admin is True
def test_order_does_not_matter_for_permissions(self):
"""Decorator order should not affect permissions."""
@require_admin
@on_command("admin")
async def handler1():
pass
@on_command("admin")
@require_admin
async def handler2():
pass
meta1 = get_handler_meta(handler1)
meta2 = get_handler_meta(handler2)
assert meta1.permissions.require_admin is True
assert meta2.permissions.require_admin is True
class TestDecoratorChaining:
"""Tests for chaining multiple decorators."""
def test_multiple_decorators_share_meta(self):
"""Multiple decorators should share the same meta object."""
@on_command("test")
@require_admin
async def handler():
pass
meta = get_handler_meta(handler)
assert isinstance(meta.trigger, CommandTrigger)
assert meta.permissions.require_admin is True
def test_last_trigger_wins(self):
"""Last trigger decorator should override previous ones."""
# Decorators are applied bottom-up, so on_message wins
@on_message()
@on_command("override")
async def handler():
pass
meta = get_handler_meta(handler)
# on_message is applied last (outermost), so it wins
assert isinstance(meta.trigger, MessageTrigger)
def test_permissions_accumulate(self):
"""Permissions should accumulate across decorators."""
@require_admin
@on_command("test")
async def handler():
pass
meta = get_handler_meta(handler)
assert meta.permissions.require_admin is True