half an attempt to asyncify tests + deprecate old temp path creation

This commit is contained in:
lda
2026-06-11 05:01:33 +07:00 Verified
parent d695fd7533
commit 9dae4a8445
32 changed files with 782 additions and 753 deletions
+3 -2
View File
@@ -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",
+10 -9
View File
@@ -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
View File
@@ -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
+62 -47
View File
@@ -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",
+9 -10
View File
@@ -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",
+57 -56
View File
@@ -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
View File
@@ -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(
+8 -7
View File
@@ -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")
)
+6 -3
View File
@@ -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",
+12 -7
View File
@@ -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
+47 -36
View File
@@ -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,
+28 -34
View File
@@ -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"] == (
+2 -4
View File
@@ -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",
+13 -3
View File
@@ -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
+66 -63
View File
@@ -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(
+47 -40
View File
@@ -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")