| import sys |
| import types |
| import unittest |
| from pathlib import Path |
| from unittest.mock import MagicMock, patch |
| |
| from gerrit_mcp_server.extensions import ExtensionContext, ExtensionLoader |
| |
| |
| def _make_ctx(tmp_path: Path) -> ExtensionContext: |
| return ExtensionContext( |
| mcp=MagicMock(), |
| get_base_url=MagicMock(return_value="https://example.com"), |
| normalize_url=lambda u: u, |
| run_curl=MagicMock(), |
| load_config=MagicMock(return_value={}), |
| log_path=tmp_path / "server.log", |
| ) |
| |
| |
| class TestExtensionLoader(unittest.TestCase): |
| def setUp(self): |
| import tempfile |
| |
| self._tmp = tempfile.mkdtemp() |
| self.ctx = _make_ctx(Path(self._tmp)) |
| self.loader = ExtensionLoader(self.ctx) |
| |
| def test_no_extensions_returns_empty(self): |
| with ( |
| patch.object(self.loader, "_discover_entry_point_modules", return_value=[]), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| ): |
| result = self.loader.load() |
| self.assertEqual(result, []) |
| |
| def test_successful_registration(self): |
| mod = types.ModuleType("fake_ext") |
| mod.register = MagicMock() |
| with ( |
| patch.object( |
| self.loader, |
| "_discover_entry_point_modules", |
| return_value=["fake_ext"], |
| ), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| patch.dict(sys.modules, {"fake_ext": mod}), |
| ): |
| result = self.loader.load() |
| self.assertEqual(result, ["fake_ext"]) |
| mod.register.assert_called_once_with(self.ctx) |
| |
| def test_import_failure_skipped(self): |
| register_mock = MagicMock() |
| with ( |
| patch.object( |
| self.loader, |
| "_discover_entry_point_modules", |
| return_value=["nonexistent.module"], |
| ), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| patch( |
| "gerrit_mcp_server.extensions.importlib.import_module", |
| side_effect=ImportError("no module"), |
| ), |
| ): |
| result = self.loader.load() |
| self.assertEqual(result, []) |
| register_mock.assert_not_called() |
| |
| def test_missing_register_skipped(self): |
| mod = types.ModuleType("no_register_ext") |
| # Use a NonCallableMagicMock so callable(mod.register) is False, |
| # matching the real "missing register" guard, while still allowing |
| # assert_not_called() to confirm register() was never invoked. |
| from unittest.mock import NonCallableMagicMock |
| |
| mod.register = NonCallableMagicMock() |
| with ( |
| patch.object( |
| self.loader, |
| "_discover_entry_point_modules", |
| return_value=["no_register_ext"], |
| ), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| patch.dict(sys.modules, {"no_register_ext": mod}), |
| ): |
| result = self.loader.load() |
| self.assertEqual(result, []) |
| mod.register.assert_not_called() |
| |
| def test_register_raises_skipped(self): |
| mod = types.ModuleType("bad_register_ext") |
| mod.register = MagicMock(side_effect=RuntimeError("boom")) |
| with ( |
| patch.object( |
| self.loader, |
| "_discover_entry_point_modules", |
| return_value=["bad_register_ext"], |
| ), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| patch.dict(sys.modules, {"bad_register_ext": mod}), |
| ): |
| result = self.loader.load() |
| self.assertEqual(result, []) |
| |
| def test_deduplication_loads_once(self): |
| mod = types.ModuleType("dup_ext") |
| mod.register = MagicMock() |
| with ( |
| patch.object( |
| self.loader, |
| "_discover_entry_point_modules", |
| return_value=["dup_ext", "dup_ext"], |
| ), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| patch.dict(sys.modules, {"dup_ext": mod}), |
| ): |
| result = self.loader.load() |
| self.assertEqual(result, ["dup_ext"]) |
| mod.register.assert_called_once() |
| |
| def test_duplicate_logs_warning(self): |
| mod = types.ModuleType("dup_warn_ext") |
| mod.register = MagicMock() |
| with ( |
| patch.object( |
| self.loader, |
| "_discover_entry_point_modules", |
| return_value=["dup_warn_ext", "dup_warn_ext"], |
| ), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| patch.dict(sys.modules, {"dup_warn_ext": mod}), |
| ): |
| self.loader.load() |
| log_content = (self.ctx.log_path).read_text() |
| self.assertIn("WARNING", log_content) |
| self.assertIn("dup_warn_ext", log_content) |
| |
| def test_entry_point_discovery_failure_does_not_abort(self): |
| # A malformed entry point in the environment can make discovery |
| # raise; startup must survive it (never-abort-startup contract). |
| with ( |
| patch( |
| "gerrit_mcp_server.extensions.entry_points", |
| side_effect=RuntimeError("bad entry point"), |
| ), |
| patch.object(self.loader, "_discover_env_modules", return_value=[]), |
| ): |
| result = self.loader.load() |
| self.assertEqual(result, []) |
| log_content = (self.ctx.log_path).read_text() |
| self.assertIn("discovery failed", log_content) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |