mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-08-29 12:06:51 +08:00
145 lines
4.5 KiB
Python
145 lines
4.5 KiB
Python
from types import SimpleNamespace
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from app.foundation.singleton import Singleton
|
|
from app.runtime.extensions.plugin.monitor import PluginMonitorController
|
|
from app.runtime.extensions.plugin.system import reset_plugin_system
|
|
from app.runtime.extensions.plugin_manager import PluginManager
|
|
from app.startup import plugins_initializer
|
|
|
|
|
|
def _reset_plugin_manager() -> None:
|
|
"""清除插件管理器单例,保证构造时序测试彼此隔离。"""
|
|
Singleton._instances.pop((PluginManager, (), frozenset()), None)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("dev", "auto_reload"),
|
|
((True, False), (False, True)),
|
|
)
|
|
def test_plugin_manager_constructor_does_not_start_monitor_before_runtime(
|
|
monkeypatch,
|
|
dev: bool,
|
|
auto_reload: bool,
|
|
) -> None:
|
|
"""组合根尚未装配时,构造插件管理器不得提前启动开发监控线程。"""
|
|
_reset_plugin_manager()
|
|
reset_plugin_system()
|
|
start = MagicMock()
|
|
monkeypatch.setattr(
|
|
"app.runtime.extensions.plugin_manager.settings",
|
|
SimpleNamespace(
|
|
DEV=dev,
|
|
PLUGIN_AUTO_RELOAD=auto_reload,
|
|
ROOT_PATH=MagicMock(),
|
|
),
|
|
)
|
|
monkeypatch.setattr(PluginMonitorController, "start", start)
|
|
|
|
PluginManager()
|
|
|
|
start.assert_not_called()
|
|
_reset_plugin_manager()
|
|
|
|
|
|
def test_init_plugins_starts_monitor_after_runtime_and_routes(monkeypatch) -> None:
|
|
"""插件运行时和动态路由就绪后,启动层才允许文件监控接收变化。"""
|
|
order: list[str] = []
|
|
manager = MagicMock()
|
|
manager.start.side_effect = lambda: order.append("plugins")
|
|
manager.start_monitor.side_effect = lambda: order.append("monitor")
|
|
monkeypatch.setattr(
|
|
plugins_initializer,
|
|
"configure_plugin_services",
|
|
lambda: order.append("services"),
|
|
)
|
|
monkeypatch.setattr(plugins_initializer, "PluginManager", lambda: manager)
|
|
monkeypatch.setattr(
|
|
plugins_initializer,
|
|
"register_plugin_api",
|
|
lambda: order.append("routes"),
|
|
)
|
|
|
|
plugins_initializer.init_plugins()
|
|
|
|
assert order == ["services", "plugins", "routes", "monitor"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("dev", "auto_reload", "expected_calls"),
|
|
((True, False, 1), (False, True, 1), (False, False, 0)),
|
|
)
|
|
def test_start_monitor_respects_runtime_configuration(
|
|
monkeypatch,
|
|
dev: bool,
|
|
auto_reload: bool,
|
|
expected_calls: int,
|
|
) -> None:
|
|
"""首次启动只在开发模式或插件自动重载启用时创建监控线程。"""
|
|
_reset_plugin_manager()
|
|
reset_plugin_system()
|
|
monkeypatch.setattr(
|
|
"app.runtime.extensions.plugin_manager.settings",
|
|
SimpleNamespace(
|
|
DEV=dev,
|
|
PLUGIN_AUTO_RELOAD=auto_reload,
|
|
ROOT_PATH=MagicMock(),
|
|
),
|
|
)
|
|
manager = PluginManager()
|
|
start = MagicMock()
|
|
manager._plugin_monitor.start = start
|
|
|
|
manager.start_monitor()
|
|
|
|
assert start.call_count == expected_calls
|
|
_reset_plugin_manager()
|
|
|
|
|
|
def test_config_change_reloads_monitor(monkeypatch) -> None:
|
|
"""配置热更新继续使用重建语义,不复用首次启动入口。"""
|
|
_reset_plugin_manager()
|
|
reset_plugin_system()
|
|
monkeypatch.setattr(
|
|
"app.runtime.extensions.plugin_manager.settings",
|
|
SimpleNamespace(
|
|
DEV=False,
|
|
PLUGIN_AUTO_RELOAD=False,
|
|
ROOT_PATH=MagicMock(),
|
|
),
|
|
)
|
|
manager = PluginManager()
|
|
reload_monitor = MagicMock()
|
|
manager.reload_monitor = reload_monitor
|
|
|
|
manager.on_config_changed()
|
|
|
|
reload_monitor.assert_called_once_with()
|
|
_reset_plugin_manager()
|
|
|
|
|
|
def test_stop_plugins_stops_monitor_before_plugin_runtime(monkeypatch) -> None:
|
|
"""关闭时先隔离文件变化,再停止插件实例。"""
|
|
order: list[str] = []
|
|
manager = MagicMock()
|
|
manager.stop_monitor.side_effect = lambda: order.append("monitor")
|
|
manager.stop.side_effect = lambda: order.append("plugins")
|
|
monkeypatch.setattr(plugins_initializer, "PluginManager", lambda: manager)
|
|
|
|
plugins_initializer.stop_plugins()
|
|
|
|
assert order == ["monitor", "plugins"]
|
|
|
|
|
|
def test_stop_plugins_still_stops_runtime_when_monitor_stop_fails(monkeypatch) -> None:
|
|
"""监控线程停止异常不得阻止插件实例释放资源。"""
|
|
manager = MagicMock()
|
|
manager.stop_monitor.side_effect = RuntimeError("monitor stop failed")
|
|
monkeypatch.setattr(plugins_initializer, "PluginManager", lambda: manager)
|
|
|
|
plugins_initializer.stop_plugins()
|
|
|
|
manager.stop.assert_called_once_with()
|