Files
MoviePilot/tests/test_workflow_invoke_plugin.py
Aqr-K d44480d739 Merge pull request #6666 from Aqr-K/feat/plugin-instance-target-enablement
feat(plugin): 插件实例默认调用目标与启停,未指定实例时不再兜底取第一个
2026-09-13 16:37:27 +08:00

125 lines
4.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""插件工作流动作标识的公开契约与执行回归。"""
from unittest.mock import Mock, patch
from app.schemas.workflow import ActionContext
from app.workflow.actions.invoke_plugin import InvokePluginAction
def _execute_with_action(action: dict) -> tuple[InvokePluginAction, ActionContext]:
"""在最小运行时替身中执行一个插件动作。"""
context = ActionContext(content="before")
action_fn = Mock(return_value=(True, context))
action["func"] = action_fn
plugin_manager = Mock()
plugin_manager.get_plugin_actions.return_value = [
{"plugin_id": "plugin-a", "actions": [action]}
]
with patch(
"app.workflow.actions.get_configured_system_config",
return_value=Mock(),
), patch(
"app.workflow.actions.invoke_plugin.get_plugin_manager",
return_value=plugin_manager,
):
action_runner = InvokePluginAction("invoke")
result = action_runner.execute(
workflow_id=1,
params={
"plugin_id": "plugin-a",
"action_id": "cleanup",
"action_params": {"force": True},
},
context=context,
)
action_fn.assert_called_once_with(context, force=True)
assert action_runner.success is True
assert action_runner.done is True
return action_runner, result
def test_invoke_plugin_uses_public_action_id() -> None:
"""插件公开的 id 字段必须可被工作流执行器解析。"""
_, result = _execute_with_action({"id": "cleanup"})
assert result.content == "before"
def test_invoke_plugin_keeps_legacy_action_id_fallback() -> None:
"""历史插件声明仍可通过 action_id 回退执行。"""
_, result = _execute_with_action({"action_id": "cleanup"})
assert result.content == "before"
def test_invoke_plugin_dispatches_to_resolved_default_target() -> None:
"""插件 ID 有分身时,动作按裁决出的默认调用目标查询动作,而不是原样使用插件 ID。
这是历史工作流在源插件本体停用、仅分身启用后仍能继续工作的关键:存量工作流
保存的还是物理插件 ID必须经过默认调用目标裁决才能落到实际在跑的分身上。
"""
context = ActionContext(content="before")
action = {"id": "cleanup"}
action_fn = Mock(return_value=(True, context))
action["func"] = action_fn
plugin_manager = Mock()
plugin_manager.resolve_plugin_call_target.return_value = "plugin-a-clone"
plugin_manager.get_plugin_actions.return_value = [
{"plugin_id": "plugin-a-clone", "actions": [action]}
]
with patch(
"app.workflow.actions.get_configured_system_config",
return_value=Mock(),
), patch(
"app.workflow.actions.invoke_plugin.get_plugin_manager",
return_value=plugin_manager,
):
action_runner = InvokePluginAction("invoke")
action_runner.execute(
workflow_id=1,
params={
"plugin_id": "plugin-a",
"action_id": "cleanup",
"action_params": {},
},
context=context,
)
plugin_manager.resolve_plugin_call_target.assert_called_once_with("plugin-a")
plugin_manager.get_plugin_actions.assert_called_once_with("plugin-a-clone")
assert action_runner.success is True
def test_invoke_plugin_fails_gracefully_when_default_target_undecidable() -> None:
"""裁决报错(未设默认目标/默认目标已停用)时动作失败但不抛出,不误执行任何实例。"""
context = ActionContext(content="before")
plugin_manager = Mock()
plugin_manager.resolve_plugin_call_target.side_effect = LookupError(
"插件 plugin-a 未设置默认实例调用必须显式指定实例可选实例a已启用、b已启用"
)
with patch(
"app.workflow.actions.get_configured_system_config",
return_value=Mock(),
), patch(
"app.workflow.actions.invoke_plugin.get_plugin_manager",
return_value=plugin_manager,
):
action_runner = InvokePluginAction("invoke")
result = action_runner.execute(
workflow_id=1,
params={
"plugin_id": "plugin-a",
"action_id": "cleanup",
"action_params": {},
},
context=context,
)
plugin_manager.get_plugin_actions.assert_not_called()
assert action_runner.success is False
assert result.content == "before"