"""A plugin enabled while Steward runs is loaded live; one that can't be is recorded with its reason, so its settings page says why and offers a restart instead of a bare "Enabled, not loaded" (#6087).""" from __future__ import annotations import sys from types import SimpleNamespace import pytest from steward.core import plugin_manager as pm @pytest.fixture(autouse=True) def clean_registries(monkeypatch): monkeypatch.setattr(pm, "_LOADED_PLUGINS", set()) monkeypatch.setattr(pm, "_FAILED_PLUGINS", {}) monkeypatch.setattr(pm, "_PLUGIN_NAV", []) monkeypatch.setattr(pm, "_PLUGIN_MODULES", {}) yield for key in [k for k in sys.modules if k.startswith("_steward_plugin_hl_")]: sys.modules.pop(key) def _app(root): return SimpleNamespace(config={"PLUGIN_DIRS": [str(root)], "PLUGINS": {}}, _task_registry=[], mcp_server=None) def _plugin(root, name, *, yaml_name=None, setup_body="pass"): pdir = root / name pdir.mkdir(parents=True) (pdir / "plugin.yaml").write_text(f"name: {yaml_name or name}\nversion: 1.0.0\n") (pdir / "__init__.py").write_text( f"def setup(app):\n {setup_body}\n\n" "def get_scheduled_tasks():\n return []\n") def test_a_plugin_that_cannot_load_live_is_recorded_with_its_reason(tmp_path): _plugin(tmp_path, "hl_broken", setup_body="raise RuntimeError('no controller')") ok, message = pm.hot_reload_plugin(_app(tmp_path), "hl_broken") assert not ok assert pm.get_plugin_failures() == {"hl_broken": "setup() raised: no controller"} assert "hl_broken" not in pm._LOADED_PLUGINS def test_an_early_refusal_is_recorded_too(tmp_path): _plugin(tmp_path, "hl_misnamed", yaml_name="other") ok, _ = pm.hot_reload_plugin(_app(tmp_path), "hl_misnamed") assert not ok and "name mismatch" in pm.get_plugin_failures()["hl_misnamed"] def test_a_plugin_that_loads_clears_an_earlier_failure(tmp_path): _plugin(tmp_path, "hl_good") pm._FAILED_PLUGINS["hl_good"] = "setup() raised: earlier" ok, _ = pm.hot_reload_plugin(_app(tmp_path), "hl_good") assert ok assert "hl_good" in pm._LOADED_PLUGINS and pm.get_plugin_failures() == {} def test_enabling_one_already_running_is_a_success_not_a_failure(tmp_path): pm._LOADED_PLUGINS.add("hl_running") ok, _ = pm.hot_reload_plugin(_app(tmp_path), "hl_running") assert ok and pm.get_plugin_failures() == {}