mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 07:56:52 +08:00
refactor(chain): remove dead data port indirection
This commit is contained in:
@@ -304,7 +304,6 @@ def configure_plugin_system_services():
|
||||
configure_chain_data_ports(
|
||||
site=site_repository,
|
||||
subscribe=lambda: SubscribeOper(),
|
||||
workflow=lambda: workflow_execution,
|
||||
download_history=lambda: DownloadHistoryOper(),
|
||||
transfer_history=lambda: TransferHistoryOper(),
|
||||
transfer_pending=lambda: TransactionalTransferAdmissionRepository(
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"application": {
|
||||
"covered_lines": 10001,
|
||||
"covered_lines": 9978,
|
||||
"percent": 78.79,
|
||||
"statements": 12694
|
||||
"statements": 12664
|
||||
},
|
||||
"domain": {
|
||||
"covered_lines": 3392,
|
||||
|
||||
+2
-7
@@ -1441,8 +1441,8 @@
|
||||
"runtime_only": true
|
||||
}
|
||||
},
|
||||
"edge_count": 6907,
|
||||
"edge_sha256": "8b3bb489cfc9573e377d0ee21337633b842b9969fc69fe99b86f547613b86a2a",
|
||||
"edge_count": 6902,
|
||||
"edge_sha256": "e2adb079d1df7415b81cbfa8358536e29e06276c2cb5af3a6f6c634e6f58fb20",
|
||||
"edges": [
|
||||
"app -> app.runtime",
|
||||
"app -> app.runtime.compat",
|
||||
@@ -3979,7 +3979,6 @@
|
||||
"app.application.backup -> app.runtime.log",
|
||||
"app.application.chain.context -> app.application",
|
||||
"app.application.chain.context -> app.application.chain",
|
||||
"app.application.chain.context -> app.application.chain.data",
|
||||
"app.application.chain.context -> app.application.chain.events",
|
||||
"app.application.chain.context -> app.application.configuration",
|
||||
"app.application.chain.context -> app.runtime",
|
||||
@@ -3988,7 +3987,6 @@
|
||||
"app.application.chain.data -> app.application.transfer",
|
||||
"app.application.chain.data -> app.application.transfer.execution",
|
||||
"app.application.chain.data -> app.application.transfer.workflow",
|
||||
"app.application.chain.data -> app.application.workflow",
|
||||
"app.application.chain.events -> app.application",
|
||||
"app.application.chain.events -> app.application.history",
|
||||
"app.application.chain.events -> app.application.transfer",
|
||||
@@ -4480,7 +4478,6 @@
|
||||
"app.chain -> app.application",
|
||||
"app.chain -> app.application.chain",
|
||||
"app.chain -> app.application.chain.context",
|
||||
"app.chain -> app.application.chain.data",
|
||||
"app.chain -> app.application.configuration",
|
||||
"app.chain -> app.chain._messaging",
|
||||
"app.chain -> app.chain._recognition",
|
||||
@@ -5065,8 +5062,6 @@
|
||||
"app.chain.webhook -> app.schemas",
|
||||
"app.chain.webhook -> app.schemas.types",
|
||||
"app.chain.workflow -> app.application",
|
||||
"app.chain.workflow -> app.application.chain",
|
||||
"app.chain.workflow -> app.application.chain.data",
|
||||
"app.chain.workflow -> app.application.workflow",
|
||||
"app.chain.workflow -> app.chain",
|
||||
"app.chain.workflow -> app.runtime",
|
||||
|
||||
@@ -1213,9 +1213,6 @@
|
||||
"E402": 4,
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_chain_runtime_context.py": {
|
||||
"I001": 1
|
||||
},
|
||||
"tests/test_cli_auto_update.py": {
|
||||
"I001": 1
|
||||
},
|
||||
|
||||
@@ -347,8 +347,8 @@ def test_workflow_query_adapter_owns_projection_sessions():
|
||||
assert "_project_workflow(record)" in source
|
||||
|
||||
|
||||
def test_workflow_execution_chain_uses_typed_transaction_port():
|
||||
"""工作流 Chain 写端不得再经过 raw Oper 或重复获取全局端口。"""
|
||||
def test_workflow_execution_chain_uses_single_application_owned_port():
|
||||
"""工作流 Chain 写端必须只使用 Application owner 的唯一配置入口。"""
|
||||
contract_path = APP_ROOT / "application" / "workflow.py"
|
||||
contract_tree = ast.parse(
|
||||
contract_path.read_text(encoding="utf-8"),
|
||||
@@ -384,29 +384,64 @@ def test_workflow_execution_chain_uses_typed_transaction_port():
|
||||
for node in data_tree.body
|
||||
if isinstance(node, ast.ClassDef) and node.name == "ChainDataPorts"
|
||||
)
|
||||
workflow_field = next(
|
||||
node
|
||||
data_fields = {
|
||||
node.target.id
|
||||
for node in data_class.body
|
||||
if isinstance(node, ast.AnnAssign)
|
||||
and isinstance(node.target, ast.Name)
|
||||
and node.target.id == "workflow"
|
||||
)
|
||||
workflow_getter = next(
|
||||
node
|
||||
}
|
||||
data_functions = {
|
||||
node.name
|
||||
for node in data_tree.body
|
||||
if isinstance(node, ast.FunctionDef)
|
||||
and node.name == "get_chain_workflow_port"
|
||||
)
|
||||
assert ast.unparse(workflow_field.annotation) == "WorkflowExecutionPortFactory"
|
||||
assert ast.unparse(workflow_getter.returns) == "WorkflowExecutionPort"
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
}
|
||||
assert "workflow" not in data_fields
|
||||
assert "get_chain_workflow_port" not in data_functions
|
||||
|
||||
chain_source = (APP_ROOT / "chain" / "workflow.py").read_text(encoding="utf-8")
|
||||
startup_source = (
|
||||
APP_ROOT / "startup" / "initializers" / "modules.py"
|
||||
).read_text(encoding="utf-8")
|
||||
assert chain_source.count("get_chain_workflow_port()") == 1
|
||||
assert "workflow=lambda: workflow_execution" in startup_source
|
||||
assert "workflow=lambda: WorkflowOper()" not in startup_source
|
||||
assert chain_source.count("get_configured_workflow_execution()") == 1
|
||||
assert "get_chain_workflow_port" not in chain_source
|
||||
assert "configure_workflow_execution(workflow_execution)" in startup_source
|
||||
assert "workflow=lambda:" not in startup_source
|
||||
|
||||
|
||||
def test_chain_registry_has_no_dynamic_proxies_or_dead_context_injection():
|
||||
"""Chain registry 不得恢复零消费者动态代理或失效 data_ports 伪注入。"""
|
||||
data_path = APP_ROOT / "application" / "chain" / "data.py"
|
||||
data_tree = ast.parse(
|
||||
data_path.read_text(encoding="utf-8"),
|
||||
filename=str(data_path),
|
||||
)
|
||||
proxy_classes = {
|
||||
node.name
|
||||
for node in data_tree.body
|
||||
if isinstance(node, ast.ClassDef)
|
||||
and (node.name.endswith("PortProxy") or node.name == "_PortProxyMeta")
|
||||
}
|
||||
dynamic_getters = {
|
||||
node.name
|
||||
for node in ast.walk(data_tree)
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
|
||||
and node.name == "__getattr__"
|
||||
}
|
||||
assert proxy_classes == set()
|
||||
assert dynamic_getters == set()
|
||||
|
||||
context_source = (
|
||||
APP_ROOT / "application" / "chain" / "context.py"
|
||||
).read_text(encoding="utf-8")
|
||||
chain_base_source = (APP_ROOT / "chain" / "__init__.py").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
startup_source = (
|
||||
APP_ROOT / "startup" / "initializers" / "modules.py"
|
||||
).read_text(encoding="utf-8")
|
||||
assert "data_ports" not in context_source
|
||||
assert "self.data_ports" not in chain_base_source
|
||||
assert "data_ports=" not in startup_source
|
||||
|
||||
|
||||
def test_canonical_workflow_oper_has_no_legacy_writer_or_duplicate_exports():
|
||||
|
||||
@@ -2,9 +2,12 @@
|
||||
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from app.application.chain import context as chain_context
|
||||
from app.application.chain import data as chain_data
|
||||
from app.application.chain.context import ChainRuntimeContext
|
||||
from app.application.configuration import ChainRuntimeConfig
|
||||
from app.application.chain import context as chain_context
|
||||
from app.chain import ChainBase
|
||||
from app.runtime.extensions.module.dispatcher import ModuleInvocationDispatcher
|
||||
|
||||
@@ -51,3 +54,42 @@ def test_no_arg_chain_uses_compatibility_context_provider(monkeypatch) -> None:
|
||||
provider.assert_called_once_with()
|
||||
assert chain.modulemanager is context.module_manager
|
||||
assert chain.pluginmanager is context.plugin_manager
|
||||
|
||||
|
||||
def test_chain_runtime_context_rejects_unconfigured_provider(monkeypatch) -> None:
|
||||
"""未由组合根配置运行上下文时必须显式拒绝无参 Chain。"""
|
||||
monkeypatch.setattr(
|
||||
chain_context,
|
||||
"_context_provider",
|
||||
chain_context._unconfigured_chain_runtime_context,
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="Chain 运行上下文尚未由启动组合根配置"):
|
||||
chain_context.get_chain_runtime_context()
|
||||
|
||||
|
||||
def test_chain_data_registry_rejects_unconfigured_and_returns_factories(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""数据 registry 未配置时拒绝访问,配置后按字段返回工厂实例。"""
|
||||
monkeypatch.setattr(chain_data, "_ports", None)
|
||||
|
||||
with pytest.raises(RuntimeError, match="Chain 数据端口尚未配置"):
|
||||
chain_data.get_chain_data_ports()
|
||||
|
||||
media_server = Mock()
|
||||
user = Mock()
|
||||
chain_data.configure_chain_data_ports(
|
||||
site=Mock,
|
||||
subscribe=Mock,
|
||||
download_history=Mock,
|
||||
transfer_history=Mock,
|
||||
transfer_pending=Mock,
|
||||
transfer_execution=Mock,
|
||||
media_server=lambda: media_server,
|
||||
download_failure=Mock,
|
||||
user=lambda: user,
|
||||
)
|
||||
|
||||
assert chain_data.get_chain_media_server_port() is media_server
|
||||
assert chain_data.get_chain_user_port() is user
|
||||
|
||||
@@ -643,7 +643,11 @@ def test_workflow_chain_process_serializes_circular_context(monkeypatch):
|
||||
return fake_oper
|
||||
|
||||
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: fake_manager)
|
||||
monkeypatch.setattr(workflow_module, "get_chain_workflow_port", get_execution_port)
|
||||
monkeypatch.setattr(
|
||||
workflow_module,
|
||||
"get_configured_workflow_execution",
|
||||
get_execution_port,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
workflow_module,
|
||||
"get_configured_workflow_query",
|
||||
@@ -952,7 +956,11 @@ def test_workflow_chain_rejects_execution_before_persisting_running_state(monkey
|
||||
workflowoper = _FakeWorkflowOper(workflow)
|
||||
manager = RejectingWorkflowManager([])
|
||||
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: manager)
|
||||
monkeypatch.setattr(workflow_module, "get_chain_workflow_port", lambda: workflowoper)
|
||||
monkeypatch.setattr(
|
||||
workflow_module,
|
||||
"get_configured_workflow_execution",
|
||||
lambda: workflowoper,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
workflow_module,
|
||||
"get_configured_workflow_query",
|
||||
@@ -995,7 +1003,11 @@ def test_workflow_chain_releases_admitted_owner_when_start_fails(monkeypatch):
|
||||
manager._executions = {}
|
||||
workflowoper = FailingWorkflowOper(_build_workflow())
|
||||
monkeypatch.setattr(workflow_module, "get_workflow_manager", lambda: manager)
|
||||
monkeypatch.setattr(workflow_module, "get_chain_workflow_port", lambda: workflowoper)
|
||||
monkeypatch.setattr(
|
||||
workflow_module,
|
||||
"get_configured_workflow_execution",
|
||||
lambda: workflowoper,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
workflow_module,
|
||||
"get_configured_workflow_query",
|
||||
|
||||
Reference in New Issue
Block a user