half an attempt to asyncify tests + deprecate old temp path creation
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
@@ -14,9 +15,9 @@ def structured(result: Any) -> dict[str, Any]:
|
||||
return content
|
||||
|
||||
|
||||
def proxy_config() -> BrokerConfig:
|
||||
def proxy_config(tmp_path: Path = local_temp_root()) -> BrokerConfig:
|
||||
return BrokerConfig(
|
||||
store_root=local_temp_root() / "proxy_store",
|
||||
store_root=tmp_path / "proxy_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
|
||||
@@ -3,18 +3,19 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import mcp.types as mcp_types
|
||||
|
||||
from wf_mcp.broker import load_broker_config
|
||||
from wf_mcp.proxy import create_proxy_client
|
||||
|
||||
from ..test_support import fixture_server_path, local_temp_root
|
||||
from ..test_support import fixture_server_path
|
||||
from .conftest import structured
|
||||
|
||||
|
||||
def test_proxy_admin_tools_mutate_config_file() -> None:
|
||||
tmp_path = local_temp_root() / "proxy_admin_store"
|
||||
def test_proxy_admin_tools_mutate_config_file(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "proxy_admin_store"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
@@ -89,8 +90,8 @@ def test_proxy_admin_tools_mutate_config_file() -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_proxy_admin_reload_remounts_connections() -> None:
|
||||
tmp_path = local_temp_root() / "proxy_reload_store"
|
||||
def test_proxy_admin_reload_remounts_connections(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "proxy_reload_store"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
@@ -130,8 +131,8 @@ def test_proxy_admin_reload_remounts_connections() -> None:
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_proxy_admin_reload_sends_list_changed_notifications() -> None:
|
||||
tmp_path = local_temp_root() / "proxy_reload_notification_store"
|
||||
def test_proxy_admin_reload_sends_list_changed_notifications(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "proxy_reload_notification_store"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
@@ -164,8 +165,8 @@ def test_proxy_admin_reload_sends_list_changed_notifications() -> None:
|
||||
assert "notifications/prompts/list_changed" in methods
|
||||
|
||||
|
||||
def test_proxy_config_mutation_does_not_notify_before_reload() -> None:
|
||||
tmp_path = local_temp_root() / "proxy_staged_notification_store"
|
||||
def test_proxy_config_mutation_does_not_notify_before_reload(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "proxy_staged_notification_store"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
|
||||
+172
-197
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import anyio
|
||||
@@ -14,72 +15,69 @@ from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.proxy import create_proxy_client
|
||||
from wf_mcp.proxy.mounts import _bounded_proxy_list
|
||||
|
||||
from ..test_support import fixture_server_path, local_temp_root
|
||||
from ..test_support import fixture_server_path
|
||||
from .conftest import proxy_config, structured
|
||||
|
||||
|
||||
def test_proxy_lists_and_calls_upstream_tools() -> None:
|
||||
config = proxy_config()
|
||||
async def test_proxy_lists_and_calls_upstream_tools(tmp_path: Path) -> None:
|
||||
config = proxy_config(tmp_path)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.list_proxy_tools" in names
|
||||
assert "wf.admin.get_proxy_tool" in names
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.list_proxy_tools" in names
|
||||
assert "wf.admin.get_proxy_tool" in names
|
||||
assert "fixture.personal.echo_tool" in names
|
||||
|
||||
connections_result = await client.call_tool("wf.admin.list_connections")
|
||||
connections = structured(connections_result)["result"]
|
||||
assert len(connections) == 1
|
||||
connection = connections[0]
|
||||
assert connection["id"] == "fixture.personal"
|
||||
assert connection["server"] == "fixture"
|
||||
assert connection["account"] == "personal"
|
||||
assert connection["enabled"] is True
|
||||
assert connection["source_config_ownership"] == "locked"
|
||||
assert connection["metadata"] == {
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
}
|
||||
connections_result = await client.call_tool("wf.admin.list_connections")
|
||||
connections = structured(connections_result)["result"]
|
||||
assert len(connections) == 1
|
||||
connection = connections[0]
|
||||
assert connection["id"] == "fixture.personal"
|
||||
assert connection["server"] == "fixture"
|
||||
assert connection["account"] == "personal"
|
||||
assert connection["enabled"] is True
|
||||
assert connection["source_config_ownership"] == "locked"
|
||||
assert connection["metadata"] == {
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
}
|
||||
|
||||
result = await client.call_tool(
|
||||
"fixture.personal.echo_tool",
|
||||
{"text": "hello"},
|
||||
)
|
||||
assert structured(result) == {"echoed": "hello"}
|
||||
result = await client.call_tool(
|
||||
"fixture.personal.echo_tool",
|
||||
{"text": "hello"},
|
||||
)
|
||||
assert structured(result) == {"echoed": "hello"}
|
||||
|
||||
proxy_tools_result = await client.call_tool("wf.admin.list_proxy_tools")
|
||||
proxy_tools_payload = structured(proxy_tools_result)
|
||||
proxy_tools = proxy_tools_payload["tools"]
|
||||
assert proxy_tools_payload["nextCursor"] is None
|
||||
assert proxy_tools_payload["total"] == 5
|
||||
assert len(proxy_tools) == 5
|
||||
assert proxy_tools[0]["proxy_name"] == "fixture.personal.echo_tool"
|
||||
assert proxy_tools[0]["connection_id"] == "fixture.personal"
|
||||
assert proxy_tools[0]["local_name"] == "echo_tool"
|
||||
assert proxy_tools[0]["enabled"] is True
|
||||
proxy_names = [tool["proxy_name"] for tool in proxy_tools]
|
||||
assert "fixture.personal.emit_notifications_tool" in proxy_names
|
||||
assert "fixture.personal.remember_value_tool" in proxy_names
|
||||
assert "fixture.personal.recall_value_tool" in proxy_names
|
||||
assert "fixture.personal.resource_link_tool" in proxy_names
|
||||
proxy_tools_result = await client.call_tool("wf.admin.list_proxy_tools")
|
||||
proxy_tools_payload = structured(proxy_tools_result)
|
||||
proxy_tools = proxy_tools_payload["tools"]
|
||||
assert proxy_tools_payload["nextCursor"] is None
|
||||
assert proxy_tools_payload["total"] == 5
|
||||
assert len(proxy_tools) == 5
|
||||
assert proxy_tools[0]["proxy_name"] == "fixture.personal.echo_tool"
|
||||
assert proxy_tools[0]["connection_id"] == "fixture.personal"
|
||||
assert proxy_tools[0]["local_name"] == "echo_tool"
|
||||
assert proxy_tools[0]["enabled"] is True
|
||||
proxy_names = [tool["proxy_name"] for tool in proxy_tools]
|
||||
assert "fixture.personal.emit_notifications_tool" in proxy_names
|
||||
assert "fixture.personal.remember_value_tool" in proxy_names
|
||||
assert "fixture.personal.recall_value_tool" in proxy_names
|
||||
assert "fixture.personal.resource_link_tool" in proxy_names
|
||||
|
||||
proxy_tool_result = await client.call_tool(
|
||||
"wf.admin.get_proxy_tool",
|
||||
{"proxy_name": "fixture.personal.echo_tool"},
|
||||
)
|
||||
proxy_tool = structured(proxy_tool_result)
|
||||
assert proxy_tool["proxy_name"] == "fixture.personal.echo_tool"
|
||||
assert proxy_tool["connection_id"] == "fixture.personal"
|
||||
assert proxy_tool["local_name"] == "echo_tool"
|
||||
assert proxy_tool["input_schema"]["properties"]["text"]["type"] == "string"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
proxy_tool_result = await client.call_tool(
|
||||
"wf.admin.get_proxy_tool",
|
||||
{"proxy_name": "fixture.personal.echo_tool"},
|
||||
)
|
||||
proxy_tool = structured(proxy_tool_result)
|
||||
assert proxy_tool["proxy_name"] == "fixture.personal.echo_tool"
|
||||
assert proxy_tool["connection_id"] == "fixture.personal"
|
||||
assert proxy_tool["local_name"] == "echo_tool"
|
||||
assert proxy_tool["input_schema"]["properties"]["text"]["type"] == "string"
|
||||
|
||||
|
||||
def test_proxy_listing_degrades_when_one_source_hangs() -> None:
|
||||
@@ -183,60 +181,49 @@ def test_proxy_listing_degrades_when_session_transport_closes(
|
||||
assert "remote.default" in caplog.text
|
||||
|
||||
|
||||
def test_proxy_registers_admin_tools_on_local_provider() -> None:
|
||||
config = proxy_config()
|
||||
async def test_proxy_registers_admin_tools_on_local_provider(tmp_path) -> None:
|
||||
config = proxy_config(tmp_path)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
admin_names = [
|
||||
tool.name for tool in tools if tool.name.startswith("wf.admin.")
|
||||
]
|
||||
assert "wf.admin.list_connections" in admin_names
|
||||
assert "wf.admin.get_connection_statuses" in admin_names
|
||||
assert "wf.admin.list_proxy_tools" in admin_names
|
||||
assert "wf.admin.get_proxy_tool" in admin_names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
admin_names = [tool.name for tool in tools if tool.name.startswith("wf.admin.")]
|
||||
assert "wf.admin.list_connections" in admin_names
|
||||
assert "wf.admin.get_connection_statuses" in admin_names
|
||||
assert "wf.admin.list_proxy_tools" in admin_names
|
||||
assert "wf.admin.get_proxy_tool" in admin_names
|
||||
|
||||
|
||||
def test_proxy_rewrites_resource_links_returned_by_tools() -> None:
|
||||
config = proxy_config()
|
||||
async def test_proxy_rewrites_resource_links_returned_by_tools(tmp_path) -> None:
|
||||
config = proxy_config(tmp_path)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
result = await client.call_tool("fixture.personal.resource_link_tool")
|
||||
link = result.content[0]
|
||||
assert link.type == "resource_link"
|
||||
assert str(link.uri) == "fixture://fixture/personal/docs/welcome"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
result = await client.call_tool("fixture.personal.resource_link_tool")
|
||||
link = result.content[0]
|
||||
assert link.type == "resource_link"
|
||||
assert str(link.uri) == "fixture://fixture/personal/docs/welcome"
|
||||
|
||||
|
||||
def test_proxy_reuses_one_upstream_session_for_stateful_tools() -> None:
|
||||
async def test_proxy_reuses_one_upstream_session_for_stateful_tools(tmp_path) -> None:
|
||||
"""Visible proxy tools must share server-local state for one MCP client."""
|
||||
config = proxy_config()
|
||||
config = proxy_config(tmp_path)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
written = await client.call_tool(
|
||||
"fixture.personal.remember_value_tool",
|
||||
{"value": "held"},
|
||||
)
|
||||
recalled = await client.call_tool("fixture.personal.recall_value_tool")
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
written = await client.call_tool(
|
||||
"fixture.personal.remember_value_tool",
|
||||
{"value": "held"},
|
||||
)
|
||||
recalled = await client.call_tool("fixture.personal.recall_value_tool")
|
||||
|
||||
assert structured(written)["remembered"] == "held"
|
||||
assert structured(recalled)["remembered"] == "held"
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
assert structured(written)["remembered"] == "held"
|
||||
assert structured(recalled)["remembered"] == "held"
|
||||
|
||||
|
||||
def test_proxy_rejects_invalid_connection_config() -> None:
|
||||
def test_proxy_rejects_invalid_connection_config(tmp_path) -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "proxy_invalid_store",
|
||||
store_root=tmp_path / "proxy_invalid_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
@@ -291,103 +278,91 @@ def test_proxy_rejects_invalid_connection_config() -> None:
|
||||
assert "connection id 'wf.admin' is reserved by wf-mcp" in message
|
||||
|
||||
|
||||
def test_proxy_can_expose_resources_and_prompts_as_tools() -> None:
|
||||
config = proxy_config()
|
||||
async def test_proxy_can_expose_resources_and_prompts_as_tools(tmp_path) -> None:
|
||||
config = proxy_config(tmp_path)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(
|
||||
config,
|
||||
resources_as_tools=True,
|
||||
prompts_as_tools=True,
|
||||
client = create_proxy_client(
|
||||
config,
|
||||
resources_as_tools=True,
|
||||
prompts_as_tools=True,
|
||||
)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "list_resources" in names
|
||||
assert "read_resource" in names
|
||||
assert "list_prompts" in names
|
||||
assert "get_prompt" in names
|
||||
|
||||
|
||||
async def test_proxy_can_collapse_upstream_tools_behind_search(tmp_path) -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=tmp_path / "search_proxy_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
server="fixture",
|
||||
account="personal",
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
client = create_proxy_client(config, search_tools=True)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "search_tools" in names
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.list_proxy_tools" in names
|
||||
assert "fixture.personal.echo_tool" not in names
|
||||
|
||||
|
||||
async def test_proxy_admin_inventory_ignores_search_visibility(tmp_path) -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=tmp_path / "search_admin_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
server="fixture",
|
||||
account="personal",
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
client = create_proxy_client(config, search_tools=True)
|
||||
async with client:
|
||||
result = await client.call_tool("wf.admin.list_proxy_tools")
|
||||
payload = structured(result)
|
||||
assert payload["total"] > 0
|
||||
|
||||
|
||||
async def test_proxy_proxy_tool_listing_supports_filters_and_cursor(tmp_path) -> None:
|
||||
config = proxy_config(tmp_path)
|
||||
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
"wf.admin.list_proxy_tools",
|
||||
{"limit": 2},
|
||||
)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "list_resources" in names
|
||||
assert "read_resource" in names
|
||||
assert "list_prompts" in names
|
||||
assert "get_prompt" in names
|
||||
payload = structured(result)
|
||||
assert len(payload["tools"]) == 2
|
||||
assert payload["nextCursor"] is not None
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_proxy_can_collapse_upstream_tools_behind_search() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "search_proxy_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
server="fixture",
|
||||
account="personal",
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config, search_tools=True)
|
||||
async with client:
|
||||
tools = await client.list_tools()
|
||||
names = [tool.name for tool in tools]
|
||||
assert "search_tools" in names
|
||||
assert "wf.admin.list_connections" in names
|
||||
assert "wf.admin.get_connection_statuses" in names
|
||||
assert "wf.admin.list_proxy_tools" in names
|
||||
assert "fixture.personal.echo_tool" not in names
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_proxy_admin_inventory_ignores_search_visibility() -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "search_admin_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
server="fixture",
|
||||
account="personal",
|
||||
metadata={
|
||||
"transport": "stdio",
|
||||
"command": sys.executable,
|
||||
"args": [fixture_server_path()],
|
||||
},
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config, search_tools=True)
|
||||
async with client:
|
||||
result = await client.call_tool("wf.admin.list_proxy_tools")
|
||||
payload = structured(result)
|
||||
assert payload["total"] > 0
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
|
||||
|
||||
def test_proxy_proxy_tool_listing_supports_filters_and_cursor() -> None:
|
||||
config = proxy_config()
|
||||
|
||||
async def run_proxy() -> None:
|
||||
client = create_proxy_client(config)
|
||||
async with client:
|
||||
result = await client.call_tool(
|
||||
"wf.admin.list_proxy_tools",
|
||||
{"limit": 2},
|
||||
)
|
||||
payload = structured(result)
|
||||
assert len(payload["tools"]) == 2
|
||||
assert payload["nextCursor"] is not None
|
||||
|
||||
result2 = await client.call_tool(
|
||||
"wf.admin.list_proxy_tools",
|
||||
{"limit": 2, "cursor": payload["nextCursor"]},
|
||||
)
|
||||
payload2 = structured(result2)
|
||||
assert len(payload2["tools"]) > 0
|
||||
|
||||
asyncio.run(run_proxy())
|
||||
result2 = await client.call_tool(
|
||||
"wf.admin.list_proxy_tools",
|
||||
{"limit": 2, "cursor": payload["nextCursor"]},
|
||||
)
|
||||
payload2 = structured(result2)
|
||||
assert len(payload2["tools"]) > 0
|
||||
|
||||
@@ -20,13 +20,12 @@ from ..test_support import (
|
||||
FakeAdapter,
|
||||
echo_tool,
|
||||
finalize_tool,
|
||||
local_temp_root,
|
||||
)
|
||||
from .conftest import single_echo_plan
|
||||
|
||||
|
||||
def test_service_builds_namespaced_catalog() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "catalog_store"))
|
||||
def test_service_builds_namespaced_catalog(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "catalog_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
@@ -41,8 +40,8 @@ def test_service_builds_namespaced_catalog() -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_service_rejects_reserved_connection_ids() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "reserved_ids_store"))
|
||||
def test_service_rejects_reserved_connection_ids(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "reserved_ids_store"))
|
||||
|
||||
for connection_id in ("wf.admin", "wf.mcp"):
|
||||
try:
|
||||
@@ -56,8 +55,8 @@ def test_service_rejects_reserved_connection_ids() -> None:
|
||||
raise AssertionError(f"expected {connection_id!r} to be rejected")
|
||||
|
||||
|
||||
def test_service_installs_builtin_stdlib_specs_by_default() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "builtin_store"))
|
||||
def test_service_installs_builtin_stdlib_specs_by_default(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "builtin_store"))
|
||||
|
||||
assert (
|
||||
"wf.std.runtime_error"
|
||||
@@ -66,8 +65,8 @@ def test_service_installs_builtin_stdlib_specs_by_default() -> None:
|
||||
assert "wf.mcp" not in service.capability_sources
|
||||
|
||||
|
||||
def test_service_does_not_install_workflow_stores_implicitly() -> None:
|
||||
root = local_temp_root() / "service_no_implicit_workflow_stores"
|
||||
def test_service_does_not_install_workflow_stores_implicitly(tmp_path: Path) -> None:
|
||||
root = tmp_path / "service_no_implicit_workflow_stores"
|
||||
service = WfMcpService(store=FileStore(root))
|
||||
|
||||
assert service.artifact_store is None
|
||||
@@ -75,8 +74,10 @@ def test_service_does_not_install_workflow_stores_implicitly() -> None:
|
||||
assert service.run_store is None
|
||||
|
||||
|
||||
def test_service_registers_empty_source_for_connection_without_catalog() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "empty_source"))
|
||||
def test_service_registers_empty_source_for_connection_without_catalog(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "empty_source"))
|
||||
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -88,8 +89,10 @@ def test_service_registers_empty_source_for_connection_without_catalog() -> None
|
||||
assert source.description == "No catalog loaded for demo.personal."
|
||||
|
||||
|
||||
def test_service_lists_all_capability_sources_with_owned_capability_names() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "source_inventory"))
|
||||
def test_service_lists_all_capability_sources_with_owned_capability_names(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "source_inventory"))
|
||||
|
||||
sources = service.list_sources()
|
||||
sources_by_id = {source["id"]: source for source in sources}
|
||||
@@ -112,8 +115,8 @@ def test_service_lists_all_capability_sources_with_owned_capability_names() -> N
|
||||
assert "wf.admin.list_sources" in admin_source["capabilities"]["tools"]
|
||||
|
||||
|
||||
def test_service_lists_compact_source_summaries() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "source_summaries"))
|
||||
def test_service_lists_compact_source_summaries(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "source_summaries"))
|
||||
|
||||
payload = service.list_source_summaries(limit=1)
|
||||
|
||||
@@ -129,8 +132,8 @@ def test_service_lists_compact_source_summaries() -> None:
|
||||
assert std_source["has_more"]["node_specs"] is True
|
||||
|
||||
|
||||
def test_wf_std_source_contains_authoring_ops() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "stdlib_source_store"))
|
||||
def test_wf_std_source_contains_authoring_ops(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "stdlib_source_store"))
|
||||
specs = service.capability_sources["wf.std"].capabilities.node_specs
|
||||
|
||||
expected = {
|
||||
@@ -158,8 +161,8 @@ def test_wf_std_source_contains_authoring_ops() -> None:
|
||||
assert set(specs) == expected
|
||||
|
||||
|
||||
def test_wf_std_source_contains_builtin_reducers() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "stdlib_reducer_store"))
|
||||
def test_wf_std_source_contains_builtin_reducers(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "stdlib_reducer_store"))
|
||||
reducers = service.capability_sources["wf.std"].capabilities.reducers
|
||||
|
||||
assert set(reducers) == {
|
||||
@@ -172,8 +175,8 @@ def test_wf_std_source_contains_builtin_reducers() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_service_sources_have_visibility_and_capability_buckets() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "source_shape_store"))
|
||||
def test_service_sources_have_visibility_and_capability_buckets(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "source_shape_store"))
|
||||
|
||||
std_source = service.capability_sources["wf.std"]
|
||||
|
||||
@@ -186,8 +189,8 @@ def test_service_sources_have_visibility_and_capability_buckets() -> None:
|
||||
assert not std_source.capabilities.tools
|
||||
|
||||
|
||||
def test_wf_recipes_source_contains_composed_capabilities() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "recipes_source_store"))
|
||||
def test_wf_recipes_source_contains_composed_capabilities(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "recipes_source_store"))
|
||||
specs = service.capability_sources["wf.recipes"].capabilities.node_specs
|
||||
|
||||
assert set(specs) == {"wf.recipes.extract_text_content"}
|
||||
@@ -196,8 +199,8 @@ def test_wf_recipes_source_contains_composed_capabilities() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_wf_admin_source_exists_but_is_not_planner_visible() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "admin_source_store"))
|
||||
def test_wf_admin_source_exists_but_is_not_planner_visible(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "admin_source_store"))
|
||||
source = service.capability_sources["wf.admin"]
|
||||
|
||||
assert source.kind == "system"
|
||||
@@ -214,9 +217,9 @@ def test_wf_admin_source_exists_but_is_not_planner_visible() -> None:
|
||||
assert "wf.admin" not in service.get_planner_catalog().snapshots
|
||||
|
||||
|
||||
def test_service_can_disable_builtin_stdlib_specs() -> None:
|
||||
def test_service_can_disable_builtin_stdlib_specs(tmp_path: Path) -> None:
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "no_builtin_store"),
|
||||
store=FileStore(tmp_path / "no_builtin_store"),
|
||||
include_builtin_specs=False,
|
||||
)
|
||||
|
||||
@@ -224,8 +227,8 @@ def test_service_can_disable_builtin_stdlib_specs() -> None:
|
||||
assert "wf.recipes" not in service.capability_sources
|
||||
|
||||
|
||||
def test_service_planner_catalog_excludes_hidden_sources() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "hidden_list_store"))
|
||||
def test_service_planner_catalog_excludes_hidden_sources(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "hidden_list_store"))
|
||||
hidden_echo_tool = NodeSpec(
|
||||
name="hidden.source.echo_tool",
|
||||
input_model=echo_tool.input_model,
|
||||
@@ -255,8 +258,10 @@ def test_service_planner_catalog_excludes_hidden_sources() -> None:
|
||||
assert "hidden.source.echo_tool" not in planner_names
|
||||
|
||||
|
||||
def test_service_catalog_split_keeps_system_specs_out_of_backend_catalog() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "planner_store"))
|
||||
def test_service_catalog_split_keeps_system_specs_out_of_backend_catalog(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "planner_store"))
|
||||
|
||||
backend_payload = service.get_catalog().as_payload()
|
||||
planner_payload = service.get_planner_catalog().as_payload()
|
||||
@@ -268,8 +273,10 @@ def test_service_catalog_split_keeps_system_specs_out_of_backend_catalog() -> No
|
||||
assert "wf.std.runtime_error" in available_names
|
||||
|
||||
|
||||
async def test_service_hydrates_planner_specs_from_stored_catalog() -> None:
|
||||
store = local_temp_root() / "restart_planner_store"
|
||||
async def test_service_hydrates_planner_specs_from_stored_catalog(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = tmp_path / "restart_planner_store"
|
||||
shutil.rmtree(store, ignore_errors=True)
|
||||
first_service = WfMcpService(store=FileStore(store))
|
||||
first_service.register_connection(
|
||||
@@ -298,8 +305,10 @@ async def test_service_hydrates_planner_specs_from_stored_catalog() -> None:
|
||||
assert run.output["echoed"] == "hello"
|
||||
|
||||
|
||||
def test_source_catalog_service_registers_and_lists_sources_directly() -> None:
|
||||
store = FileStore(local_temp_root() / "source_catalog_direct")
|
||||
def test_source_catalog_service_registers_and_lists_sources_directly(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = FileStore(tmp_path / "source_catalog_direct")
|
||||
|
||||
def unused_tool_executor(connection: ConnectionConfig):
|
||||
raise AssertionError("tool executor should not be used by source listing")
|
||||
@@ -333,19 +342,21 @@ def test_source_catalog_service_registers_and_lists_sources_directly() -> None:
|
||||
assert payload["sources"][0]["id"] == "demo.personal"
|
||||
|
||||
|
||||
def test_wfmcpservice_capability_sources_proxy_source_catalog() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "source_catalog_proxy"))
|
||||
def test_wfmcpservice_capability_sources_proxy_source_catalog(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "source_catalog_proxy"))
|
||||
|
||||
assert service.capability_sources is service.source_catalog.capability_sources
|
||||
assert "wf.std" in service.source_catalog.capability_sources
|
||||
|
||||
|
||||
def test_source_catalog_service_excludes_hidden_sources_from_planner_catalog() -> None:
|
||||
def test_source_catalog_service_excludes_hidden_sources_from_planner_catalog(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
def unused_tool_executor(connection: ConnectionConfig):
|
||||
raise AssertionError("tool executor should not be used by planner listing")
|
||||
|
||||
catalog = SourceCatalogService(
|
||||
store=FileStore(local_temp_root() / "source_catalog_hidden"),
|
||||
store=FileStore(tmp_path / "source_catalog_hidden"),
|
||||
connection_lookup=lambda connection_id: ConnectionConfig(
|
||||
id=connection_id,
|
||||
server="demo",
|
||||
@@ -410,10 +421,10 @@ def test_source_catalog_service_excludes_hidden_sources_from_planner_catalog() -
|
||||
assert "hidden.source.echo_tool" not in planner_names
|
||||
|
||||
|
||||
async def test_source_catalog_hydrates_connection_source_from_snapshot_directly() -> (
|
||||
None
|
||||
):
|
||||
root = local_temp_root() / "source_catalog_hydrate_direct"
|
||||
async def test_source_catalog_hydrates_connection_source_from_snapshot_directly(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = tmp_path / "source_catalog_hydrate_direct"
|
||||
shutil.rmtree(root, ignore_errors=True)
|
||||
first_service = WfMcpService(store=FileStore(root))
|
||||
first_service.register_connection(
|
||||
@@ -434,7 +445,9 @@ async def test_source_catalog_hydrates_connection_source_from_snapshot_directly(
|
||||
assert "demo.personal.echo_tool" in specs
|
||||
|
||||
|
||||
def test_source_catalog_register_specs_replaces_discovered_specs_directly() -> None:
|
||||
def test_source_catalog_register_specs_replaces_discovered_specs_directly(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
connection = ConnectionConfig(
|
||||
id="demo.personal",
|
||||
server="demo",
|
||||
@@ -445,7 +458,7 @@ def test_source_catalog_register_specs_replaces_discovered_specs_directly() -> N
|
||||
raise AssertionError("tool executor should not be used by spec registration")
|
||||
|
||||
catalog = SourceCatalogService(
|
||||
store=FileStore(local_temp_root() / "source_catalog_register_specs"),
|
||||
store=FileStore(tmp_path / "source_catalog_register_specs"),
|
||||
connection_lookup=lambda connection_id: connection,
|
||||
connection_list_enabled=lambda: [connection],
|
||||
connection_list_all=lambda: [connection],
|
||||
@@ -476,8 +489,10 @@ def test_source_catalog_register_specs_replaces_discovered_specs_directly() -> N
|
||||
assert catalog.store.load_catalog("demo.personal") is not None
|
||||
|
||||
|
||||
def test_source_catalog_finds_local_documentation_resource_directly() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "source_local_docs"))
|
||||
def test_source_catalog_finds_local_documentation_resource_directly(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "source_local_docs"))
|
||||
test_resource = DocumentationResource(
|
||||
name="test.docs.example",
|
||||
uri="wf://docs/example",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from wf_mcp.admin_surface import BrokerAdminHandlers, TransparentAdminHandlers
|
||||
@@ -8,11 +9,9 @@ from wf_mcp.broker import WfMcpService
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.storage import FileStore
|
||||
|
||||
from .test_support import local_temp_root
|
||||
|
||||
|
||||
def test_broker_admin_handlers_list_connections_and_events() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "admin_broker_store"))
|
||||
def test_broker_admin_handlers_list_connections_and_events(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "admin_broker_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
@@ -34,8 +33,8 @@ def test_broker_admin_handlers_list_connections_and_events() -> None:
|
||||
assert sources["total"] >= 2
|
||||
|
||||
|
||||
def test_broker_admin_handlers_report_failed_refresh_payload() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "admin_refresh_store"))
|
||||
def test_broker_admin_handlers_report_failed_refresh_payload(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "admin_refresh_store"))
|
||||
handlers = BrokerAdminHandlers(service)
|
||||
|
||||
payload = _run(handlers.refresh_connection_catalog("missing.personal"))
|
||||
@@ -45,8 +44,8 @@ def test_broker_admin_handlers_report_failed_refresh_payload() -> None:
|
||||
assert payload["error_type"] == "KeyError"
|
||||
|
||||
|
||||
def test_transparent_admin_handlers_delegate_config_operations() -> None:
|
||||
runtime = FakeProxyAdminRuntime()
|
||||
def test_transparent_admin_handlers_delegate_config_operations(tmp_path: Path) -> None:
|
||||
runtime = FakeProxyAdminRuntime(tmp_path)
|
||||
handlers = TransparentAdminHandlers(runtime)
|
||||
|
||||
connections = handlers.list_connections()
|
||||
@@ -130,10 +129,10 @@ class FakeManager:
|
||||
|
||||
|
||||
class FakeProxyAdminRuntime:
|
||||
def __init__(self) -> None:
|
||||
def __init__(self, tmp_path: Path) -> None:
|
||||
self.manager = FakeManager(added=[])
|
||||
self._config = BrokerConfig(
|
||||
store_root=local_temp_root() / "transparent_admin_handlers_store",
|
||||
store_root=tmp_path / "transparent_admin_handlers_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="demo.personal",
|
||||
|
||||
@@ -33,13 +33,12 @@ from .test_support import (
|
||||
FakeAdapter,
|
||||
echo_tool,
|
||||
input_binding,
|
||||
local_temp_root,
|
||||
output_binding,
|
||||
)
|
||||
|
||||
|
||||
def test_load_broker_config_resolves_relative_store_root() -> None:
|
||||
tmp_path = local_temp_root() / "broker_config_test"
|
||||
def test_load_broker_config_resolves_relative_store_root(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "broker_config_test"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
@@ -64,8 +63,10 @@ def test_load_broker_config_resolves_relative_store_root() -> None:
|
||||
assert [connection.id for connection in config.connections] == ["demo.personal"]
|
||||
|
||||
|
||||
def test_create_broker_server_exposes_tools_resources_and_prompts() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "broker_server_store"))
|
||||
def test_create_broker_server_exposes_tools_resources_and_prompts(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "broker_server_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
@@ -111,8 +112,8 @@ def test_create_broker_server_exposes_tools_resources_and_prompts() -> None:
|
||||
assert "demo.personal" in all_source_ids
|
||||
|
||||
|
||||
def test_broker_admin_tools_are_backed_by_wf_admin_source() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "broker_admin_source"))
|
||||
def test_broker_admin_tools_are_backed_by_wf_admin_source(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "broker_admin_source"))
|
||||
server = create_broker_server(service)
|
||||
|
||||
tools = asyncio.run(server.list_tools())
|
||||
@@ -125,9 +126,9 @@ def test_broker_admin_tools_are_backed_by_wf_admin_source() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_build_service_from_config_registers_connections() -> None:
|
||||
def test_build_service_from_config_registers_connections(tmp_path: Path) -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "broker_config_store",
|
||||
store_root=tmp_path / "broker_config_store",
|
||||
connections=[
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal"),
|
||||
ConnectionConfig(id="demo.work", server="demo", account="work"),
|
||||
@@ -140,8 +141,8 @@ def test_build_service_from_config_registers_connections() -> None:
|
||||
assert ids == ["demo.personal", "demo.work"]
|
||||
|
||||
|
||||
def test_broker_refresh_tool_returns_structured_error() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "broker_fail_store"))
|
||||
def test_broker_refresh_tool_returns_structured_error(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "broker_fail_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
@@ -162,11 +163,11 @@ def test_broker_refresh_tool_returns_structured_error() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_broker_lists_workflow_artifacts_from_artifact_store() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "broker_artifacts")
|
||||
def test_broker_lists_workflow_artifacts_from_artifact_store(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "broker_artifacts")
|
||||
artifact_store.save_artifact(_artifact())
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_artifacts_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_artifacts_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
@@ -181,13 +182,11 @@ def test_broker_lists_workflow_artifacts_from_artifact_store() -> None:
|
||||
assert "plan" not in nodes[0]
|
||||
|
||||
|
||||
def test_broker_inspects_workflow_artifact_from_artifact_store() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_inspect_artifacts"
|
||||
)
|
||||
def test_broker_inspects_workflow_artifact_from_artifact_store(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "broker_inspect_artifacts")
|
||||
artifact_store.save_artifact(_artifact())
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_inspect_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_inspect_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
@@ -205,10 +204,10 @@ def test_broker_inspects_workflow_artifact_from_artifact_store() -> None:
|
||||
assert artifact["plan"]["name"] == "summarize_docs"
|
||||
|
||||
|
||||
def test_broker_validates_workflow_deployment_from_artifact_store() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_validate_artifacts"
|
||||
)
|
||||
def test_broker_validates_workflow_deployment_from_artifact_store(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "broker_validate_artifacts")
|
||||
artifact_store.save_artifact(_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
@@ -221,7 +220,7 @@ def test_broker_validates_workflow_deployment_from_artifact_store() -> None:
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_validate_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_validate_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
@@ -240,12 +239,10 @@ def test_broker_validates_workflow_deployment_from_artifact_store() -> None:
|
||||
assert payload["diagnostics"][0]["code"] == "source_missing"
|
||||
|
||||
|
||||
def test_broker_saves_workflow_artifact() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_save_artifacts"
|
||||
)
|
||||
def test_broker_saves_workflow_artifact(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "broker_save_artifacts")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_save_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_save_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
@@ -264,12 +261,12 @@ def test_broker_saves_workflow_artifact() -> None:
|
||||
assert loaded.title == "Summarize Docs"
|
||||
|
||||
|
||||
def test_broker_creates_workflow_artifact_from_plan() -> None:
|
||||
def test_broker_creates_workflow_artifact_from_plan(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_create_artifact_from_plan"
|
||||
tmp_path / "broker_create_artifact_from_plan"
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_create_artifact_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_create_artifact_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
@@ -306,12 +303,10 @@ def test_broker_creates_workflow_artifact_from_plan() -> None:
|
||||
assert loaded.required_capability_map()["demo.echo_tool"].logical_source == "demo"
|
||||
|
||||
|
||||
def test_broker_saves_and_lists_workflow_deployments() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_save_deployments"
|
||||
)
|
||||
def test_broker_saves_and_lists_workflow_deployments(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "broker_save_deployments")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_save_deployments_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_save_deployments_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
@@ -345,10 +340,8 @@ def test_broker_saves_and_lists_workflow_deployments() -> None:
|
||||
assert "bindings" not in list_payload["deployments"][0]
|
||||
|
||||
|
||||
def test_broker_runs_non_interrupting_workflow_deployment() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_run_artifacts"
|
||||
)
|
||||
def test_broker_runs_non_interrupting_workflow_deployment(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "broker_run_artifacts")
|
||||
artifact_store.save_artifact(_echo_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
@@ -359,9 +352,9 @@ def test_broker_runs_non_interrupting_workflow_deployment() -> None:
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_run_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_run_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "broker_run_mcp_store"),
|
||||
run_store=FileRunStore(tmp_path / "broker_run_mcp_store"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -388,9 +381,11 @@ def test_broker_runs_non_interrupting_workflow_deployment() -> None:
|
||||
assert payload["trace_count"] > 0
|
||||
|
||||
|
||||
def test_broker_run_deployment_returns_unrunnable_for_dependency_errors() -> None:
|
||||
def test_broker_run_deployment_returns_unrunnable_for_dependency_errors(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_run_unrunnable_artifacts"
|
||||
tmp_path / "broker_run_unrunnable_artifacts"
|
||||
)
|
||||
artifact_store.save_artifact(_artifact())
|
||||
artifact_store.save_deployment(
|
||||
@@ -404,7 +399,7 @@ def test_broker_run_deployment_returns_unrunnable_for_dependency_errors() -> Non
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_run_unrunnable_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_run_unrunnable_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
@@ -425,9 +420,11 @@ def test_broker_run_deployment_returns_unrunnable_for_dependency_errors() -> Non
|
||||
assert payload["diagnostics"][0]["code"] == "source_missing"
|
||||
|
||||
|
||||
def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts() -> None:
|
||||
def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "broker_run_interrupt_artifacts"
|
||||
tmp_path / "broker_run_interrupt_artifacts"
|
||||
)
|
||||
artifact_store.save_artifact(_interrupt_artifact())
|
||||
artifact_store.save_deployment(
|
||||
@@ -439,9 +436,9 @@ def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts() -> No
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "broker_run_interrupt_mcp_store"),
|
||||
store=FileStore(tmp_path / "broker_run_interrupt_mcp_store"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "broker_run_interrupt_mcp_store"),
|
||||
run_store=FileRunStore(tmp_path / "broker_run_interrupt_mcp_store"),
|
||||
)
|
||||
server = create_broker_server(service)
|
||||
|
||||
@@ -478,8 +475,10 @@ def test_broker_run_deployment_pauses_and_resumes_interrupting_artifacts() -> No
|
||||
assert resumed["resume_readiness"] == "not_applicable"
|
||||
|
||||
|
||||
def test_build_service_from_config_uses_store_root_for_workflow_stores() -> None:
|
||||
store_root = local_temp_root() / "broker_config_workflow_stores"
|
||||
def test_build_service_from_config_uses_store_root_for_workflow_stores(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store_root = tmp_path / "broker_config_workflow_stores"
|
||||
config = BrokerConfig(store_root=store_root, connections=[])
|
||||
|
||||
service = build_service_from_config(config)
|
||||
@@ -512,8 +511,10 @@ def _registry_entry(
|
||||
)
|
||||
|
||||
|
||||
def test_build_service_from_config_loads_source_registry_entries() -> None:
|
||||
tmp_path = local_temp_root() / "broker_config_registry_load"
|
||||
def test_build_service_from_config_loads_source_registry_entries(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
tmp_path = tmp_path / "broker_config_registry_load"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config = BrokerConfig(store_root=tmp_path, connections=[])
|
||||
FileSourceRegistryStore(tmp_path).save_registry(
|
||||
@@ -527,8 +528,8 @@ def test_build_service_from_config_loads_source_registry_entries() -> None:
|
||||
assert "fixture.registry" in service.capability_sources
|
||||
|
||||
|
||||
def test_build_service_from_config_config_shadows_registry() -> None:
|
||||
tmp_path = local_temp_root() / "broker_config_registry_shadow"
|
||||
def test_build_service_from_config_config_shadows_registry(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "broker_config_registry_shadow"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config = BrokerConfig(
|
||||
store_root=tmp_path,
|
||||
|
||||
+14
-12
@@ -9,8 +9,6 @@ from pydantic import ValidationError
|
||||
from wf_mcp.broker import load_broker_config
|
||||
from wf_mcp.cli import build_parser, main
|
||||
|
||||
from .test_support import local_temp_root
|
||||
|
||||
|
||||
def _write_config(path: Path) -> None:
|
||||
path.write_text(
|
||||
@@ -100,8 +98,10 @@ def test_build_parser_accepts_no_admin_tools_flag() -> None:
|
||||
assert args.admin_tools is False
|
||||
|
||||
|
||||
def test_cli_connections_prints_configured_connections(capsys) -> None:
|
||||
tmp_path = local_temp_root() / "cli_connections_test"
|
||||
def test_cli_connections_prints_configured_connections(
|
||||
capsys: pytest.CaptureFixture[str], tmp_path: Path
|
||||
) -> None:
|
||||
tmp_path = tmp_path / "cli_connections_test"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
_write_config(config_path)
|
||||
@@ -115,9 +115,9 @@ def test_cli_connections_prints_configured_connections(capsys) -> None:
|
||||
|
||||
|
||||
def test_cli_catalog_prints_empty_catalog_when_not_refreshed(
|
||||
capsys,
|
||||
capsys: pytest.CaptureFixture[str], tmp_path: Path
|
||||
) -> None:
|
||||
tmp_path = local_temp_root() / "cli_catalog_test"
|
||||
tmp_path = tmp_path / "cli_catalog_test"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
_write_config(config_path)
|
||||
@@ -132,8 +132,10 @@ def test_cli_catalog_prints_empty_catalog_when_not_refreshed(
|
||||
assert payload["prompts"] == []
|
||||
|
||||
|
||||
def test_cli_status_prints_connection_statuses(capsys) -> None:
|
||||
tmp_path = local_temp_root() / "cli_status_test"
|
||||
def test_cli_status_prints_connection_statuses(
|
||||
capsys: pytest.CaptureFixture[str], tmp_path: Path
|
||||
) -> None:
|
||||
tmp_path = tmp_path / "cli_status_test"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
_write_config(config_path)
|
||||
@@ -159,8 +161,8 @@ def test_cli_status_prints_connection_statuses(capsys) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_load_broker_config_normalizes_typed_stdio_metadata() -> None:
|
||||
tmp_path = local_temp_root() / "cli_typed_stdio_config_test"
|
||||
def test_load_broker_config_normalizes_typed_stdio_metadata(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "cli_typed_stdio_config_test"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
@@ -195,8 +197,8 @@ def test_load_broker_config_normalizes_typed_stdio_metadata() -> None:
|
||||
}
|
||||
|
||||
|
||||
def test_load_broker_config_rejects_bad_metadata_shape() -> None:
|
||||
tmp_path = local_temp_root() / "cli_bad_config_test"
|
||||
def test_load_broker_config_rejects_bad_metadata_shape(tmp_path: Path) -> None:
|
||||
tmp_path = tmp_path / "cli_bad_config_test"
|
||||
tmp_path.mkdir(parents=True, exist_ok=True)
|
||||
config_path = tmp_path / "wf_mcp.config.json"
|
||||
config_path.write_text(
|
||||
|
||||
@@ -2,13 +2,14 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
|
||||
from wf_mcp.broker import WfMcpService
|
||||
from wf_mcp.events import EventBus, InMemoryEventSink, McpEvent, make_event
|
||||
from wf_mcp.models import ConnectionConfig
|
||||
from wf_mcp.storage import FileStore
|
||||
|
||||
from .test_support import FakeAdapter, echo_tool, local_temp_root
|
||||
from .test_support import FakeAdapter, echo_tool
|
||||
|
||||
|
||||
def test_event_bus_fans_out_to_subscribers() -> None:
|
||||
@@ -24,11 +25,11 @@ def test_event_bus_fans_out_to_subscribers() -> None:
|
||||
assert seen_kinds == ["catalog_changed"]
|
||||
|
||||
|
||||
def test_service_records_events_through_event_bus() -> None:
|
||||
def test_service_records_events_through_event_bus(tmp_path: Path) -> None:
|
||||
sink = InMemoryEventSink()
|
||||
bus = EventBus(sink)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "event_bus_service_store"),
|
||||
store=FileStore(tmp_path / "event_bus_service_store"),
|
||||
event_bus=bus,
|
||||
)
|
||||
|
||||
@@ -40,8 +41,8 @@ def test_service_records_events_through_event_bus() -> None:
|
||||
assert sink.list_events()[0] is service.list_events()[0]
|
||||
|
||||
|
||||
def test_register_specs_emits_tool_and_catalog_change_events() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "spec_change_store"))
|
||||
def test_register_specs_emits_tool_and_catalog_change_events(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "spec_change_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
@@ -66,8 +67,8 @@ def test_register_specs_emits_tool_and_catalog_change_events() -> None:
|
||||
assert catalog_changed[0].payload["reason"] == "specs_registered"
|
||||
|
||||
|
||||
def test_refresh_catalog_emits_capability_change_events() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "refresh_change_store"))
|
||||
def test_refresh_catalog_emits_capability_change_events(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "refresh_change_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import mcp.types as mcp_types
|
||||
import pytest
|
||||
@@ -11,7 +12,7 @@ from mcp.client.stdio import StdioServerParameters, stdio_client
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.proxy import create_proxy_client
|
||||
|
||||
from .test_support import fixture_server_path, local_temp_root
|
||||
from .test_support import fixture_server_path
|
||||
|
||||
|
||||
def test_fixture_server_initialize_capabilities_are_observable_directly() -> None:
|
||||
@@ -40,9 +41,11 @@ def test_fixture_server_initialize_capabilities_are_observable_directly() -> Non
|
||||
assert capabilities.logging is None
|
||||
|
||||
|
||||
def test_unified_proxy_initialize_capabilities_reflect_local_surface() -> None:
|
||||
def test_unified_proxy_initialize_capabilities_reflect_local_surface(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "protocol_capabilities_store",
|
||||
store_root=tmp_path / "protocol_capabilities_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
|
||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import asyncio
|
||||
import sys
|
||||
from collections.abc import Awaitable, Callable
|
||||
from pathlib import Path
|
||||
|
||||
import mcp.types as mcp_types
|
||||
import pytest
|
||||
@@ -12,7 +13,7 @@ from mcp.client.stdio import StdioServerParameters, stdio_client
|
||||
from wf_mcp.models import BrokerConfig, ConnectionConfig
|
||||
from wf_mcp.proxy import create_proxy_client
|
||||
|
||||
from .test_support import fixture_server_path, local_temp_root
|
||||
from .test_support import fixture_server_path
|
||||
|
||||
NotificationProbe = Callable[
|
||||
[Callable[[mcp_types.ServerNotification], None]],
|
||||
@@ -70,9 +71,9 @@ def test_fixture_server_emits_observable_protocol_notifications_directly() -> No
|
||||
assert "notifications/message" in methods
|
||||
|
||||
|
||||
def _fixture_proxy_notification_methods() -> list[str]:
|
||||
def _fixture_proxy_notification_methods(tmp_path: Path) -> list[str]:
|
||||
config = BrokerConfig(
|
||||
store_root=local_temp_root() / "protocol_relay_store",
|
||||
store_root=tmp_path / "protocol_relay_store",
|
||||
connections=[
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
@@ -105,8 +106,10 @@ def _fixture_proxy_notification_methods() -> list[str]:
|
||||
return _notification_methods(notifications)
|
||||
|
||||
|
||||
def test_proxy_does_not_relay_generic_upstream_notifications_yet() -> None:
|
||||
methods = _fixture_proxy_notification_methods()
|
||||
def test_proxy_does_not_relay_generic_upstream_notifications_yet(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
methods = _fixture_proxy_notification_methods(tmp_path)
|
||||
|
||||
# Stateful proxy sessions preserve FastMCP's supported request callbacks,
|
||||
# but generic upstream change/update notification rebroadcast is separate
|
||||
@@ -124,7 +127,9 @@ def test_proxy_does_not_relay_generic_upstream_notifications_yet() -> None:
|
||||
"data; valid string-valued MCP logging data is rejected upstream."
|
||||
),
|
||||
)
|
||||
def test_proxy_relays_string_valued_upstream_log_when_fastmcp_supports_it() -> None:
|
||||
methods = _fixture_proxy_notification_methods()
|
||||
def test_proxy_relays_string_valued_upstream_log_when_fastmcp_supports_it(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
methods = _fixture_proxy_notification_methods(tmp_path)
|
||||
|
||||
assert "notifications/message" in methods
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from wf_api.saved_subgraphs import resolve_saved_subgraph_tree
|
||||
@@ -17,11 +18,11 @@ from wf_mcp.models import ConnectionConfig
|
||||
from wf_mcp.storage import FileStore
|
||||
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
|
||||
|
||||
from .test_support import echo_tool, input_binding, local_temp_root, output_binding
|
||||
from .test_support import echo_tool, input_binding, output_binding
|
||||
|
||||
|
||||
def test_saved_subgraph_tree_loads_exact_child_artifact_version() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_tree")
|
||||
def test_saved_subgraph_tree_loads_exact_child_artifact_version(tmp_path: Path) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_tree")
|
||||
parent = _parent_artifact()
|
||||
store.save_artifact(_leaf_artifact())
|
||||
|
||||
@@ -35,8 +36,8 @@ def test_saved_subgraph_tree_loads_exact_child_artifact_version() -> None:
|
||||
assert resolution.artifacts_by_ref["workflow.child.v1"].version == 1
|
||||
|
||||
|
||||
def test_saved_subgraph_tree_reports_missing_child_artifact() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_missing")
|
||||
def test_saved_subgraph_tree_reports_missing_child_artifact(tmp_path: Path) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_missing")
|
||||
|
||||
resolution = resolve_saved_subgraph_tree(
|
||||
root_artifact=_parent_artifact(),
|
||||
@@ -48,8 +49,8 @@ def test_saved_subgraph_tree_reports_missing_child_artifact() -> None:
|
||||
assert resolution.diagnostics[0].logical_ref == "workflow.child.v1"
|
||||
|
||||
|
||||
def test_saved_subgraph_tree_reports_recursive_child_cycle() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_cycle")
|
||||
def test_saved_subgraph_tree_reports_recursive_child_cycle(tmp_path: Path) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_cycle")
|
||||
parent = _parent_artifact()
|
||||
child = _parent_artifact(
|
||||
artifact_id="child",
|
||||
@@ -69,12 +70,14 @@ def test_saved_subgraph_tree_reports_recursive_child_cycle() -> None:
|
||||
assert resolution.diagnostics[0].logical_ref == "workflow.parent.v1"
|
||||
|
||||
|
||||
def test_saved_child_uses_parent_deployment_binding_for_validation() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_validate")
|
||||
def test_saved_child_uses_parent_deployment_binding_for_validation(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_validate")
|
||||
store.save_artifact(_parent_artifact())
|
||||
store.save_artifact(_leaf_artifact())
|
||||
store.save_deployment(_deployment())
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
result = asyncio.run(handlers.validate_deployment(deployment_id="parent.personal"))
|
||||
|
||||
@@ -82,12 +85,12 @@ def test_saved_child_uses_parent_deployment_binding_for_validation() -> None:
|
||||
assert result["diagnostics"] == []
|
||||
|
||||
|
||||
def test_saved_child_missing_parent_binding_is_unrunnable() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_unbound")
|
||||
def test_saved_child_missing_parent_binding_is_unrunnable(tmp_path: Path) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_unbound")
|
||||
store.save_artifact(_parent_artifact())
|
||||
store.save_artifact(_leaf_artifact())
|
||||
store.save_deployment(_deployment(bindings={}))
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
result = asyncio.run(handlers.validate_deployment(deployment_id="parent.personal"))
|
||||
|
||||
@@ -96,14 +99,14 @@ def test_saved_child_missing_parent_binding_is_unrunnable() -> None:
|
||||
assert result["diagnostics"][0]["logical_ref"] == "demo.echo_tool"
|
||||
|
||||
|
||||
def test_interrupting_saved_child_pauses_and_resumes_through_deployment_surface() -> (
|
||||
None
|
||||
):
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_interrupt")
|
||||
def test_interrupting_saved_child_pauses_and_resumes_through_deployment_surface(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_interrupt")
|
||||
store.save_artifact(_parent_artifact())
|
||||
store.save_artifact(_interrupting_child_artifact())
|
||||
store.save_deployment(_deployment())
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
validation = asyncio.run(
|
||||
handlers.validate_deployment(deployment_id="parent.personal")
|
||||
@@ -125,7 +128,7 @@ def test_interrupting_saved_child_pauses_and_resumes_through_deployment_surface(
|
||||
assert paused["interrupt"]["payload"]["question"] == "hello"
|
||||
|
||||
# Durable resume must not rely on the process-local handler instance.
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
resumed = asyncio.run(
|
||||
handlers.resume_run(
|
||||
run_id=paused["run_id"],
|
||||
@@ -138,12 +141,14 @@ def test_interrupting_saved_child_pauses_and_resumes_through_deployment_surface(
|
||||
assert resumed["output"]["echoed"] == "world"
|
||||
|
||||
|
||||
def test_interrupted_saved_child_blocks_resume_until_pinned_source_returns() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_blocked")
|
||||
def test_interrupted_saved_child_blocks_resume_until_pinned_source_returns(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_blocked")
|
||||
store.save_artifact(_parent_artifact())
|
||||
store.save_artifact(_interrupting_child_artifact(requires_demo=True))
|
||||
store.save_deployment(_deployment())
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
paused = asyncio.run(
|
||||
handlers.run_deployment(
|
||||
@@ -184,11 +189,13 @@ def test_interrupted_saved_child_blocks_resume_until_pinned_source_returns() ->
|
||||
assert run_store.get_latest_checkpoint(paused["run_id"]).sequence == 2
|
||||
|
||||
|
||||
def test_missing_saved_child_is_unrunnable_on_deployment_surface() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_missing_run")
|
||||
def test_missing_saved_child_is_unrunnable_on_deployment_surface(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_missing_run")
|
||||
store.save_artifact(_parent_artifact())
|
||||
store.save_deployment(_deployment())
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
result = asyncio.run(handlers.validate_deployment(deployment_id="parent.personal"))
|
||||
|
||||
@@ -197,8 +204,8 @@ def test_missing_saved_child_is_unrunnable_on_deployment_surface() -> None:
|
||||
assert result["diagnostics"][0]["logical_ref"] == "workflow.child.v1"
|
||||
|
||||
|
||||
def test_cyclic_saved_child_is_unrunnable_on_deployment_surface() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_cycle_run")
|
||||
def test_cyclic_saved_child_is_unrunnable_on_deployment_surface(tmp_path: Path) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_cycle_run")
|
||||
store.save_artifact(_parent_artifact())
|
||||
store.save_artifact(
|
||||
_parent_artifact(
|
||||
@@ -208,7 +215,7 @@ def test_cyclic_saved_child_is_unrunnable_on_deployment_surface() -> None:
|
||||
)
|
||||
)
|
||||
store.save_deployment(_deployment())
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
result = asyncio.run(handlers.validate_deployment(deployment_id="parent.personal"))
|
||||
|
||||
@@ -217,12 +224,14 @@ def test_cyclic_saved_child_is_unrunnable_on_deployment_surface() -> None:
|
||||
assert result["diagnostics"][0]["logical_ref"] == "workflow.parent.v1"
|
||||
|
||||
|
||||
def test_saved_child_runs_natively_with_parent_deployment_binding() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_run")
|
||||
def test_saved_child_runs_natively_with_parent_deployment_binding(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_run")
|
||||
store.save_artifact(_parent_artifact())
|
||||
store.save_artifact(_leaf_artifact())
|
||||
store.save_deployment(_deployment())
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
result = asyncio.run(
|
||||
handlers.run_deployment(
|
||||
@@ -236,8 +245,8 @@ def test_saved_child_runs_natively_with_parent_deployment_binding() -> None:
|
||||
assert result["diagnostics"] == []
|
||||
|
||||
|
||||
def test_nested_saved_child_inherits_root_deployment_binding() -> None:
|
||||
store = FileWorkflowArtifactStore(local_temp_root() / "saved_subgraph_nested_run")
|
||||
def test_nested_saved_child_inherits_root_deployment_binding(tmp_path: Path) -> None:
|
||||
store = FileWorkflowArtifactStore(tmp_path / "saved_subgraph_nested_run")
|
||||
store.save_artifact(_parent_artifact(child_artifact_id="middle"))
|
||||
store.save_artifact(
|
||||
_parent_artifact(
|
||||
@@ -248,7 +257,7 @@ def test_nested_saved_child_inherits_root_deployment_binding() -> None:
|
||||
)
|
||||
store.save_artifact(_leaf_artifact())
|
||||
store.save_deployment(_deployment())
|
||||
handlers = _handlers(store)
|
||||
handlers = _handlers(store, tmp_path)
|
||||
|
||||
result = asyncio.run(
|
||||
handlers.run_deployment(
|
||||
@@ -381,8 +390,10 @@ def _deployment(*, bindings: dict[str, str] | None = None) -> WorkflowDeployment
|
||||
)
|
||||
|
||||
|
||||
def _handlers(artifact_store: FileWorkflowArtifactStore) -> WorkflowSurfaceHandlers:
|
||||
mcp_root = local_temp_root() / f"{artifact_store.root.name}_mcp"
|
||||
def _handlers(
|
||||
artifact_store: FileWorkflowArtifactStore, tmp_path: Path
|
||||
) -> WorkflowSurfaceHandlers:
|
||||
mcp_root = tmp_path / f"{artifact_store.root.name}_mcp"
|
||||
service = WfMcpService(
|
||||
store=FileStore(mcp_root),
|
||||
artifact_store=artifact_store,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from mcp import McpError
|
||||
@@ -16,7 +16,6 @@ from wf_sources_mcp.connections import mcp_source_connection_from_connection_con
|
||||
from .test_support import (
|
||||
everything_server_connection,
|
||||
fixture_server_path,
|
||||
local_temp_root,
|
||||
sys,
|
||||
)
|
||||
|
||||
@@ -70,8 +69,9 @@ class _WrappedToolsOnlyAdapter(_ToolsOnlyAdapter):
|
||||
)
|
||||
|
||||
|
||||
def test_mcp_sdk_adapter_lists_and_calls_stdio_tool() -> None:
|
||||
service = WfMcpService(store=FileStore(local_temp_root() / "sdk_adapter_store"))
|
||||
@pytest.mark.asyncio
|
||||
async def test_mcp_sdk_adapter_lists_and_calls_stdio_tool(tmp_path: Path) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "sdk_adapter_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(
|
||||
id="fixture.personal",
|
||||
@@ -87,7 +87,7 @@ def test_mcp_sdk_adapter_lists_and_calls_stdio_tool() -> None:
|
||||
service.register_adapter("fixture", McpSdkAdapter())
|
||||
|
||||
try:
|
||||
asyncio.run(service.refresh_connection_catalog("fixture.personal"))
|
||||
await service.refresh_connection_catalog("fixture.personal")
|
||||
except PermissionError as exc:
|
||||
pytest.skip(f"stdio MCP transport is not permitted in this environment: {exc}")
|
||||
|
||||
@@ -117,14 +117,10 @@ def test_mcp_sdk_adapter_lists_and_calls_stdio_tool() -> None:
|
||||
}
|
||||
]
|
||||
|
||||
resource_result = asyncio.run(
|
||||
service.read_resource("fixture.personal.resource.welcome")
|
||||
)
|
||||
prompt_result = asyncio.run(
|
||||
service.render_prompt(
|
||||
"fixture.personal.prompt.summarize",
|
||||
arguments={"text": "hello"},
|
||||
)
|
||||
resource_result = await service.read_resource("fixture.personal.resource.welcome")
|
||||
prompt_result = await service.render_prompt(
|
||||
"fixture.personal.prompt.summarize",
|
||||
arguments={"text": "hello"},
|
||||
)
|
||||
assert (
|
||||
resource_result["contents"][0]["text"] == "Welcome from the fixture MCP server."
|
||||
@@ -133,7 +129,7 @@ def test_mcp_sdk_adapter_lists_and_calls_stdio_tool() -> None:
|
||||
prompt_result["messages"][0]["content"]["text"]
|
||||
== "Summarize this text:\n\nhello"
|
||||
)
|
||||
ping_result = asyncio.run(service.invoke_method("fixture.personal", "ping"))
|
||||
ping_result = await service.invoke_method("fixture.personal", "ping")
|
||||
assert ping_result == {}
|
||||
|
||||
adapter = McpSdkAdapter()
|
||||
@@ -141,13 +137,11 @@ def test_mcp_sdk_adapter_lists_and_calls_stdio_tool() -> None:
|
||||
source_connection = mcp_source_connection_from_connection_config(
|
||||
service.connections.get("fixture.personal")
|
||||
)
|
||||
result = asyncio.run(
|
||||
adapter.call_tool(
|
||||
connection=source_connection,
|
||||
auth=None,
|
||||
tool_name="echo_tool",
|
||||
payload={"text": "hello"},
|
||||
)
|
||||
result = await adapter.call_tool(
|
||||
connection=source_connection,
|
||||
auth=None,
|
||||
tool_name="echo_tool",
|
||||
payload={"text": "hello"},
|
||||
)
|
||||
except PermissionError as exc:
|
||||
pytest.skip(f"stdio MCP transport is not permitted in this environment: {exc}")
|
||||
@@ -155,21 +149,19 @@ def test_mcp_sdk_adapter_lists_and_calls_stdio_tool() -> None:
|
||||
assert result.output == {"echoed": "hello"}
|
||||
|
||||
|
||||
def test_mcp_sdk_adapter_can_probe_everything_server() -> None:
|
||||
async def test_mcp_sdk_adapter_can_probe_everything_server(tmp_path: Path) -> None:
|
||||
connection = everything_server_connection()
|
||||
if connection is None:
|
||||
pytest.skip(
|
||||
"set MCP_EVERYTHING_COMMAND to enable the live everything-server integration test"
|
||||
)
|
||||
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "everything_server_store")
|
||||
)
|
||||
service = WfMcpService(store=FileStore(tmp_path / "everything_server_store"))
|
||||
service.register_connection(connection)
|
||||
service.register_adapter("everything", McpSdkAdapter())
|
||||
|
||||
try:
|
||||
asyncio.run(service.refresh_connection_catalog("everything.default"))
|
||||
await service.refresh_connection_catalog("everything.default")
|
||||
except PermissionError as exc:
|
||||
pytest.skip(f"live MCP transport is not permitted in this environment: {exc}")
|
||||
|
||||
@@ -183,10 +175,10 @@ def test_mcp_sdk_adapter_can_probe_everything_server() -> None:
|
||||
assert "prompts" in payload
|
||||
|
||||
|
||||
def test_refresh_catalog_keeps_tools_when_optional_lists_are_unsupported() -> None:
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "tools_only_server_store")
|
||||
)
|
||||
async def test_refresh_catalog_keeps_tools_when_optional_lists_are_unsupported(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(store=FileStore(tmp_path / "tools_only_server_store"))
|
||||
service.register_connection(
|
||||
ConnectionConfig(
|
||||
id="tools_only.personal",
|
||||
@@ -197,7 +189,7 @@ def test_refresh_catalog_keeps_tools_when_optional_lists_are_unsupported() -> No
|
||||
)
|
||||
service.register_adapter("tools_only", _ToolsOnlyAdapter())
|
||||
|
||||
asyncio.run(service.refresh_connection_catalog("tools_only.personal"))
|
||||
await service.refresh_connection_catalog("tools_only.personal")
|
||||
|
||||
payload = service.get_catalog().as_payload()
|
||||
assert payload["nodes"][0]["qualified_name"] == "tools_only.personal.echo_tool"
|
||||
@@ -205,9 +197,11 @@ def test_refresh_catalog_keeps_tools_when_optional_lists_are_unsupported() -> No
|
||||
assert payload["prompts"] == []
|
||||
|
||||
|
||||
def test_refresh_catalog_unwraps_taskgroup_method_not_found() -> None:
|
||||
async def test_refresh_catalog_unwraps_taskgroup_method_not_found(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "wrapped_tools_only_server_store")
|
||||
store=FileStore(tmp_path / "wrapped_tools_only_server_store")
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(
|
||||
@@ -219,7 +213,7 @@ def test_refresh_catalog_unwraps_taskgroup_method_not_found() -> None:
|
||||
)
|
||||
service.register_adapter("wrapped_tools_only", _WrappedToolsOnlyAdapter())
|
||||
|
||||
asyncio.run(service.refresh_connection_catalog("wrapped_tools_only.personal"))
|
||||
await service.refresh_connection_catalog("wrapped_tools_only.personal")
|
||||
|
||||
payload = service.get_catalog().as_payload()
|
||||
assert payload["nodes"][0]["qualified_name"] == (
|
||||
|
||||
@@ -7,11 +7,9 @@ from wf_mcp.connections import parse_connection_id
|
||||
from wf_mcp.models import AuthRecord, CatalogSnapshot
|
||||
from wf_mcp.storage import FileAuthStore, FileCatalogStore, FileStore
|
||||
|
||||
from .test_support import local_temp_root
|
||||
|
||||
|
||||
def test_file_store_round_trips_auth() -> None:
|
||||
store = FileStore(local_temp_root() / "auth_store")
|
||||
def test_file_store_round_trips_auth(tmp_path) -> None:
|
||||
store = FileStore(tmp_path / "auth_store")
|
||||
record = AuthRecord(
|
||||
connection_id="demo.personal",
|
||||
scheme="oauth",
|
||||
|
||||
@@ -3,7 +3,8 @@ from __future__ import annotations
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Any, overload
|
||||
from warnings import deprecated
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
@@ -47,8 +48,17 @@ def finalize_tool(
|
||||
)
|
||||
|
||||
|
||||
def local_temp_root() -> Path:
|
||||
root = Path("test-artifacts") / "wf_mcp_store"
|
||||
@deprecated("Use pytests tmp_path fixture instead")
|
||||
@overload
|
||||
def local_temp_root() -> Path: ...
|
||||
|
||||
|
||||
@overload
|
||||
def local_temp_root(root_path: Path) -> Path: ...
|
||||
|
||||
|
||||
def local_temp_root(root_path: Path | None = None) -> Path:
|
||||
root = root_path or (Path("test-artifacts") / "wf_mcp_store")
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
return root
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from wf_artifacts import (
|
||||
FileDraftWorkspaceStore,
|
||||
@@ -15,7 +16,7 @@ from wf_mcp.storage import FileStore
|
||||
from wf_mcp.workflow_surface import WorkflowSurfaceHandlers
|
||||
from wf_mcp.workflow_surface.models import CreateMinimalDraftWorkspaceRequest
|
||||
|
||||
from ..test_support import echo_tool, local_temp_root
|
||||
from ..test_support import echo_tool
|
||||
from .conftest import (
|
||||
ContentOnlyOutputAdapter,
|
||||
echo_draft,
|
||||
@@ -24,14 +25,12 @@ from .conftest import (
|
||||
)
|
||||
|
||||
|
||||
def test_workflow_surface_rejects_unknown_draft_route_outcome_when_spec_is_known() -> (
|
||||
None
|
||||
):
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_draft_bad_outcome"
|
||||
)
|
||||
def test_workflow_surface_rejects_unknown_draft_route_outcome_when_spec_is_known(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_draft_bad_outcome")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_draft_bad_outcome_mcp"),
|
||||
store=FileStore(tmp_path / "surface_draft_bad_outcome_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -48,14 +47,12 @@ def test_workflow_surface_rejects_unknown_draft_route_outcome_when_spec_is_known
|
||||
assert payload["diagnostics"][0]["path"] == "routes.echo.typo"
|
||||
|
||||
|
||||
def test_workflow_surface_creates_artifact_from_draft_with_binding_suggestions() -> (
|
||||
None
|
||||
):
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_draft_create"
|
||||
)
|
||||
def test_workflow_surface_creates_artifact_from_draft_with_binding_suggestions(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_draft_create")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_draft_create_mcp"),
|
||||
store=FileStore(tmp_path / "surface_draft_create_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -85,10 +82,10 @@ def test_workflow_surface_creates_artifact_from_draft_with_binding_suggestions()
|
||||
assert artifact.required_capability_map()["demo.echo_tool"].logical_source == "demo"
|
||||
|
||||
|
||||
def test_workflow_surface_draft_artifact_requires_std_self_binding() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_draft_missing_std"
|
||||
)
|
||||
def test_workflow_surface_draft_artifact_requires_std_self_binding(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_draft_missing_std")
|
||||
h = handlers(artifact_store)
|
||||
|
||||
asyncio.run(
|
||||
@@ -119,15 +116,15 @@ def test_workflow_surface_draft_artifact_requires_std_self_binding() -> None:
|
||||
assert payload["diagnostics"][0]["logical_ref"] == "wf.std.replace"
|
||||
|
||||
|
||||
def test_workflow_surface_validates_draft_workspace_with_live_outcomes() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_validate"
|
||||
)
|
||||
def test_workflow_surface_validates_draft_workspace_with_live_outcomes(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_workspace_validate")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_workspace_validate_mcp"),
|
||||
store=FileStore(tmp_path / "surface_workspace_validate_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_workspace_validate_mcp"
|
||||
tmp_path / "surface_workspace_validate_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -153,15 +150,15 @@ def test_workflow_surface_validates_draft_workspace_with_live_outcomes() -> None
|
||||
assert fetched["status"] == "invalid"
|
||||
|
||||
|
||||
def test_workflow_surface_creates_minimal_draft_workspace_with_error_route() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_minimal_workspace"
|
||||
)
|
||||
def test_workflow_surface_creates_minimal_draft_workspace_with_error_route(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_minimal_workspace")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_minimal_workspace_mcp"),
|
||||
store=FileStore(tmp_path / "surface_minimal_workspace_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_minimal_workspace_mcp"
|
||||
tmp_path / "surface_minimal_workspace_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -205,14 +202,16 @@ def test_workflow_surface_creates_minimal_draft_workspace_with_error_route() ->
|
||||
]
|
||||
|
||||
|
||||
def test_workflow_surface_minimal_draft_honors_explicit_error_message_source() -> None:
|
||||
def test_workflow_surface_minimal_draft_honors_explicit_error_message_source(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_minimal_explicit_error_mcp"),
|
||||
store=FileStore(tmp_path / "surface_minimal_explicit_error_mcp"),
|
||||
artifact_store=FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_minimal_explicit_error"
|
||||
tmp_path / "surface_minimal_explicit_error"
|
||||
),
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_minimal_explicit_error_mcp"
|
||||
tmp_path / "surface_minimal_explicit_error_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -266,14 +265,16 @@ def test_minimal_draft_request_accepts_structural_error_message_source() -> None
|
||||
assert request.error_message_source.parts == ("error_message",)
|
||||
|
||||
|
||||
def test_workflow_surface_accepts_canonical_bindings_for_minimal_workspace() -> None:
|
||||
def test_workflow_surface_accepts_canonical_bindings_for_minimal_workspace(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_minimal_canonical_mcp"),
|
||||
store=FileStore(tmp_path / "surface_minimal_canonical_mcp"),
|
||||
artifact_store=FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_minimal_canonical"
|
||||
tmp_path / "surface_minimal_canonical"
|
||||
),
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_minimal_canonical_mcp"
|
||||
tmp_path / "surface_minimal_canonical_mcp"
|
||||
),
|
||||
)
|
||||
h = WorkflowSurfaceHandlers(service)
|
||||
@@ -318,15 +319,17 @@ def test_workflow_surface_accepts_canonical_bindings_for_minimal_workspace() ->
|
||||
]
|
||||
|
||||
|
||||
def test_workflow_surface_creates_draft_workspace_from_capability_hints() -> None:
|
||||
def test_workflow_surface_creates_draft_workspace_from_capability_hints(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_from_capability"
|
||||
tmp_path / "surface_workspace_from_capability"
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_workspace_from_capability_mcp"),
|
||||
store=FileStore(tmp_path / "surface_workspace_from_capability_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_workspace_from_capability_mcp"
|
||||
tmp_path / "surface_workspace_from_capability_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -374,15 +377,13 @@ def test_workflow_surface_creates_draft_workspace_from_capability_hints() -> Non
|
||||
]
|
||||
|
||||
|
||||
def test_workflow_surface_creates_artifact_from_workspace() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_artifact"
|
||||
)
|
||||
def test_workflow_surface_creates_artifact_from_workspace(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_workspace_artifact")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_workspace_artifact_mcp"),
|
||||
store=FileStore(tmp_path / "surface_workspace_artifact_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_workspace_artifact_mcp"
|
||||
tmp_path / "surface_workspace_artifact_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -419,15 +420,17 @@ def test_workflow_surface_creates_artifact_from_workspace() -> None:
|
||||
assert required.output_schema_snapshot is not None
|
||||
|
||||
|
||||
def test_workflow_surface_workspace_artifact_infers_raw_concrete_dependency() -> None:
|
||||
def test_workflow_surface_workspace_artifact_infers_raw_concrete_dependency(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_artifact_raw_dependency"
|
||||
tmp_path / "surface_workspace_artifact_raw_dependency"
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_workspace_artifact_raw_mcp"),
|
||||
store=FileStore(tmp_path / "surface_workspace_artifact_raw_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_workspace_artifact_raw_mcp"
|
||||
tmp_path / "surface_workspace_artifact_raw_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -459,15 +462,13 @@ def test_workflow_surface_workspace_artifact_infers_raw_concrete_dependency() ->
|
||||
assert required.output_schema_snapshot is not None
|
||||
|
||||
|
||||
def test_workflow_surface_creates_wrapper_from_workspace() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_wrapper"
|
||||
)
|
||||
def test_workflow_surface_creates_wrapper_from_workspace(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_workspace_wrapper")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_workspace_wrapper_mcp"),
|
||||
store=FileStore(tmp_path / "surface_workspace_wrapper_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_workspace_wrapper_mcp"
|
||||
tmp_path / "surface_workspace_wrapper_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -499,15 +500,17 @@ def test_workflow_surface_creates_wrapper_from_workspace() -> None:
|
||||
assert artifact.plan["nodes"][0]["node"] == "demo.echo_tool"
|
||||
|
||||
|
||||
def test_workflow_surface_low_confidence_draft_returns_patch_guidance() -> None:
|
||||
def test_workflow_surface_low_confidence_draft_returns_patch_guidance(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_workspace_low_confidence"
|
||||
tmp_path / "surface_workspace_low_confidence"
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_workspace_low_confidence_mcp"),
|
||||
store=FileStore(tmp_path / "surface_workspace_low_confidence_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
draft_workspace_store=FileDraftWorkspaceStore(
|
||||
local_temp_root() / "surface_workspace_low_confidence_mcp"
|
||||
tmp_path / "surface_workspace_low_confidence_mcp"
|
||||
),
|
||||
)
|
||||
service.register_connection(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from wf_artifacts import FileRunStore, FileWorkflowArtifactStore, WorkflowDeployment
|
||||
from wf_mcp.broker import WfMcpService
|
||||
@@ -15,7 +16,7 @@ from wf_platform import (
|
||||
SourceVisibility,
|
||||
)
|
||||
|
||||
from ..test_support import echo_tool, local_temp_root
|
||||
from ..test_support import echo_tool
|
||||
from .conftest import (
|
||||
amount_tool,
|
||||
changed_echo_tool,
|
||||
@@ -37,8 +38,8 @@ def test_raw_workflow_plan_uses_core_step_and_edge_models() -> None:
|
||||
assert plan.edges[0].outcome == "ok"
|
||||
|
||||
|
||||
def test_workflow_surface_runs_non_interrupting_deployment() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "surface_run")
|
||||
def test_workflow_surface_runs_non_interrupting_deployment(tmp_path: Path) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_run")
|
||||
artifact_store.save_artifact(echo_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
@@ -49,9 +50,9 @@ def test_workflow_surface_runs_non_interrupting_deployment() -> None:
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_run_mcp"),
|
||||
store=FileStore(tmp_path / "surface_run_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "surface_run_mcp"),
|
||||
run_store=FileRunStore(tmp_path / "surface_run_mcp"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -97,10 +98,10 @@ def test_workflow_surface_runs_non_interrupting_deployment() -> None:
|
||||
assert traced["trace_truncated"] is False
|
||||
|
||||
|
||||
def test_workflow_surface_failed_deployment_exposes_error_on_run_and_inspect() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_failed_run_error"
|
||||
)
|
||||
def test_workflow_surface_failed_deployment_exposes_error_on_run_and_inspect(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_failed_run_error")
|
||||
artifact_store.save_artifact(failing_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
@@ -111,9 +112,9 @@ def test_workflow_surface_failed_deployment_exposes_error_on_run_and_inspect() -
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_failed_run_error_mcp"),
|
||||
store=FileStore(tmp_path / "surface_failed_run_error_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "surface_failed_run_error_mcp"),
|
||||
run_store=FileRunStore(tmp_path / "surface_failed_run_error_mcp"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -139,10 +140,10 @@ def test_workflow_surface_failed_deployment_exposes_error_on_run_and_inspect() -
|
||||
assert inspected["next_actions"]["recommended_next_tool"] is None
|
||||
|
||||
|
||||
def test_workflow_surface_run_deployment_can_include_trace_detail() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_run_trace_detail"
|
||||
)
|
||||
def test_workflow_surface_run_deployment_can_include_trace_detail(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_run_trace_detail")
|
||||
artifact_store.save_artifact(echo_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
@@ -153,9 +154,9 @@ def test_workflow_surface_run_deployment_can_include_trace_detail() -> None:
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_run_trace_detail_mcp"),
|
||||
store=FileStore(tmp_path / "surface_run_trace_detail_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "surface_run_trace_detail_mcp"),
|
||||
run_store=FileRunStore(tmp_path / "surface_run_trace_detail_mcp"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -188,9 +189,11 @@ def test_workflow_surface_run_deployment_can_include_trace_detail() -> None:
|
||||
assert validated["trace_truncated"] is False
|
||||
|
||||
|
||||
def test_workflow_surface_run_deployment_can_read_empty_trace_range() -> None:
|
||||
def test_workflow_surface_run_deployment_can_read_empty_trace_range(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_run_trace_empty_range"
|
||||
tmp_path / "surface_run_trace_empty_range"
|
||||
)
|
||||
artifact_store.save_artifact(echo_artifact())
|
||||
artifact_store.save_deployment(
|
||||
@@ -202,9 +205,9 @@ def test_workflow_surface_run_deployment_can_read_empty_trace_range() -> None:
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
|
||||
store=FileStore(tmp_path / "surface_run_trace_empty_range_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "surface_run_trace_empty_range_mcp"),
|
||||
run_store=FileRunStore(tmp_path / "surface_run_trace_empty_range_mcp"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -227,8 +230,10 @@ def test_workflow_surface_run_deployment_can_read_empty_trace_range() -> None:
|
||||
assert payload["trace_truncated"] is False
|
||||
|
||||
|
||||
def test_workflow_surface_runs_deployment_with_bound_node_spec_dependency() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "surface_bound_node")
|
||||
def test_workflow_surface_runs_deployment_with_bound_node_spec_dependency(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_bound_node")
|
||||
artifact_store.save_artifact(logical_echo_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
@@ -239,9 +244,9 @@ def test_workflow_surface_runs_deployment_with_bound_node_spec_dependency() -> N
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_bound_node_mcp"),
|
||||
store=FileStore(tmp_path / "surface_bound_node_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "surface_bound_node_mcp"),
|
||||
run_store=FileRunStore(tmp_path / "surface_bound_node_mcp"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -261,14 +266,14 @@ def test_workflow_surface_runs_deployment_with_bound_node_spec_dependency() -> N
|
||||
assert payload["diagnostics"] == []
|
||||
|
||||
|
||||
def test_workflow_surface_runs_artifact_created_from_concrete_node_ref() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_created_bound_node"
|
||||
)
|
||||
def test_workflow_surface_runs_artifact_created_from_concrete_node_ref(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_created_bound_node")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_created_bound_node_mcp"),
|
||||
store=FileStore(tmp_path / "surface_created_bound_node_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "surface_created_bound_node_mcp"),
|
||||
run_store=FileRunStore(tmp_path / "surface_created_bound_node_mcp"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
@@ -313,12 +318,12 @@ def test_workflow_surface_runs_artifact_created_from_concrete_node_ref() -> None
|
||||
assert payload["diagnostics"] == []
|
||||
|
||||
|
||||
def test_workflow_surface_detects_drift_from_saved_node_spec_snapshot() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(
|
||||
local_temp_root() / "surface_created_drift"
|
||||
)
|
||||
def test_workflow_surface_detects_drift_from_saved_node_spec_snapshot(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_created_drift")
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_created_drift_mcp"),
|
||||
store=FileStore(tmp_path / "surface_created_drift_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
)
|
||||
service.register_connection(
|
||||
@@ -379,8 +384,10 @@ def test_workflow_surface_detects_drift_from_saved_node_spec_snapshot() -> None:
|
||||
assert payload["diagnostics"][0]["code"] == "schema_changed"
|
||||
|
||||
|
||||
def test_workflow_surface_runs_deployment_with_bound_reducer_dependency() -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(local_temp_root() / "surface_reducer")
|
||||
def test_workflow_surface_runs_deployment_with_bound_reducer_dependency(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "surface_reducer")
|
||||
artifact_store.save_artifact(custom_reducer_artifact())
|
||||
artifact_store.save_deployment(
|
||||
WorkflowDeployment(
|
||||
@@ -394,9 +401,9 @@ def test_workflow_surface_runs_deployment_with_bound_reducer_dependency() -> Non
|
||||
)
|
||||
)
|
||||
service = WfMcpService(
|
||||
store=FileStore(local_temp_root() / "surface_reducer_mcp"),
|
||||
store=FileStore(tmp_path / "surface_reducer_mcp"),
|
||||
artifact_store=artifact_store,
|
||||
run_store=FileRunStore(local_temp_root() / "surface_reducer_mcp"),
|
||||
run_store=FileRunStore(tmp_path / "surface_reducer_mcp"),
|
||||
)
|
||||
service.register_connection(
|
||||
ConnectionConfig(id="demo.personal", server="demo", account="personal")
|
||||
|
||||
Reference in New Issue
Block a user