blob: 34bab70976a7bffafd463d128ecb382980132fe5 [file]
import asyncio
import json
import unittest
from gerrit_mcp_server.plugin_registry import PluginRegistry, requires_plugin
def _run(coro):
return asyncio.run(coro)
def _make_registry(plugins_by_host: dict, ttl: float = 300.0) -> PluginRegistry:
async def run_curl(args, base_url):
return json.dumps(plugins_by_host.get(base_url, {}))
config = {"gerrit_hosts": [{"external_url": url} for url in plugins_by_host]}
return PluginRegistry(
run_curl=run_curl,
normalize_url=lambda u: u.rstrip("/"),
load_config=lambda: config,
ttl_seconds=ttl,
)
class TestPluginRegistry(unittest.TestCase):
def test_host_has_plugin_true(self):
reg = _make_registry({"https://host-a": {"task": {"version": "1.0"}}})
self.assertTrue(_run(reg.host_has_plugin("https://host-a", "task")))
def test_host_has_plugin_false(self):
reg = _make_registry({"https://host-a": {}})
self.assertFalse(_run(reg.host_has_plugin("https://host-a", "task")))
def test_disabled_plugin_excluded(self):
reg = _make_registry({"https://host-a": {"task": {"disabled": True}}})
self.assertFalse(_run(reg.host_has_plugin("https://host-a", "task")))
def test_hosts_with_plugin(self):
reg = _make_registry(
{
"https://host-a": {"task": {}},
"https://host-b": {},
}
)
hosts = _run(reg.hosts_with_plugin("task"))
self.assertIn("https://host-a", hosts)
self.assertNotIn("https://host-b", hosts)
def test_ttl_cache_hit(self):
call_count = 0
async def run_curl(args, base_url):
nonlocal call_count
call_count += 1
return json.dumps({"task": {}})
reg = PluginRegistry(
run_curl=run_curl,
normalize_url=lambda u: u,
load_config=lambda: {"gerrit_hosts": [{"external_url": "https://host-a"}]},
ttl_seconds=300.0,
)
_run(reg.host_has_plugin("https://host-a", "task"))
_run(reg.host_has_plugin("https://host-a", "task"))
self.assertEqual(call_count, 1)
def test_invalidate_single_host(self):
call_count = 0
async def run_curl(args, base_url):
nonlocal call_count
call_count += 1
return json.dumps({"task": {}})
reg = PluginRegistry(
run_curl=run_curl,
normalize_url=lambda u: u,
load_config=lambda: {"gerrit_hosts": [{"external_url": "https://host-a"}]},
ttl_seconds=300.0,
)
_run(reg.host_has_plugin("https://host-a", "task"))
reg.invalidate("https://host-a")
self.assertNotIn("https://host-a", reg._cache)
self.assertNotIn("https://host-a", reg._locks)
def test_invalidate_all(self):
reg = _make_registry(
{
"https://host-a": {"task": {}},
"https://host-b": {"task": {}},
}
)
_run(reg.hosts_with_plugin("task"))
reg.invalidate()
self.assertEqual(reg._cache, {})
self.assertEqual(reg._locks, {})
def test_plugin_version(self):
reg = _make_registry({"https://host-a": {"task": {"version": "2.3"}}})
v = _run(reg.plugin_version("https://host-a", "task"))
self.assertEqual(v, "2.3")
def test_plugin_version_missing(self):
reg = _make_registry({"https://host-a": {}})
v = _run(reg.plugin_version("https://host-a", "task"))
self.assertIsNone(v)
class TestRequiresPlugin(unittest.TestCase):
def _make_reg(self, has_plugin: bool) -> PluginRegistry:
async def run_curl(args, base_url):
return json.dumps({"task": {}} if has_plugin else {})
return PluginRegistry(
run_curl=run_curl,
normalize_url=lambda u: u,
load_config=lambda: {"gerrit_hosts": [{"external_url": "https://host-a"}]},
)
def test_routes_to_available_host(self):
reg = self._make_reg(has_plugin=True)
received_url = []
@requires_plugin("task", reg)
async def my_tool(gerrit_base_url=None):
received_url.append(gerrit_base_url)
return "ok"
result = _run(my_tool())
self.assertEqual(result, "ok")
self.assertEqual(received_url, ["https://host-a"])
def test_soft_hint_when_no_host_has_plugin(self):
reg = self._make_reg(has_plugin=False)
@requires_plugin("task", reg)
async def my_tool(gerrit_base_url=None):
return "ok"
with self.assertRaises(RuntimeError) as ctx:
_run(my_tool())
self.assertIn("task", str(ctx.exception))
def test_soft_hint_when_explicit_host_lacks_plugin(self):
reg = self._make_reg(has_plugin=False)
@requires_plugin("task", reg)
async def my_tool(gerrit_base_url=None):
return "ok"
with self.assertRaises(RuntimeError) as ctx:
_run(my_tool(gerrit_base_url="https://host-a"))
self.assertIn("not installed", str(ctx.exception))
def test_passes_through_when_host_has_plugin(self):
reg = self._make_reg(has_plugin=True)
@requires_plugin("task", reg)
async def my_tool(gerrit_base_url=None):
return "done"
result = _run(my_tool(gerrit_base_url="https://host-a"))
self.assertEqual(result, "done")
if __name__ == "__main__":
unittest.main()