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

250 lines
7.5 KiB
Python

"""
Unit tests for API decorators and Star class.
"""
from __future__ import annotations
import pytest
from astrbot_sdk import Context, MessageEvent, Star
from astrbot_sdk.decorators import (
get_handler_meta,
on_command,
on_event,
on_message,
on_schedule,
require_admin,
)
from astrbot_sdk.protocol.descriptors import (
CommandTrigger,
EventTrigger,
MessageTrigger,
ScheduleTrigger,
)
class TestOnCommandDecorator:
"""Tests for @on_command decorator."""
def test_decorator_sets_handler_meta(self):
"""@on_command should set __astrbot_handler_meta__."""
@on_command("hello")
async def hello_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(hello_handler)
assert meta is not None
assert isinstance(meta.trigger, CommandTrigger)
assert meta.trigger.command == "hello"
def test_decorator_supports_aliases(self):
"""@on_command should support command aliases."""
@on_command("hello", aliases=["hi", "hey"])
async def hello_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(hello_handler)
assert meta.trigger.aliases == ["hi", "hey"]
def test_decorator_supports_description(self):
"""@on_command should support description."""
@on_command("hello", description="Say hello")
async def hello_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(hello_handler)
assert meta.trigger.description == "Say hello"
class TestOnMessageDecorator:
"""Tests for @on_message decorator."""
def test_decorator_sets_handler_meta(self):
"""@on_message should set __astrbot_handler_meta__."""
@on_message()
async def message_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(message_handler)
assert meta is not None
assert isinstance(meta.trigger, MessageTrigger)
def test_decorator_supports_keywords(self):
"""@on_message should support keyword filtering."""
@on_message(keywords=["hello", "hi"])
async def keyword_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(keyword_handler)
assert meta.trigger.keywords == ["hello", "hi"]
def test_decorator_supports_regex(self):
"""@on_message should support regex filtering."""
@on_message(regex=r"\d+")
async def regex_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(regex_handler)
assert meta.trigger.regex == r"\d+"
class TestOnEventDecorator:
"""Tests for @on_event decorator."""
def test_decorator_sets_handler_meta(self):
"""@on_event should set __astrbot_handler_meta__."""
@on_event("message_received")
async def event_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(event_handler)
assert meta is not None
assert isinstance(meta.trigger, EventTrigger)
assert meta.trigger.event_type == "message_received"
class TestOnScheduleDecorator:
"""Tests for @on_schedule decorator."""
def test_decorator_sets_cron_trigger(self):
"""@on_schedule should create ScheduleTrigger with cron."""
@on_schedule(cron="* * * * *")
async def scheduled_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(scheduled_handler)
assert meta is not None
assert isinstance(meta.trigger, ScheduleTrigger)
assert meta.trigger.cron == "* * * * *"
def test_decorator_sets_interval_trigger(self):
"""@on_schedule should create ScheduleTrigger with interval."""
@on_schedule(interval_seconds=60)
async def interval_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(interval_handler)
assert meta.trigger.interval_seconds == 60
class TestRequireAdminDecorator:
"""Tests for @require_admin decorator."""
def test_decorator_sets_admin_permission(self):
"""@require_admin should set require_admin permission."""
@require_admin
async def admin_handler(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(admin_handler)
assert meta.permissions.require_admin is True
def test_can_combine_with_on_command(self):
"""@require_admin can be combined with @on_command."""
@on_command("admin")
@require_admin
async def admin_cmd(event: MessageEvent, ctx: Context):
pass
meta = get_handler_meta(admin_cmd)
assert isinstance(meta.trigger, CommandTrigger)
assert meta.trigger.command == "admin"
assert meta.permissions.require_admin is True
class TestStarClass:
"""Tests for Star base class."""
def test_star_is_new_star_by_default(self):
"""Star subclasses should be recognized as new-style."""
class MyPlugin(Star):
pass
assert MyPlugin.__astrbot_is_new_star__() is True
def test_star_collects_handler_names_from_decorators(self):
"""Star should collect decorated method names in __handlers__."""
class MyPlugin(Star):
@on_command("hello")
async def hello(self, event: MessageEvent, ctx: Context):
pass
@on_message()
async def on_msg(self, event: MessageEvent, ctx: Context):
pass
assert "hello" in MyPlugin.__handlers__
assert "on_msg" in MyPlugin.__handlers__
@pytest.mark.asyncio
async def test_star_on_error_calls_reply(self):
"""Star.on_error should call event.reply with error message."""
replies = []
class MyPlugin(Star):
pass
plugin = MyPlugin()
# Create event with mock reply handler
event = MessageEvent(text="test", session_id="s1")
event.bind_reply_handler(lambda text: replies.append(text) or asyncio.sleep(0))
# Create context (not used in default impl)
ctx = None
# on_error should call reply
await plugin.on_error(RuntimeError("test error"), event, ctx)
assert len(replies) == 1
assert "问题" in replies[0]
class TestTriggerModels:
"""Tests for trigger model validation."""
def test_command_trigger_validation(self):
"""CommandTrigger should validate command name."""
trigger = CommandTrigger(command="hello")
assert trigger.command == "hello"
def test_message_trigger_optional_keywords(self):
"""MessageTrigger should have optional keywords."""
trigger = MessageTrigger()
assert trigger.keywords == []
trigger_with_keywords = MessageTrigger(keywords=["a", "b"])
assert trigger_with_keywords.keywords == ["a", "b"]
def test_event_trigger_validation(self):
"""EventTrigger should store event type."""
trigger = EventTrigger(event_type="custom_event")
assert trigger.event_type == "custom_event"
def test_schedule_trigger_requires_one_strategy(self):
"""ScheduleTrigger should require exactly one strategy."""
with pytest.raises(ValueError):
ScheduleTrigger()
with pytest.raises(ValueError):
ScheduleTrigger(cron="* * * * *", interval_seconds=10)
trigger = ScheduleTrigger(interval_seconds=30)
assert trigger.interval_seconds == 30
import asyncio # For the async test