Files
MoviePilot/tests/test_application_messaging_agent_architecture.py
2026-09-16 07:17:07 +08:00

102 lines
3.9 KiB
Python

"""Agent 消息应用层职责拆分门禁。"""
import ast
import importlib
from pathlib import Path
PROJECT_ROOT = Path(__file__).parents[1]
MESSAGING_ROOT = PROJECT_ROOT / "app" / "application" / "messaging"
AGENT_FACADE_PATH = MESSAGING_ROOT / "agent.py"
AGENT_INTERACTION_PATH = MESSAGING_ROOT / "interaction" / "agent.py"
CHANNEL_ADMIN_PATH = MESSAGING_ROOT / "channel" / "admin.py"
WEB_AGENT_ROOT = MESSAGING_ROOT / "webagent"
WEB_AGENT_EVENTS_PATH = WEB_AGENT_ROOT / "events.py"
WEB_AGENT_STREAM_PATH = WEB_AGENT_ROOT / "stream.py"
def _top_level_owners(path: Path) -> set[str]:
"""返回模块顶层定义的类和函数名称。"""
tree = ast.parse(path.read_text(encoding="utf-8-sig"), filename=str(path))
return {
node.name
for node in tree.body
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef))
}
def test_agent_interaction_has_one_canonical_owner() -> None:
"""Agent 选择交互只能在独立模块实现,根模块不再暴露重复入口。"""
names = {
"AgentInteractionManager",
"AgentInteractionOption",
"PendingAgentInteraction",
"build_agent_choice_button_rows",
"build_agent_choice_callback",
"parse_agent_choice_callback",
}
facade = importlib.import_module("app.application.messaging.agent")
importlib.import_module("app.application.messaging.interaction.agent")
assert names <= _top_level_owners(AGENT_INTERACTION_PATH)
assert names.isdisjoint(_top_level_owners(AGENT_FACADE_PATH))
assert all(not hasattr(facade, name) for name in names | {"agent_interaction_manager"})
def test_channel_admin_has_one_canonical_owner() -> None:
"""渠道管理员匹配只能在权限模块实现,根 Agent 模块不再暴露重复入口。"""
names = {
"matches_channel_admin",
"register_channel_admin_resolver",
"resolve_config_principal_ids",
}
facade = importlib.import_module("app.application.messaging.agent")
importlib.import_module("app.application.messaging.channel.admin")
assert names <= _top_level_owners(CHANNEL_ADMIN_PATH)
assert names.isdisjoint(_top_level_owners(AGENT_FACADE_PATH))
assert all(not hasattr(facade, name) for name in names)
def test_web_agent_event_bridge_has_one_canonical_owner() -> None:
"""WebAgent 通知与编辑桥接只能在独立模块实现,根 Agent 模块不再暴露重复入口。"""
names = {
"attach_web_agent_edit_queue",
"attach_web_agent_message_queue",
"build_web_agent_message_update_event",
"detach_web_agent_edit_queue",
"detach_web_agent_message_queue",
"dispatch_web_agent_edit_event",
"dispatch_web_agent_message_event",
"edit_web_agent_message",
"extract_web_agent_message_from_event_data",
"is_web_agent_message_for_user",
"normalize_web_agent_button_rows",
}
facade = importlib.import_module("app.application.messaging.agent")
importlib.import_module("app.application.messaging.webagent.events")
assert names <= _top_level_owners(WEB_AGENT_EVENTS_PATH)
assert names.isdisjoint(_top_level_owners(AGENT_FACADE_PATH))
assert all(not hasattr(facade, name) for name in names)
def test_web_agent_modules_share_one_subpackage() -> None:
"""同前缀 WebAgent 实现集中在子包,根目录不得保留平级门面。"""
names = {
"WebAgentStreamDependencies",
"build_agent_web_agent_stream",
"submit_web_agent_steering",
}
canonical = importlib.import_module(
"app.application.messaging.webagent.stream"
)
assert names <= _top_level_owners(WEB_AGENT_STREAM_PATH)
assert {path.name for path in WEB_AGENT_ROOT.glob("*.py")} == {
"__init__.py",
"events.py",
"stream.py",
}
assert not list(MESSAGING_ROOT.glob("webagent*.py"))
assert all(hasattr(canonical, name) for name in names)