mirror of
https://github.com/AstrBotDevs/AstrBot
synced 2026-07-17 01:49:15 +08:00
243 lines
7.5 KiB
Python
243 lines
7.5 KiB
Python
"""
|
||
新架构合规性测试套件
|
||
|
||
根据 openspec 最高旨意:
|
||
1. 使用 anyio 作为异步库(不是 asyncio)
|
||
2. 类型标注完整
|
||
3. 代码美观
|
||
4. 最终目标:实现 ABP 协议
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import ast
|
||
import inspect
|
||
from unittest.mock import AsyncMock, MagicMock
|
||
|
||
import pytest
|
||
|
||
|
||
class TestAnyioCompliance:
|
||
"""测试是否遵守 anyio 异步库指令
|
||
|
||
根据 openspec:
|
||
- 异步库使用 anyio
|
||
- 不是 asyncio
|
||
|
||
注意: 这些测试验证是否使用 anyio,当发现使用 asyncio 时会失败。
|
||
这正是我们想要的行为 - 测试驱动开发。
|
||
"""
|
||
|
||
def test_orchestrator_run_loop_documents_asyncio_violation(self):
|
||
"""记录: orchestrator 使用 asyncio.sleep 而不是 anyio.sleep
|
||
|
||
VIOLATION: 当前代码使用 asyncio.sleep
|
||
需要修改为 anyio.sleep
|
||
"""
|
||
from astrbot._internal.runtime.orchestrator import AstrbotOrchestrator
|
||
|
||
source = inspect.getsource(AstrbotOrchestrator.run_loop)
|
||
|
||
# 检查是否使用了 asyncio
|
||
uses_asyncio = "asyncio.sleep" in source
|
||
|
||
if uses_asyncio:
|
||
pytest.fail(
|
||
"orchestrator.run_loop 使用了 asyncio.sleep,违反 openspec 指令\n"
|
||
"应该使用 anyio.sleep\n"
|
||
"per openspec directive: '异步库使用anyio'"
|
||
)
|
||
|
||
def test_orchestrator_documents_asyncio_cancelled_error_violation(self):
|
||
"""记录: orchestrator 捕获 asyncio.CancelledError
|
||
|
||
VIOLATION: 当前代码捕获 asyncio.CancelledError
|
||
需要修改为 anyio.CancelledError
|
||
"""
|
||
from astrbot._internal.runtime.orchestrator import AstrbotOrchestrator
|
||
|
||
source = inspect.getsource(AstrbotOrchestrator.run_loop)
|
||
|
||
uses_asyncio_cancelled = "asyncio.CancelledError" in source
|
||
|
||
if uses_asyncio_cancelled:
|
||
pytest.fail(
|
||
"orchestrator 捕获了 asyncio.CancelledError,违反 openspec 指令\n"
|
||
"应该捕获 anyio.CancelledError\n"
|
||
"per openspec directive: '异步库使用anyio'"
|
||
)
|
||
|
||
def test_mcp_client_documents_asyncio_lock_violation(self):
|
||
"""记录: MCP 客户端使用 asyncio.Lock
|
||
|
||
VIOLATION: 当前代码使用 asyncio.Lock
|
||
需要修改为 anyio.Lock
|
||
"""
|
||
from astrbot._internal.protocols.mcp.client import McpClient
|
||
import asyncio
|
||
|
||
client = McpClient()
|
||
|
||
uses_asyncio_lock = isinstance(client._reconnect_lock, asyncio.Lock)
|
||
|
||
if uses_asyncio_lock:
|
||
pytest.fail(
|
||
"MCP 客户端使用了 asyncio.Lock,违反 openspec 指令\n"
|
||
"应该使用 anyio.Lock\n"
|
||
"per openspec directive: '异步库使用anyio'"
|
||
)
|
||
|
||
|
||
class TestTypeAnnotations:
|
||
"""测试类型标注完整性
|
||
|
||
根据 openspec:
|
||
- 代码必须通过 ty check
|
||
- 避免使用 Any 和 cast
|
||
"""
|
||
|
||
def test_abp_client_has_type_annotations(self):
|
||
"""ABP 客户端应该有类型标注"""
|
||
from astrbot._internal.protocols.abp.client import AstrbotAbpClient
|
||
|
||
client = AstrbotAbpClient()
|
||
|
||
# 检查主要方法是否有返回类型
|
||
connect_ann = inspect.signature(client.connect).return_annotation
|
||
assert connect_ann in (None, "None")
|
||
|
||
shutdown_ann = inspect.signature(client.shutdown).return_annotation
|
||
assert shutdown_ann in (None, "None")
|
||
|
||
def test_abp_client_connected_property_has_type(self):
|
||
"""connected 属性应该有类型标注"""
|
||
from astrbot._internal.protocols.abp.client import AstrbotAbpClient
|
||
|
||
client = AstrbotAbpClient()
|
||
assert isinstance(client.connected, bool)
|
||
|
||
|
||
class TestCodeStyle:
|
||
"""测试代码风格
|
||
|
||
根据 openspec:
|
||
- 代码必须通过 ruff check
|
||
- 代码必须通过 ruff format
|
||
"""
|
||
|
||
def test_abp_client_noobvious_style_issues(self):
|
||
"""ABP 客户端没有明显的风格问题"""
|
||
from astrbot._internal.protocols.abp.client import AstrbotAbpClient
|
||
import ast
|
||
|
||
source = inspect.getsource(AstrbotAbpClient)
|
||
|
||
try:
|
||
ast.parse(source)
|
||
except SyntaxError as e:
|
||
pytest.fail(f"ABP client has syntax error: {e}")
|
||
|
||
def test_orchestrator_noobvious_style_issues(self):
|
||
"""Orchestrator 没有明显的风格问题"""
|
||
from astrbot._internal.runtime.orchestrator import AstrbotOrchestrator
|
||
import ast
|
||
|
||
source = inspect.getsource(AstrbotOrchestrator)
|
||
|
||
try:
|
||
ast.parse(source)
|
||
except SyntaxError as e:
|
||
pytest.fail(f"Orchestrator has syntax error: {e}")
|
||
|
||
|
||
class TestArchitectureGoals:
|
||
"""测试架构目标
|
||
|
||
根据 openspec:
|
||
- 最终目标:实现 ABP 协议
|
||
- 代码美观
|
||
- 类型标注完美
|
||
- 将现有功能迁移到新架构
|
||
"""
|
||
|
||
@pytest.fixture
|
||
def abp_client(self):
|
||
from astrbot._internal.protocols.abp.client import AstrbotAbpClient
|
||
return AstrbotAbpClient()
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_abp_client_meets_basic_protocol_requirements(self, abp_client):
|
||
"""ABP 客户端满足基本协议要求
|
||
|
||
根据 base_astrbot_abp_client.py ABC 定义:
|
||
- connect() -> None
|
||
- register_star(name, instance) -> None
|
||
- unregister_star(name) -> None
|
||
- call_star_tool(star, tool, args) -> Any
|
||
- shutdown() -> None
|
||
"""
|
||
await abp_client.connect()
|
||
assert abp_client.connected is True
|
||
|
||
# Test star registration
|
||
star = MagicMock()
|
||
star.call_tool = AsyncMock(return_value="ok")
|
||
abp_client.register_star("test-star", star)
|
||
|
||
# Test tool calling
|
||
result = await abp_client.call_star_tool("test-star", "test-tool", {})
|
||
assert result == "ok"
|
||
|
||
# Test unregistration
|
||
abp_client.unregister_star("test-star")
|
||
assert "test-star" not in abp_client._stars
|
||
|
||
# Test shutdown
|
||
await abp_client.shutdown()
|
||
assert abp_client.connected is False
|
||
|
||
def test_abp_client_has_abc_compliance(self):
|
||
"""ABP 客户端继承自 ABC"""
|
||
from astrbot._internal.protocols.abp.client import AstrbotAbpClient
|
||
from astrbot._internal.abc.abp.base_astrbot_abp_client import BaseAstrbotAbpClient
|
||
|
||
assert issubclass(AstrbotAbpClient, BaseAstrbotAbpClient)
|
||
|
||
|
||
class TestMigrationGuidance:
|
||
"""迁移指导测试
|
||
|
||
为将现有功能迁移到新架构提供测试支持
|
||
"""
|
||
|
||
@pytest.fixture
|
||
def orchestrator(self):
|
||
from astrbot._internal.runtime.orchestrator import AstrbotOrchestrator
|
||
return AstrbotOrchestrator()
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_orchestrator_provides_star_management(self, orchestrator):
|
||
"""Orchestrator 提供 star 管理功能"""
|
||
star = MagicMock()
|
||
star.call_tool = AsyncMock(return_value="ok")
|
||
|
||
# Register
|
||
await orchestrator.register_star("test-star", star)
|
||
assert await orchestrator.get_star("test-star") is star
|
||
|
||
# List
|
||
stars = await orchestrator.list_stars()
|
||
assert "test-star" in stars
|
||
|
||
# Unregister
|
||
await orchestrator.unregister_star("test-star")
|
||
assert await orchestrator.get_star("test-star") is None
|
||
|
||
@pytest.mark.asyncio
|
||
async def test_orchestrator_manages_all_protocol_clients(self, orchestrator):
|
||
"""Orchestrator 管理所有协议客户端"""
|
||
assert hasattr(orchestrator, "lsp")
|
||
assert hasattr(orchestrator, "mcp")
|
||
assert hasattr(orchestrator, "acp")
|
||
assert hasattr(orchestrator, "abp")
|