feat: add source registry mutations

This commit is contained in:
lda
2026-06-04 15:14:31 +07:00 Unverified
parent c0a885f050
commit 9ca81fedf2
15 changed files with 1689 additions and 80 deletions
@@ -2,6 +2,8 @@ from __future__ import annotations
from pathlib import Path
import pytest
from wf_mcp.broker.service.source_registry_admin import SourceRegistryAdminProvider
from wf_mcp.models import ConnectionConfig
from wf_mcp.source_registry import (
@@ -27,6 +29,28 @@ def _entry(source_id: str, *, provider: str = "github", account: str = "work") -
)
def _entry_dict(source_id: str, *, provider: str = "github", account: str = "work") -> dict:
return {
"id": source_id,
"provider": provider,
"account": account,
"transport": {"kind": "stdio", "command": "npx", "args": (), "env": {}},
}
def _provider(
tmp_path: Path,
entries: list[McpSourceRegistryEntry] | None = None,
config_ids: frozenset[str] | None = None,
) -> SourceRegistryAdminProvider:
store = _store_with_entries(tmp_path / "reg", *(entries or []))
connections = [ConnectionConfig(id=cid, server="s", account="a") for cid in (config_ids or frozenset())]
return SourceRegistryAdminProvider(source_registry_store=store, config_connections=connections)
# -- read tests ------------------------------------------------------------
def test_provider_lists_entries_from_store(tmp_path: Path) -> None:
store = _store_with_entries(
tmp_path / "reg",
@@ -38,7 +62,7 @@ def test_provider_lists_entries_from_store(tmp_path: Path) -> None:
entries = provider.list_registry_entries()
assert len(entries) == 2
ids = {getattr(e, "id", getattr(e, "get", lambda k: None)("id")) for e in entries}
ids = {e.id for e in entries}
assert ids == {"alpha.work", "zeta.personal"}
@@ -67,3 +91,124 @@ def test_provider_empty_store(tmp_path: Path) -> None:
assert entries == []
assert shadowed == set()
# -- add tests -------------------------------------------------------------
def test_add_persists_and_round_trips(tmp_path: Path) -> None:
provider = _provider(tmp_path)
result = provider.add_registry_entry(_entry_dict("new.server"))
assert result.id == "new.server"
reloaded = provider.list_registry_entries()
assert len(reloaded) == 1
assert reloaded[0].id == "new.server"
def test_add_rejects_config_shadowed_id(tmp_path: Path) -> None:
provider = _provider(tmp_path, config_ids=frozenset({"config.server"}))
with pytest.raises(ValueError, match="shadowed by a config connection"):
provider.add_registry_entry(_entry_dict("config.server"))
assert provider.list_registry_entries() == []
def test_add_rejects_duplicate_registry_id(tmp_path: Path) -> None:
provider = _provider(tmp_path, entries=[_entry("existing.server")])
with pytest.raises(ValueError, match="duplicate"):
provider.add_registry_entry(_entry_dict("existing.server"))
assert len(provider.list_registry_entries()) == 1
def test_add_malformed_payload_raises_validation_error(tmp_path: Path) -> None:
provider = _provider(tmp_path)
with pytest.raises(Exception, match="validation"):
provider.add_registry_entry({"id": "x"})
# -- update tests ----------------------------------------------------------
def test_update_persists_provider_account_transport_changes(tmp_path: Path) -> None:
provider = _provider(tmp_path, entries=[_entry("src.server")])
result = provider.update_registry_entry(
"src.server",
{"provider": "new_provider", "account": "new_account"},
)
assert result.id == "src.server"
assert result.provider == "new_provider"
assert result.account == "new_account"
reloaded = provider.list_registry_entries()
reloaded_entry = reloaded[0]
assert reloaded_entry.provider == "new_provider"
assert reloaded_entry.account == "new_account"
def test_update_rejects_id_change(tmp_path: Path) -> None:
provider = _provider(tmp_path, entries=[_entry("old.name")])
with pytest.raises(ValueError, match="cannot change source id"):
provider.update_registry_entry("old.name", {"id": "new.name"})
# original unchanged
reloaded = provider.list_registry_entries()
assert reloaded[0].id == "old.name"
def test_update_missing_source_raises_key_error(tmp_path: Path) -> None:
provider = _provider(tmp_path)
with pytest.raises(KeyError, match="unknown registry source"):
provider.update_registry_entry("no.such.id", {})
# -- enable/disable tests --------------------------------------------------
def test_enable_disable_persist(tmp_path: Path) -> None:
provider = _provider(tmp_path, entries=[_entry("toggle.server")])
disabled = provider.set_registry_entry_enabled("toggle.server", False)
assert disabled.enabled is False
reloaded = provider.list_registry_entries()
assert reloaded[0].enabled is False
enabled = provider.set_registry_entry_enabled("toggle.server", True)
assert enabled.enabled is True
reloaded = provider.list_registry_entries()
assert reloaded[0].enabled is True
def test_enable_disable_missing_source_raises_key_error(tmp_path: Path) -> None:
provider = _provider(tmp_path)
with pytest.raises(KeyError, match="unknown registry source"):
provider.set_registry_entry_enabled("no.such.id", True)
# -- remove tests ----------------------------------------------------------
def test_remove_persists_absence_and_does_not_touch_unrelated(tmp_path: Path) -> None:
provider = _provider(tmp_path, entries=[_entry("keep.server"), _entry("drop.server")])
result = provider.remove_registry_entry("drop.server")
assert result == {"removed": True, "source_id": "drop.server"}
reloaded = provider.list_registry_entries()
assert len(reloaded) == 1
assert reloaded[0].id == "keep.server"
def test_remove_missing_source_raises_key_error(tmp_path: Path) -> None:
provider = _provider(tmp_path)
with pytest.raises(KeyError, match="unknown registry source"):
provider.remove_registry_entry("no.such.id")