Files
AstrBot/tests/unit/test_internal/test_architecture_compliance.py
LIghtJUNction c7c878a8e2 feat(dashboard): blueprint light mode theme
- Surface/background: #F0F4F8 cool blue-gray lab white
- On-surface text: #1A2B3C deep indigo
- ContainerBg: translucent rgba glass for light mode
- Surface-variant: slightly deeper cool tone
2026-03-29 17:07:12 +08:00

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")