mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-16 01:40:15 +08:00
215 lines
6.1 KiB
Python
215 lines
6.1 KiB
Python
# Test configuration
|
|
import asyncio
|
|
from collections.abc import Generator
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
# 将 src-new 加入路径 - 这使得测试可以运行,但不算"已安装"
|
|
import sys
|
|
|
|
SRC_NEW_PATH = str(Path(__file__).parent.parent / "src-new")
|
|
sys.path.insert(0, SRC_NEW_PATH)
|
|
|
|
|
|
# ============================================================
|
|
# Async Configuration
|
|
# ============================================================
|
|
|
|
|
|
@pytest.fixture(scope="session")
|
|
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
|
|
"""Create an event loop for async tests."""
|
|
policy = asyncio.get_event_loop_policy()
|
|
loop = policy.new_event_loop()
|
|
yield loop
|
|
loop.close()
|
|
|
|
|
|
# ============================================================
|
|
# Transport Fixtures
|
|
# ============================================================
|
|
|
|
|
|
class MemoryTransport:
|
|
"""In-memory transport for testing peer communication."""
|
|
|
|
def __init__(self) -> None:
|
|
self._closed = asyncio.Event()
|
|
self._message_handler = None
|
|
self.partner: MemoryTransport | None = None
|
|
|
|
def set_message_handler(self, handler) -> None:
|
|
self._message_handler = handler
|
|
|
|
async def start(self) -> None:
|
|
self._closed.clear()
|
|
|
|
async def stop(self) -> None:
|
|
self._closed.set()
|
|
|
|
async def wait_closed(self) -> None:
|
|
await self._closed.wait()
|
|
|
|
async def send(self, payload: str) -> None:
|
|
if self.partner is None:
|
|
raise RuntimeError("MemoryTransport 未连接 partner")
|
|
if self.partner._message_handler is not None:
|
|
await self.partner._message_handler(payload)
|
|
|
|
async def _dispatch(self, payload: str) -> None:
|
|
if self._message_handler is not None:
|
|
await self._message_handler(payload)
|
|
|
|
|
|
@pytest.fixture
|
|
def transport_pair() -> tuple[MemoryTransport, MemoryTransport]:
|
|
"""Create a connected pair of in-memory transports."""
|
|
left = MemoryTransport()
|
|
right = MemoryTransport()
|
|
left.partner = right
|
|
right.partner = left
|
|
return left, right
|
|
|
|
|
|
# ============================================================
|
|
# Mock/Fake Fixtures
|
|
# ============================================================
|
|
|
|
|
|
class FakeEnvManager:
|
|
"""Fake environment manager for testing."""
|
|
|
|
def prepare_environment(self, _plugin: Any) -> Path:
|
|
return Path(sys.executable)
|
|
|
|
|
|
@pytest.fixture
|
|
def fake_env_manager() -> FakeEnvManager:
|
|
"""Provide a fake environment manager."""
|
|
return FakeEnvManager()
|
|
|
|
|
|
# ============================================================
|
|
# Peer Fixtures
|
|
# ============================================================
|
|
|
|
from astrbot_sdk.protocol.messages import InitializeOutput, PeerInfo
|
|
from astrbot_sdk.runtime.capability_router import CapabilityRouter
|
|
from astrbot_sdk.runtime.peer import Peer
|
|
|
|
|
|
@pytest.fixture
|
|
async def core_peer(transport_pair: tuple[MemoryTransport, MemoryTransport]) -> Peer:
|
|
"""Create a core peer with default handlers."""
|
|
left, _ = transport_pair
|
|
router = CapabilityRouter()
|
|
|
|
peer = Peer(
|
|
transport=left,
|
|
peer_info=PeerInfo(name="core", role="core", version="v4"),
|
|
)
|
|
|
|
async def init_handler(_message) -> InitializeOutput:
|
|
return InitializeOutput(
|
|
peer=PeerInfo(name="core", role="core", version="v4"),
|
|
capabilities=router.descriptors(),
|
|
metadata={},
|
|
)
|
|
|
|
async def invoke_handler(message, token):
|
|
return await router.execute(
|
|
message.capability,
|
|
message.input,
|
|
stream=message.stream,
|
|
cancel_token=token,
|
|
request_id=message.id,
|
|
)
|
|
|
|
peer.set_initialize_handler(init_handler)
|
|
peer.set_invoke_handler(invoke_handler)
|
|
|
|
await peer.start()
|
|
yield peer
|
|
await peer.stop()
|
|
|
|
|
|
@pytest.fixture
|
|
async def plugin_peer(transport_pair: tuple[MemoryTransport, MemoryTransport]) -> Peer:
|
|
"""Create a plugin peer connected to core."""
|
|
_, right = transport_pair
|
|
|
|
peer = Peer(
|
|
transport=right,
|
|
peer_info=PeerInfo(name="plugin", role="plugin", version="v4"),
|
|
)
|
|
|
|
await peer.start()
|
|
yield peer
|
|
await peer.stop()
|
|
|
|
|
|
# ============================================================
|
|
# Temporary Plugin Fixtures
|
|
# ============================================================
|
|
|
|
import tempfile
|
|
import textwrap
|
|
|
|
|
|
@pytest.fixture
|
|
def temp_plugin_dir() -> Generator[Path, None, None]:
|
|
"""Create a temporary directory for plugin testing."""
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
yield Path(temp_dir)
|
|
|
|
|
|
def create_test_plugin(plugin_root: Path, name: str = "test_plugin") -> None:
|
|
"""Helper to create a minimal test plugin."""
|
|
(plugin_root / "commands").mkdir(parents=True, exist_ok=True)
|
|
(plugin_root / "commands" / "__init__.py").write_text("", encoding="utf-8")
|
|
(plugin_root / "requirements.txt").write_text("", encoding="utf-8")
|
|
(plugin_root / "plugin.yaml").write_text(
|
|
textwrap.dedent(
|
|
f"""\
|
|
_schema_version: 2
|
|
name: {name}
|
|
display_name: Test Plugin
|
|
desc: test
|
|
author: tester
|
|
version: 0.1.0
|
|
runtime:
|
|
python: "{sys.version_info.major}.{sys.version_info.minor}"
|
|
components:
|
|
- class: commands.sample:TestPlugin
|
|
type: command
|
|
name: test
|
|
description: test command
|
|
"""
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
(plugin_root / "commands" / "sample.py").write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
from astrbot_sdk import Context, MessageEvent, Star, on_command
|
|
|
|
|
|
class TestPlugin(Star):
|
|
@on_command("test")
|
|
async def test_cmd(self, event: MessageEvent, ctx: Context):
|
|
await event.reply("test ok")
|
|
"""
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def test_plugin(temp_plugin_dir: Path) -> Path:
|
|
"""Create a test plugin and return its root directory."""
|
|
plugin_root = temp_plugin_dir / "plugins" / "test_plugin"
|
|
create_test_plugin(plugin_root)
|
|
return temp_plugin_dir / "plugins"
|