refactor: move broker dto conversions out of wf_sources_mcp
This commit is contained in:
@@ -13,6 +13,7 @@ from wf_mcp.source_registry import (
|
||||
StdioSourceTransport,
|
||||
connection_config_to_registry_entry,
|
||||
registry_entry_to_connection_config,
|
||||
workflow_mcp_source_to_connection_config,
|
||||
)
|
||||
|
||||
|
||||
@@ -120,6 +121,13 @@ def test_registry_entry_to_connection_config_disabled_entry() -> None:
|
||||
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",
|
||||
@@ -134,7 +142,7 @@ def test_connection_config_to_registry_entry_preserves_transport_metadata() -> N
|
||||
},
|
||||
)
|
||||
|
||||
entry = connection_config_to_registry_entry(connection)
|
||||
entry = connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||
|
||||
assert entry.id == "github.work"
|
||||
assert entry.provider == "github"
|
||||
@@ -160,7 +168,7 @@ def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> No
|
||||
},
|
||||
)
|
||||
|
||||
entry = connection_config_to_registry_entry(connection)
|
||||
entry = connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||
|
||||
assert entry.transport.kind == "stdio"
|
||||
assert isinstance(entry.transport, StdioSourceTransport)
|
||||
@@ -183,7 +191,7 @@ def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> Non
|
||||
},
|
||||
)
|
||||
|
||||
entry = connection_config_to_registry_entry(connection)
|
||||
entry = connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||
|
||||
assert entry.transport.kind == "http"
|
||||
assert isinstance(entry.transport, HttpSourceTransport)
|
||||
@@ -196,4 +204,82 @@ def test_connection_config_to_registry_entry_requires_transport_metadata() -> No
|
||||
connection = ConnectionConfig(id="github.work", server="github", account="work")
|
||||
|
||||
with pytest.raises(ValueError, match="requires metadata.transport"):
|
||||
connection_config_to_registry_entry(connection)
|
||||
connection_config_to_registry_entry(connection) # type: ignore[arg-type]
|
||||
|
||||
|
||||
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]
|
||||
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)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Protocol
|
||||
|
||||
import pytest
|
||||
@@ -23,6 +25,15 @@ from wf_sources_mcp.transports import (
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _LegacyConnectionLike:
|
||||
id: str
|
||||
server: str
|
||||
account: str
|
||||
enabled: bool = True
|
||||
metadata: Mapping[str, object] = field(default_factory=dict)
|
||||
|
||||
|
||||
def test_stdio_source_transport_is_typed() -> None:
|
||||
transport = StdioSourceTransport(
|
||||
command="uvx",
|
||||
@@ -130,7 +141,7 @@ def test_mcp_source_connection_from_legacy_connection_config_stdio() -> None:
|
||||
},
|
||||
)
|
||||
|
||||
connection = mcp_source_connection_from_connection_config(legacy)
|
||||
connection = mcp_source_connection_from_connection_config(legacy) # type: ignore[arg-type]
|
||||
|
||||
assert connection.id == "github.work"
|
||||
assert connection.provider == "github"
|
||||
@@ -159,7 +170,7 @@ def test_mcp_source_connection_from_legacy_connection_config_http() -> None:
|
||||
},
|
||||
)
|
||||
|
||||
connection = mcp_source_connection_from_connection_config(legacy)
|
||||
connection = mcp_source_connection_from_connection_config(legacy) # type: ignore[arg-type]
|
||||
|
||||
assert isinstance(connection.transport, HttpSourceTransport)
|
||||
assert str(connection.transport.url) == "http://127.0.0.1:8000/mcp"
|
||||
@@ -176,11 +187,45 @@ def test_mcp_source_connection_accepts_missing_legacy_transport_until_open() ->
|
||||
metadata={},
|
||||
)
|
||||
|
||||
connection = mcp_source_connection_from_connection_config(legacy)
|
||||
connection = mcp_source_connection_from_connection_config(legacy) # type: ignore[arg-type]
|
||||
|
||||
assert connection.transport is None
|
||||
|
||||
|
||||
def test_structural_legacy_connection_stdio_without_wf_mcp() -> None:
|
||||
legacy = _LegacyConnectionLike(
|
||||
id="github.work",
|
||||
server="github",
|
||||
account="work",
|
||||
enabled=False,
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": "uvx",
|
||||
"args": ["github-mcp"],
|
||||
"env": {"A": "B"},
|
||||
"cwd": "C:/repo",
|
||||
"auth_ref": "github.token",
|
||||
"profile": "engineering",
|
||||
"source_registry": True,
|
||||
"team": "platform",
|
||||
},
|
||||
)
|
||||
|
||||
connection = mcp_source_connection_from_connection_config(legacy)
|
||||
|
||||
assert connection.id == "github.work"
|
||||
assert connection.provider == "github"
|
||||
assert connection.account == "work"
|
||||
assert connection.enabled is False
|
||||
assert connection.profile == "engineering"
|
||||
assert connection.auth_ref == "github.token"
|
||||
assert connection.metadata == {"source_registry": True, "team": "platform"}
|
||||
assert isinstance(connection.transport, StdioSourceTransport)
|
||||
assert connection.transport.command == "uvx"
|
||||
assert connection.transport.args == ("github-mcp",)
|
||||
assert connection.transport.cwd == "C:/repo"
|
||||
|
||||
|
||||
class _ConnectionLike(Protocol):
|
||||
id: str
|
||||
auth_ref: str | None
|
||||
|
||||
@@ -270,3 +270,26 @@ def test_wf_sources_mcp_does_not_import_old_wf_mcp_id_modules() -> None:
|
||||
"wf_sources_mcp still imports old wf_mcp source ID modules:\n"
|
||||
+ "\n".join(f" {violation}" for violation in violations)
|
||||
)
|
||||
|
||||
|
||||
def test_wf_sources_mcp_does_not_import_wf_mcp_broker_dtos() -> None:
|
||||
root = Path(__file__).resolve().parents[2] / "src" / "wf_sources_mcp"
|
||||
forbidden = {"wf_mcp.models", "wf_mcp.broker.models"}
|
||||
violations: list[str] = []
|
||||
|
||||
for py_file in sorted(root.rglob("*.py")):
|
||||
rel = py_file.relative_to(root.parent)
|
||||
module = str(rel.with_suffix("")).replace("/", ".").replace("\\", ".")
|
||||
tree = ast.parse(py_file.read_text(encoding="utf-8"), filename=str(py_file))
|
||||
for node in ast.walk(tree):
|
||||
if isinstance(node, ast.ImportFrom) and node.module in forbidden:
|
||||
violations.append(f"{module}:{node.lineno}: from {node.module} import ...")
|
||||
elif isinstance(node, ast.Import):
|
||||
for alias in node.names:
|
||||
if alias.name in forbidden:
|
||||
violations.append(f"{module}:{node.lineno}: import {alias.name}")
|
||||
|
||||
assert violations == [], (
|
||||
"wf_sources_mcp still imports wf_mcp broker DTO modules:\n"
|
||||
+ "\n".join(f" {violation}" for violation in violations)
|
||||
)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from wf_mcp.models import ConnectionConfig
|
||||
from wf_sources_mcp.source_registry import (
|
||||
FileSourceRegistryStore,
|
||||
HttpSourceTransport,
|
||||
@@ -12,10 +13,18 @@ from wf_sources_mcp.source_registry import (
|
||||
SourceRegistryFile,
|
||||
StdioSourceTransport,
|
||||
connection_config_to_registry_entry,
|
||||
registry_entry_to_connection_config,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
class _LegacyConnectionLike:
|
||||
id: str
|
||||
server: str
|
||||
account: str
|
||||
enabled: bool = True
|
||||
metadata: Mapping[str, object] = field(default_factory=dict)
|
||||
|
||||
|
||||
def _entry(source_id: str = "github.work") -> McpSourceRegistryEntry:
|
||||
return McpSourceRegistryEntry(
|
||||
id=source_id,
|
||||
@@ -83,45 +92,8 @@ def test_file_source_registry_store_validates_loaded_registry(tmp_path: Path) ->
|
||||
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_connection_config_to_registry_entry_preserves_transport_metadata() -> None:
|
||||
connection = ConnectionConfig(
|
||||
connection = _LegacyConnectionLike(
|
||||
id="github.work",
|
||||
server="github",
|
||||
account="work",
|
||||
@@ -147,7 +119,7 @@ def test_connection_config_to_registry_entry_preserves_transport_metadata() -> N
|
||||
|
||||
|
||||
def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> None:
|
||||
connection = ConnectionConfig(
|
||||
connection = _LegacyConnectionLike(
|
||||
id="github.work",
|
||||
server="github",
|
||||
account="work",
|
||||
@@ -171,7 +143,7 @@ def test_connection_config_to_registry_entry_accepts_flat_stdio_metadata() -> No
|
||||
|
||||
|
||||
def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> None:
|
||||
connection = ConnectionConfig(
|
||||
connection = _LegacyConnectionLike(
|
||||
id="context7.default",
|
||||
server="context7",
|
||||
account="default",
|
||||
@@ -193,7 +165,7 @@ def test_connection_config_to_registry_entry_accepts_flat_http_metadata() -> Non
|
||||
|
||||
|
||||
def test_connection_config_to_registry_entry_requires_transport_metadata() -> None:
|
||||
connection = ConnectionConfig(id="github.work", server="github", account="work")
|
||||
connection = _LegacyConnectionLike(id="github.work", server="github", account="work")
|
||||
|
||||
with pytest.raises(ValueError, match="requires metadata.transport"):
|
||||
connection_config_to_registry_entry(connection)
|
||||
|
||||
Reference in New Issue
Block a user