286 lines
9.3 KiB
Python
286 lines
9.3 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from wf_mcp.models import ConnectionConfig
|
|
from wf_mcp.source_registry import (
|
|
FileSourceRegistryStore,
|
|
HttpSourceTransport,
|
|
McpSourceRegistryEntry,
|
|
SourceRegistryFile,
|
|
StdioSourceTransport,
|
|
connection_config_to_registry_entry,
|
|
registry_entry_to_connection_config,
|
|
workflow_mcp_source_to_connection_config,
|
|
)
|
|
|
|
|
|
def _entry(source_id: str = "github.work") -> McpSourceRegistryEntry:
|
|
return McpSourceRegistryEntry(
|
|
id=source_id,
|
|
provider="github",
|
|
account="work",
|
|
transport=StdioSourceTransport(
|
|
command="npx",
|
|
args=("-y", "@modelcontextprotocol/server-github"),
|
|
env={"GITHUB_TOKEN": "${GITHUB_TOKEN}"},
|
|
),
|
|
auth_ref=source_id,
|
|
metadata={"purpose": "tests"},
|
|
)
|
|
|
|
|
|
def test_source_registry_entry_keeps_identity_and_transport_structural() -> None:
|
|
entry = _entry()
|
|
|
|
assert entry.id == "github.work"
|
|
assert entry.provider == "github"
|
|
assert entry.account == "work"
|
|
assert entry.profile is None
|
|
assert entry.transport.kind == "stdio"
|
|
assert entry.transport.command == "npx"
|
|
assert entry.auth_ref == "github.work"
|
|
|
|
|
|
def test_source_registry_accepts_http_transport() -> None:
|
|
entry = McpSourceRegistryEntry(
|
|
id="github.http",
|
|
provider="github",
|
|
account="work",
|
|
transport=HttpSourceTransport(url="https://example.test/mcp"), # type: ignore[arg-type, ty:invalid-argument-type]
|
|
)
|
|
|
|
assert entry.transport.kind == "http"
|
|
assert str(entry.transport.url) == "https://example.test/mcp"
|
|
|
|
|
|
def test_source_registry_rejects_reserved_ids() -> None:
|
|
with pytest.raises(ValueError, match="reserved"):
|
|
_entry("wf.admin")
|
|
|
|
|
|
def test_file_source_registry_store_round_trips_registry(tmp_path: Path) -> None:
|
|
store = FileSourceRegistryStore(tmp_path)
|
|
registry = SourceRegistryFile(sources=[_entry("github.work")])
|
|
|
|
store.save_registry(registry)
|
|
loaded = store.load_registry()
|
|
|
|
assert loaded.source_map()["github.work"].provider == "github"
|
|
assert loaded.source_map()["github.work"].transport.kind == "stdio"
|
|
|
|
|
|
def test_file_source_registry_store_validates_loaded_registry(tmp_path: Path) -> None:
|
|
store = FileSourceRegistryStore(tmp_path)
|
|
store.path.write_text(
|
|
'{"version": 1, "sources": [{"id": "wf.admin", "provider": "wf", '
|
|
'"account": "admin", "transport": {"kind": "stdio", "command": "x"}}]}',
|
|
encoding="utf-8",
|
|
)
|
|
|
|
with pytest.raises(ValueError, match="reserved"):
|
|
store.load_registry()
|
|
|
|
|
|
def test_registry_entry_to_connection_config_preserves_identity() -> None:
|
|
entry = _entry()
|
|
config = registry_entry_to_connection_config(entry)
|
|
|
|
assert config.id == "github.work"
|
|
assert config.server == "github"
|
|
assert config.account == "work"
|
|
assert config.enabled is True
|
|
|
|
|
|
def test_registry_entry_to_connection_config_preserves_transport_metadata() -> None:
|
|
entry = _entry()
|
|
entry.auth_ref = "github.work.auth"
|
|
config = registry_entry_to_connection_config(entry)
|
|
|
|
assert config.metadata["auth_ref"] == "github.work.auth"
|
|
assert config.metadata["profile"] is None
|
|
assert config.metadata["transport"]["kind"] == "stdio"
|
|
assert config.metadata["transport"]["command"] == "npx"
|
|
assert config.metadata["source_registry"] is True
|
|
|
|
|
|
def test_registry_entry_to_connection_config_preserves_user_metadata() -> None:
|
|
entry = _entry()
|
|
config = registry_entry_to_connection_config(entry)
|
|
|
|
assert config.metadata["purpose"] == "tests"
|
|
|
|
|
|
def test_registry_entry_to_connection_config_disabled_entry() -> None:
|
|
entry = _entry()
|
|
entry.enabled = False
|
|
config = registry_entry_to_connection_config(entry)
|
|
|
|
assert config.enabled is False
|
|
|
|
|
|
def test_registry_entry_to_connection_config_returns_broker_dto() -> None:
|
|
entry = _entry()
|
|
config = registry_entry_to_connection_config(entry)
|
|
|
|
assert isinstance(config, ConnectionConfig)
|
|
|
|
|
|
def test_connection_config_to_registry_entry_preserves_transport_metadata() -> None:
|
|
connection = ConnectionConfig(
|
|
id="github.work",
|
|
server="github",
|
|
account="work",
|
|
enabled=False,
|
|
metadata={
|
|
"transport": {"kind": "stdio", "command": "npx", "args": ["server"]},
|
|
"profile": "corp",
|
|
"auth_ref": "secret://github/work",
|
|
"region": "us",
|
|
},
|
|
)
|
|
|
|
entry = connection_config_to_registry_entry(connection)
|
|
|
|
assert entry.id == "github.work"
|
|
assert entry.provider == "github"
|
|
assert entry.account == "work"
|
|
assert entry.enabled is False
|
|
assert entry.profile == "corp"
|
|
assert entry.auth_ref == "secret://github/work"
|
|
assert entry.transport.kind == "stdio"
|
|
assert entry.metadata["region"] == "us"
|
|
|
|
|
|
def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> None:
|
|
connection = ConnectionConfig(
|
|
id="github.work",
|
|
server="github",
|
|
account="work",
|
|
metadata={
|
|
"transport": "stdio",
|
|
"command": "npx",
|
|
"args": ["server"],
|
|
"env": {"DEBUG": "1"},
|
|
"region": "us",
|
|
},
|
|
)
|
|
|
|
entry = connection_config_to_registry_entry(connection)
|
|
|
|
assert entry.transport.kind == "stdio"
|
|
assert isinstance(entry.transport, StdioSourceTransport)
|
|
assert entry.transport.command == "npx"
|
|
assert entry.transport.args == ("server",)
|
|
assert entry.transport.env == {"DEBUG": "1"}
|
|
assert entry.metadata == {"region": "us"}
|
|
|
|
|
|
def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> None:
|
|
connection = ConnectionConfig(
|
|
id="context7.default",
|
|
server="context7",
|
|
account="default",
|
|
metadata={
|
|
"transport": "sse",
|
|
"url": "http://127.0.0.1:3000/sse",
|
|
"headers": {"X-Test": "yes"},
|
|
"purpose": "legacy",
|
|
},
|
|
)
|
|
|
|
entry = connection_config_to_registry_entry(connection)
|
|
|
|
assert entry.transport.kind == "http"
|
|
assert isinstance(entry.transport, HttpSourceTransport)
|
|
assert str(entry.transport.url) == "http://127.0.0.1:3000/sse"
|
|
assert entry.transport.headers == {"X-Test": "yes"}
|
|
assert entry.metadata == {"purpose": "legacy", "legacy_transport": "sse"}
|
|
|
|
|
|
def test_connection_config_to_registry_entry_requires_transport_metadata() -> None:
|
|
connection = ConnectionConfig(id="github.work", server="github", account="work")
|
|
|
|
with pytest.raises(ValueError, match="requires metadata.transport"):
|
|
connection_config_to_registry_entry(connection)
|
|
|
|
|
|
class _McpSource:
|
|
"""Minimal mock for wf_config MCP source objects."""
|
|
|
|
def __init__(self) -> None:
|
|
self.kind = "mcp"
|
|
self.id = "github.work"
|
|
self.provider = "github"
|
|
self.account = "work"
|
|
self.enabled = True
|
|
self.ownership = "seed"
|
|
self.transport = StdioSourceTransport(command="npx", args=("-y", "server"))
|
|
self.metadata: dict[str, object] = {"region": "us"}
|
|
self.profile: str | None = "engineering"
|
|
self.auth_ref: str | None = "github.token"
|
|
|
|
|
|
class _McpSourceHttp:
|
|
"""Minimal mock for wf_config MCP source with HTTP transport."""
|
|
|
|
def __init__(self) -> None:
|
|
self.kind = "mcp"
|
|
self.id = "ctx.default"
|
|
self.provider = "ctx"
|
|
self.account = "default"
|
|
self.enabled = True
|
|
self.ownership = "locked"
|
|
self.transport = HttpSourceTransport(url="http://127.0.0.1:3000/sse") # type: ignore[arg-type, ty:invalid-argument-type]
|
|
self.metadata: dict[str, object] = {}
|
|
self.profile: str | None = None
|
|
self.auth_ref: str | None = None
|
|
|
|
|
|
def test_workflow_mcp_source_to_connection_config_stdio() -> None:
|
|
source = _McpSource()
|
|
config = workflow_mcp_source_to_connection_config(source)
|
|
|
|
assert isinstance(config, ConnectionConfig)
|
|
assert config.id == "github.work"
|
|
assert config.server == "github"
|
|
assert config.account == "work"
|
|
assert config.enabled is True
|
|
assert config.source_config_ownership == "seed"
|
|
assert config.metadata["transport"] == "stdio"
|
|
assert config.metadata["command"] == "npx"
|
|
assert config.metadata["args"] == ["-y", "server"]
|
|
assert config.metadata["profile"] == "engineering"
|
|
assert config.metadata["auth_ref"] == "github.token"
|
|
assert config.metadata["region"] == "us"
|
|
assert config.metadata["source_registry"] is False
|
|
|
|
|
|
def test_workflow_mcp_source_to_connection_config_http() -> None:
|
|
source = _McpSourceHttp()
|
|
config = workflow_mcp_source_to_connection_config(source)
|
|
|
|
assert isinstance(config, ConnectionConfig)
|
|
assert config.id == "ctx.default"
|
|
assert config.metadata["transport"] == "streamable_http"
|
|
assert config.metadata["url"] == "http://127.0.0.1:3000/sse"
|
|
assert config.metadata["source_registry"] is False
|
|
|
|
|
|
def test_workflow_mcp_source_to_connection_config_rejects_non_mcp() -> None:
|
|
source = _McpSource()
|
|
source.kind = "stdlib"
|
|
|
|
with pytest.raises(ValueError, match="expected wf_config MCP source"):
|
|
workflow_mcp_source_to_connection_config(source)
|
|
|
|
|
|
def test_workflow_mcp_source_to_connection_config_rejects_missing_fields() -> None:
|
|
source = _McpSource()
|
|
source.id = None # type: ignore[assignment]
|
|
|
|
with pytest.raises(ValueError, match="missing required field"):
|
|
workflow_mcp_source_to_connection_config(source)
|