"""Unit tests for the bundled + external multi-root plugin model. Pure-function coverage (no DB, no app): path resolution, multi-root migration discovery, and the config split into bundled/external roots + install dir. """ from __future__ import annotations from pathlib import Path from steward.core.migration_runner import discover_all_in from steward.core.plugin_manager import resolve_plugin_path def _make_plugin(root: Path, name: str, with_migrations: bool = False) -> None: pdir = root / name pdir.mkdir(parents=True) (pdir / "plugin.yaml").write_text(f"name: {name}\n") if with_migrations: (pdir / "migrations" / "versions").mkdir(parents=True) def test_resolve_plugin_path_prefers_earlier_root(tmp_path): bundled = tmp_path / "bundled" external = tmp_path / "external" _make_plugin(bundled, "dupe") _make_plugin(external, "dupe") _make_plugin(external, "only_external") # Bundled is listed first → it shadows the external copy of the same name. assert resolve_plugin_path([bundled, external], "dupe") == bundled / "dupe" # A name only present externally still resolves. assert resolve_plugin_path([bundled, external], "only_external") == external / "only_external" def test_resolve_plugin_path_missing_returns_none(tmp_path): assert resolve_plugin_path([tmp_path], "nope") is None def test_discover_all_in_unions_roots(tmp_path): bundled = tmp_path / "bundled" external = tmp_path / "external" _make_plugin(bundled, "a", with_migrations=True) _make_plugin(bundled, "no_mig") # no migrations dir → not discovered _make_plugin(external, "b", with_migrations=True) found = discover_all_in([bundled, external]) assert (bundled / "a" / "migrations") in found assert (external / "b" / "migrations") in found assert (bundled / "no_mig" / "migrations") not in found def test_discover_all_in_skips_missing_root(tmp_path): bundled = tmp_path / "bundled" _make_plugin(bundled, "a", with_migrations=True) # A non-existent external root must not raise. found = discover_all_in([bundled, tmp_path / "does_not_exist"]) assert found == [bundled / "a" / "migrations"] def test_bootstrap_splits_bundled_and_external(tmp_path, monkeypatch): from steward.config import load_bootstrap monkeypatch.setenv("STEWARD_DATABASE_URL", "postgresql+asyncpg://x/y") monkeypatch.setenv("STEWARD_SECRET_KEY", "k" * 32) monkeypatch.setenv("STEWARD_PLUGIN_DIR", "/srv/external-plugins") boot = load_bootstrap(config_path=tmp_path / "absent.yaml") assert boot["plugin_dirs"] == ["plugins", "/srv/external-plugins"] # Installs target the external (writable) root, never the bundled one. assert boot["plugin_install_dir"] == "/srv/external-plugins" def test_bootstrap_external_defaults_to_data_plugins(tmp_path, monkeypatch): from steward.config import load_bootstrap monkeypatch.setenv("STEWARD_DATABASE_URL", "postgresql+asyncpg://x/y") monkeypatch.setenv("STEWARD_SECRET_KEY", "k" * 32) monkeypatch.delenv("STEWARD_PLUGIN_DIR", raising=False) boot = load_bootstrap(config_path=tmp_path / "absent.yaml") assert boot["plugin_dirs"] == ["plugins", "/data/plugins"] assert boot["plugin_install_dir"] == "/data/plugins"