Files
MoviePilot/tests/test_agent_composition.py

151 lines
4.6 KiB
Python

"""Agent 启动组合根的对象身份与装配时序测试。"""
from __future__ import annotations
import json
import subprocess
import sys
from types import SimpleNamespace
from app.startup.composition import agent as agent_composition
from app.startup.composition.runtime import RuntimeDependencies
class _StartedWorker:
"""仅允许在已启动状态读取容量的数据库 worker 桩。"""
def __init__(self, events: list[str]) -> None:
"""保存顺序记录并模拟已完成启动。"""
self.started = True
self.events = events
def snapshot(self) -> SimpleNamespace:
"""确认启动已完成后返回测试容量。"""
assert self.started is True
self.events.append("read-capacity")
return SimpleNamespace(capacity=17)
def _compose() -> tuple[agent_composition.AgentComposition, _StartedWorker]:
"""构造一组无需真实数据库连接的 Agent composition。"""
events = ["worker-started"]
worker = _StartedWorker(events)
runtime = SimpleNamespace(
worker=worker,
transaction=SimpleNamespace(sync=lambda operation: operation(object())),
)
system_config = SimpleNamespace(publish_many=lambda _values: None)
dependencies = RuntimeDependencies(
download_history=object(),
transfer_history=object(),
site=object(),
subscription=object(),
subscription_history=object(),
transfer_execution=object(),
message_helper=object(),
message_queue=object(),
)
composition = agent_composition.compose_agent(
runtime=runtime,
system_config=system_config,
dependencies=dependencies,
)
return composition, worker
def test_agent_composition_reuses_shared_objects_and_started_worker() -> None:
"""HostRuntime、Agent 数据与任务入口必须复用同一批对象。"""
composition, worker = _compose()
assert composition.data.tasks is composition.tasks
assert composition.persistence is composition.data.chat_persistence
assert composition.data.chat_persistence._async_executor is worker
assert composition.data.chat_persistence._capacity == 17
assert composition.execution._async_executor is worker
assert worker.events == ["worker-started", "read-capacity"]
def test_publish_agent_services_registers_composed_identity(monkeypatch) -> None:
"""兼容 provider 与 Agent initializer 必须收到 composition 中的原对象。"""
composition, _worker = _compose()
published: dict[str, object] = {}
monkeypatch.setattr(
agent_composition,
"configure_agent_chat_service",
lambda service: published.update(chat=service),
)
monkeypatch.setattr(
agent_composition,
"configure_agent_chat_persistence",
lambda service: published.update(persistence=service),
)
monkeypatch.setattr(
agent_composition,
"configure_agent_task_execution",
lambda service: published.update(execution=service),
)
agent_composition.publish_agent_services(
composition,
data_context_registrar=lambda data: published.update(data=data),
)
assert published == {
"chat": composition.data.chat,
"persistence": composition.data.chat_persistence,
"execution": composition.execution,
"data": composition.data,
}
def test_agent_composition_import_is_cold() -> None:
"""导入 Agent composition 不得登记服务或加载 Agent 实现层。"""
completed = subprocess.run(
[
sys.executable,
"-c",
r"""
import json
import sys
from app.application.agenttask import get_agent_task_execution_service
from app.application.messaging.chat import (
get_configured_agent_chat_persistence,
get_configured_agent_chat_service,
)
import app.startup.composition.agent
unconfigured = []
for getter in (
get_agent_task_execution_service,
get_configured_agent_chat_persistence,
get_configured_agent_chat_service,
):
try:
getter()
except RuntimeError:
unconfigured.append(True)
loaded_agent_implementation = sorted(
name for name in sys.modules if name == "app.agent" or name.startswith("app.agent.")
)
print(json.dumps({
"initializer_loaded": "app.startup.initializers.agent" in sys.modules,
"loaded_agent_implementation": loaded_agent_implementation,
"unconfigured": len(unconfigured),
}))
""",
],
check=True,
capture_output=True,
text=True,
)
assert json.loads(completed.stdout) == {
"initializer_loaded": False,
"loaded_agent_implementation": [],
"unconfigured": 3,
}