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
View File
@@ -213,3 +213,6 @@ wf*.config.json
# Generated MCPB packages
*.mcpb
# pytest files
.pytest-tmp/
+2 -1
View File
@@ -29,11 +29,12 @@ dev = [
"pytest>=8",
# "pytest-sugar>=1",
"pytest-asyncio>=1.4.0",
"pytest-xdist>=3.8.0",
"ruff>=0.15.15",
]
[tool.pytest.ini_options]
addopts = "-p no:cacheprovider"
addopts = "-p no:cacheprovider -n auto --basetemp .pytest-tmp"
pythonpath = ["."]
asyncio_mode = "auto"
+8 -12
View File
@@ -1,7 +1,5 @@
from __future__ import annotations
import asyncio
from pydantic import BaseModel
from wf_authoring import build_async_registry, node
@@ -33,7 +31,7 @@ class InferredAsyncOutput(BaseModel):
echoed: str
def test_async_registry_accepts_sync_and_async_specs() -> None:
async def test_async_registry_accepts_sync_and_async_specs() -> None:
@node()
def sync_echo(
payload: InferredEchoInput,
@@ -54,14 +52,14 @@ def test_async_registry_accepts_sync_and_async_specs() -> None:
async def run_handler(name: str, value: str) -> dict[str, object]:
return await registry[name]({"value": value}, ctx)
sync_result = asyncio.run(run_handler("sync_echo", "hello"))
async_result = asyncio.run(run_handler("async_echo", "world"))
sync_result = await run_handler("sync_echo", "hello")
async_result = await run_handler("async_echo", "world")
assert sync_result == {"outcome": "ok", "output": {"echoed": "hello"}}
assert async_result == {"outcome": "ok", "output": {"echoed": "async:world"}}
def test_execute_workflow_async_runs_with_async_registry() -> None:
async def test_execute_workflow_async_runs_with_async_registry() -> None:
workflow, _ = build_authoring_demo_workflow()
registry = build_async_registry(
drive_list_files_spec,
@@ -71,12 +69,10 @@ def test_execute_workflow_async_runs_with_async_registry() -> None:
mark_email_skipped_spec,
)
run = asyncio.run(
execute_workflow_async(
workflow,
{"folder_id": "demo-folder", "should_email": False},
registry,
)
run = await execute_workflow_async(
workflow,
{"folder_id": "demo-folder", "should_email": False},
registry,
)
assert run.status == RunStatus.COMPLETED
@@ -1,6 +1,5 @@
from __future__ import annotations
import asyncio
from typing import Any, cast
import pytest
@@ -33,8 +32,8 @@ def test_authoring_concurrent_foreach_collects_item_errors() -> None:
assert error["item"] == "bad"
def test_authoring_async_concurrent_foreach_commits_in_item_order() -> None:
run = asyncio.run(run_async_ordered_example())
async def test_authoring_async_concurrent_foreach_commits_in_item_order() -> None:
run = await run_async_ordered_example()
assert run.status == RunStatus.COMPLETED
assert run.output["seen"] == ["a", "b", "c"]
+5 -9
View File
@@ -1,7 +1,5 @@
from __future__ import annotations
import asyncio
from pydantic import BaseModel
from examples.authoring_workflow_as_node import (
@@ -63,7 +61,7 @@ def test_subgraph_node_wraps_compiled_workflow() -> None:
assert "summary" in result["output"]
def test_async_subgraph_node_wraps_async_compiled_workflow() -> None:
async def test_async_subgraph_node_wraps_async_compiled_workflow() -> None:
class ChildInput(BaseModel):
text: str
@@ -114,12 +112,10 @@ def test_async_subgraph_node_wraps_async_compiled_workflow() -> None:
parent.set_entry_point(step)
parent.connect(step, "ok", END)
run = asyncio.run(
execute_workflow_async(
parent.compile(),
{"text": "hello"},
build_async_registry(wrapped),
)
run = await execute_workflow_async(
parent.compile(),
{"text": "hello"},
build_async_registry(wrapped),
)
assert run.status == RunStatus.COMPLETED
+2 -10
View File
@@ -18,11 +18,7 @@ from wf_core import (
)
def test_async_concurrent_foreach_respects_max_active() -> None:
asyncio.run(_assert_async_concurrent_foreach_respects_max_active())
async def _assert_async_concurrent_foreach_respects_max_active() -> None:
async def test_async_concurrent_foreach_respects_max_active() -> None:
workflow = _workflow(max_active=2)
active = 0
max_seen = 0
@@ -45,11 +41,7 @@ async def _assert_async_concurrent_foreach_respects_max_active() -> None:
assert run.state["seen"] == ["a", "b", "c", "d"]
def test_async_concurrent_foreach_commits_in_item_index_order() -> None:
asyncio.run(_assert_async_concurrent_foreach_commits_in_item_index_order())
async def _assert_async_concurrent_foreach_commits_in_item_index_order() -> None:
async def test_async_concurrent_foreach_commits_in_item_index_order() -> None:
workflow = _workflow(max_active=3)
async def record(payload: dict[str, Any], _ctx: object) -> dict[str, Any]:
@@ -21,11 +21,7 @@ from wf_core import (
)
def test_concurrent_foreach_interrupt_returns_before_refill() -> None:
asyncio.run(_assert_concurrent_foreach_interrupt_returns_before_refill())
async def _assert_concurrent_foreach_interrupt_returns_before_refill() -> None:
async def test_concurrent_foreach_interrupt_returns_before_refill() -> None:
run = await execute_workflow_async(
_workflow(),
{"items": ["a", "b", "c"]},
@@ -40,11 +36,7 @@ async def _assert_concurrent_foreach_interrupt_returns_before_refill() -> None:
assert "seen" not in run.state
def test_resume_prioritizes_interrupted_item_before_siblings() -> None:
asyncio.run(_assert_resume_prioritizes_interrupted_item_before_siblings())
async def _assert_resume_prioritizes_interrupted_item_before_siblings() -> None:
async def test_resume_prioritizes_interrupted_item_before_siblings() -> None:
workflow = _workflow()
run = await execute_workflow_async(
workflow,
+19 -28
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import asyncio
from typing import Any
import pytest
@@ -27,54 +26,46 @@ async def explode(_payload: dict[str, Any], _context: RuntimeContext) -> dict[st
raise ValueError("boom")
def test_execute_result_api_returns_failed_state_without_changing_strict_execute() -> (
@pytest.mark.asyncio
async def test_execute_result_api_returns_failed_state_without_changing_strict_execute() -> (
None
):
workflow = _failing_workflow()
failed = asyncio.run(
execute_workflow_result_async(workflow, {}, {"explode": explode})
)
failed = await execute_workflow_result_async(workflow, {}, {"explode": explode})
assert failed.status is RunStatus.FAILED
assert failed.error == "boom"
with pytest.raises(ValueError, match="boom"):
asyncio.run(execute_workflow_async(workflow, {}, {"explode": explode}))
await execute_workflow_async(workflow, {}, {"explode": explode})
def test_resume_result_api_returns_failed_state_without_changing_strict_resume() -> (
@pytest.mark.asyncio
async def test_resume_result_api_returns_failed_state_without_changing_strict_resume() -> (
None
):
workflow = _interrupt_then_fail_workflow()
interrupted = asyncio.run(
execute_workflow_async(workflow, {}, {"explode": explode})
interrupted = await execute_workflow_async(workflow, {}, {"explode": explode})
failed = await resume_workflow_result_async(
workflow,
interrupted,
{"explode": explode},
resume_payload={},
)
failed = asyncio.run(
resume_workflow_result_async(
assert failed.status is RunStatus.FAILED
assert failed.error == "boom"
interrupted = await execute_workflow_async(workflow, {}, {"explode": explode})
with pytest.raises(ValueError, match="boom"):
await resume_workflow_async(
workflow,
interrupted,
{"explode": explode},
resume_payload={},
)
)
assert failed.status is RunStatus.FAILED
assert failed.error == "boom"
interrupted = asyncio.run(
execute_workflow_async(workflow, {}, {"explode": explode})
)
with pytest.raises(ValueError, match="boom"):
asyncio.run(
resume_workflow_async(
workflow,
interrupted,
{"explode": explode},
resume_payload={},
)
)
def _failing_workflow() -> Workflow:
+6 -6
View File
@@ -1,7 +1,5 @@
from __future__ import annotations
import asyncio
import pytest
from wf_core import (
@@ -187,7 +185,8 @@ def test_subgraph_step_projects_child_context_output() -> None:
assert run.output["answer"] == "root:subgraph:child"
def test_subgraph_step_executes_prepared_async_child() -> None:
@pytest.mark.asyncio
async def test_subgraph_step_executes_prepared_async_child() -> None:
async def answer(payload: dict[str, object], _ctx: object) -> dict[str, object]:
return {"answer": f"async:{payload['text']}"}
@@ -204,7 +203,7 @@ def test_subgraph_step_executes_prepared_async_child() -> None:
},
)
run = asyncio.run(execute())
run = await execute()
assert run.output["answer"] == "async:hello"
assert run.trace[-1].step_type == "subgraph"
@@ -295,7 +294,8 @@ def test_subgraph_step_interrupts_and_resumes_inside_prepared_child() -> None:
assert resumed.state["answer"] == "yes"
def test_subgraph_step_resumes_interrupted_async_prepared_child() -> None:
@pytest.mark.asyncio
async def test_subgraph_step_resumes_interrupted_async_prepared_child() -> None:
async def run_child() -> RunState:
child = _interrupting_child_workflow()
parent = _workflow(output_schema=_schema({"answer": {"type": "string"}}))
@@ -314,7 +314,7 @@ def test_subgraph_step_resumes_interrupted_async_prepared_child() -> None:
subgraphs={"child.workflow": prepared},
)
resumed = asyncio.run(run_child())
resumed = await run_child()
assert resumed.status == "completed"
assert resumed.output["answer"] == "async yes"
+26 -15
View File
@@ -1,9 +1,9 @@
from __future__ import annotations
import asyncio
from pathlib import Path
import httpx
import pytest
from wf_authoring import NodeReturn
from wf_openapi.executor import (
@@ -17,7 +17,8 @@ from wf_openapi.validation import load_openapi_app
FIXTURE = Path(__file__).parent / "fixtures" / "petstore_minimal.openapi.json"
def test_call_openapi_operation_maps_success() -> None:
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_success() -> None:
app = load_openapi_app(FIXTURE)
operation = next(
op for op in load_openapi_operations(FIXTURE) if op.name == "get_pet"
@@ -38,14 +39,15 @@ def test_call_openapi_operation_maps_success() -> None:
client=client,
)
result = asyncio.run(run())
result = await run()
assert result.outcome == "ok"
assert result.output.status_code == 200
assert result.output.body["id"] == "pet-1"
def test_call_openapi_operation_maps_declared_http_error() -> None:
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_declared_http_error() -> None:
app = load_openapi_app(FIXTURE)
operation = next(
op for op in load_openapi_operations(FIXTURE) if op.name == "get_pet"
@@ -65,14 +67,15 @@ def test_call_openapi_operation_maps_declared_http_error() -> None:
client=client,
)
result = asyncio.run(run())
result = await run()
assert result.outcome == "http_error"
assert result.output.status_code == 404
assert result.output.body["message"] == "missing"
def test_call_openapi_operation_maps_unexpected_status() -> None:
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_unexpected_status() -> None:
app = load_openapi_app(FIXTURE)
operation = next(
op for op in load_openapi_operations(FIXTURE) if op.name == "get_pet"
@@ -92,14 +95,17 @@ def test_call_openapi_operation_maps_unexpected_status() -> None:
client=client,
)
result = asyncio.run(run())
result = await run()
assert result.outcome == "unexpected_status"
assert result.output.status_code == 418
assert result.output.validation_errors
def test_call_openapi_operation_maps_invalid_request_to_validation_error() -> None:
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_invalid_request_to_validation_error() -> (
None
):
app = load_openapi_app(FIXTURE)
operation = next(
op for op in load_openapi_operations(FIXTURE) if op.name == "create_pet"
@@ -118,14 +124,17 @@ def test_call_openapi_operation_maps_invalid_request_to_validation_error() -> No
client=client,
)
result = asyncio.run(run())
result = await run()
assert result.outcome == "validation_error"
assert result.output.status_code == 0
assert result.output.validation_errors
def test_call_openapi_operation_maps_invalid_response_to_validation_error() -> None:
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_invalid_response_to_validation_error() -> (
None
):
app = load_openapi_app(FIXTURE)
operation = next(
op for op in load_openapi_operations(FIXTURE) if op.name == "get_pet"
@@ -145,14 +154,15 @@ def test_call_openapi_operation_maps_invalid_response_to_validation_error() -> N
client=client,
)
result = asyncio.run(run())
result = await run()
assert result.outcome == "validation_error"
assert result.output.status_code == 200
assert result.output.validation_errors
def test_call_openapi_operation_maps_malformed_json_response_to_validation_error() -> (
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_malformed_json_response_to_validation_error() -> (
None
):
app = load_openapi_app(FIXTURE)
@@ -178,14 +188,15 @@ def test_call_openapi_operation_maps_malformed_json_response_to_validation_error
client=client,
)
result = asyncio.run(run())
result = await run()
assert result.outcome == "validation_error"
assert result.output.status_code == 200
assert result.output.validation_errors
def test_call_openapi_operation_maps_transport_error() -> None:
@pytest.mark.asyncio
async def test_call_openapi_operation_maps_transport_error() -> None:
app = load_openapi_app(FIXTURE)
operation = next(
op for op in load_openapi_operations(FIXTURE) if op.name == "get_pet"
@@ -205,7 +216,7 @@ def test_call_openapi_operation_maps_transport_error() -> None:
client=client,
)
result = asyncio.run(run())
result = await run()
assert result.outcome == "transport_error"
assert result.output.status_code == 0
+9 -7
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import asyncio
from dataclasses import dataclass, field
from typing import Any
@@ -55,11 +54,12 @@ class FakeAdminProvider:
return self.events
def test_admin_api_lists_connections_in_id_order() -> None:
@pytest.mark.asyncio
async def test_admin_api_lists_connections_in_id_order() -> None:
provider = FakeAdminProvider()
api = WorkflowAdminApi(connections=provider, events=provider)
payload = asyncio.run(api.list_connections())
payload = await api.list_connections()
assert payload["total"] == 2
assert [connection["id"] for connection in payload["connections"]] == [
@@ -68,11 +68,12 @@ def test_admin_api_lists_connections_in_id_order() -> None:
]
def test_admin_api_lists_connection_statuses_in_id_order() -> None:
@pytest.mark.asyncio
async def test_admin_api_lists_connection_statuses_in_id_order() -> None:
provider = FakeAdminProvider()
api = WorkflowAdminApi(connections=provider, events=provider)
payload = asyncio.run(api.get_connection_statuses())
payload = await api.get_connection_statuses()
assert payload["total"] == 2
assert [status["connection_id"] for status in payload["statuses"]] == [
@@ -81,11 +82,12 @@ def test_admin_api_lists_connection_statuses_in_id_order() -> None:
]
def test_admin_api_lists_events() -> None:
@pytest.mark.asyncio
async def test_admin_api_lists_events() -> None:
provider = FakeAdminProvider()
api = WorkflowAdminApi(connections=provider, events=provider)
payload = asyncio.run(api.list_events())
payload = await api.list_events()
assert payload["total"] == 1
assert payload["events"][0]["kind"] == "connection_registered"
+42 -47
View File
@@ -2,7 +2,6 @@
from __future__ import annotations
import asyncio
from dataclasses import replace
from pathlib import Path
from typing import Any
@@ -134,11 +133,12 @@ def _artifact_api(
return WorkflowArtifactApi(context), service
def test_save_artifact_stores_and_returns_saved(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_save_artifact_stores_and_returns_saved(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_save")
api, _service = _artifact_api(artifact_store)
result = asyncio.run(api.save_artifact(_echo_artifact().model_dump(mode="json")))
result = await api.save_artifact(_echo_artifact().model_dump(mode="json"))
assert result["saved"] is True
assert result["artifact_id"] == "echo"
@@ -147,7 +147,8 @@ def test_save_artifact_stores_and_returns_saved(tmp_path: Path) -> None:
assert saved.id == "echo"
def test_list_artifacts_returns_empty_page_without_artifact_store(
@pytest.mark.asyncio
async def test_list_artifacts_returns_empty_page_without_artifact_store(
tmp_path: Path,
) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_no_store")
@@ -155,27 +156,26 @@ def test_list_artifacts_returns_empty_page_without_artifact_store(
context = replace(context_from_service(service), artifact_store=None)
api = WorkflowArtifactApi(context)
result = asyncio.run(api.list_artifacts())
result = await api.list_artifacts()
assert result["nodes"] == []
assert result["next_cursor"] is None
assert result["total"] == 0
def test_create_artifact_from_plan_saves_with_observed_node_specs(
@pytest.mark.asyncio
async def test_create_artifact_from_plan_saves_with_observed_node_specs(
tmp_path: Path,
) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_from_plan")
api, _service = _artifact_api(artifact_store, register_echo=True)
result = asyncio.run(
api.create_artifact_from_plan(
artifact_id="echo",
version=1,
title="Echo",
plan=_echo_artifact().plan,
outcomes=("completed",),
)
result = await api.create_artifact_from_plan(
artifact_id="echo",
version=1,
title="Echo",
plan=_echo_artifact().plan,
outcomes=("completed",),
)
assert result["saved"] is True
@@ -184,7 +184,7 @@ def test_create_artifact_from_plan_saves_with_observed_node_specs(
assert saved.id == "echo"
def test_create_artifact_from_workspace_returns_saved_false_when_invalid(
async def test_create_artifact_from_workspace_returns_saved_false_when_invalid(
tmp_path: Path,
) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_workspace_invalid")
@@ -195,49 +195,42 @@ def test_create_artifact_from_workspace_returns_saved_false_when_invalid(
context = context_from_service(service)
drafts_api = WorkflowDraftApi(context)
asyncio.run(
drafts_api.create_draft_workspace(
workspace_id="echo_ws",
draft=draft,
)
await drafts_api.create_draft_workspace(
workspace_id="echo_ws",
draft=draft,
)
result = asyncio.run(
api.create_artifact_from_workspace(
workspace_id="echo_ws",
artifact_id="echo",
version=1,
title="Echo",
outcomes=("completed",),
)
result = await api.create_artifact_from_workspace(
workspace_id="echo_ws",
artifact_id="echo",
version=1,
title="Echo",
outcomes=("completed",),
)
assert result["saved"] is False
assert result["status"] == "invalid"
def test_create_wrapper_from_workspace_saves_kind_wrapper(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_create_wrapper_from_workspace_saves_kind_wrapper(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_wrapper_workspace")
api, service = _artifact_api(artifact_store, register_echo=True)
from wf_api.drafts import WorkflowDraftApi
context = context_from_service(service)
drafts_api = WorkflowDraftApi(context)
asyncio.run(
drafts_api.create_draft_workspace(
workspace_id="echo_ws",
draft=_echo_draft(),
)
await drafts_api.create_draft_workspace(
workspace_id="echo_ws",
draft=_echo_draft(),
)
result = asyncio.run(
api.create_wrapper_from_workspace(
workspace_id="echo_ws",
artifact_id="echo_wrapper",
version=1,
title="Echo Wrapper",
outcomes=("completed",),
)
result = await api.create_wrapper_from_workspace(
workspace_id="echo_ws",
artifact_id="echo_wrapper",
version=1,
title="Echo Wrapper",
outcomes=("completed",),
)
assert result["saved"] is True
@@ -245,12 +238,13 @@ def test_create_wrapper_from_workspace_saves_kind_wrapper(tmp_path: Path) -> Non
assert saved.kind == "wrapper"
def test_inspect_artifact_returns_stable_fields(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_inspect_artifact_returns_stable_fields(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_inspect")
api, _service = _artifact_api(artifact_store)
artifact_store.save_artifact(_echo_artifact())
result = asyncio.run(api.inspect_artifact(artifact_id="echo", version=1))
result = await api.inspect_artifact(artifact_id="echo", version=1)
assert result["id"] == "echo"
assert result["version"] == 1
@@ -258,7 +252,8 @@ def test_inspect_artifact_returns_stable_fields(tmp_path: Path) -> None:
assert "plan" in result
def test_handler_delegation_for_inspect_artifact(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_handler_delegation_for_inspect_artifact(tmp_path: Path) -> None:
"""WorkflowSurfaceHandlers.inspect_artifact delegates to WorkflowArtifactApi."""
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_delegation")
mcp_root = artifact_store.root / "delegation_mcp"
@@ -273,8 +268,8 @@ def test_handler_delegation_for_inspect_artifact(tmp_path: Path) -> None:
context = context_from_service(service)
api = WorkflowArtifactApi(context)
handler_result = asyncio.run(h.inspect_artifact(artifact_id="echo", version=1))
api_result = asyncio.run(api.inspect_artifact(artifact_id="echo", version=1))
handler_result = await h.inspect_artifact(artifact_id="echo", version=1)
api_result = await api.inspect_artifact(artifact_id="echo", version=1)
assert handler_result["id"] == api_result["id"]
assert handler_result["version"] == api_result["version"]
+50 -49
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import asyncio
from pathlib import Path
import pytest
@@ -42,11 +41,14 @@ def _capability_api(
return WorkflowCapabilityApi(context), service
def test_list_capabilities_returns_planner_visible_sources(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_list_capabilities_returns_planner_visible_sources(
tmp_path: Path,
) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_list")
api, _service = _capability_api(artifact_store, register_echo=True)
result = asyncio.run(api.list_capabilities())
result = await api.list_capabilities()
assert result["total"] >= 1
assert any(
@@ -62,22 +64,24 @@ def test_list_capabilities_returns_planner_visible_sources(tmp_path: Path) -> No
assert "output_fields" in first
def test_list_capabilities_filters_by_source(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_list_capabilities_filters_by_source(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_list_filter")
api, _service = _capability_api(artifact_store, register_echo=True)
result = asyncio.run(api.list_capabilities(source_id="wf.std", query="truthy"))
result = await api.list_capabilities(source_id="wf.std", query="truthy")
assert [item["name"] for item in result["capabilities"]] == ["wf.std.truthy"]
def test_inspect_capability_returns_detail_with_wrapper_hints(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_inspect_capability_returns_detail_with_wrapper_hints(
tmp_path: Path,
) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_inspect")
api, _service = _capability_api(artifact_store, register_echo=True)
detail = asyncio.run(
api.inspect_capability(qualified_name="demo.personal.echo_tool")
)
detail = await api.inspect_capability(qualified_name="demo.personal.echo_tool")
assert detail["name"] == "demo.personal.echo_tool"
assert "wrapper_hints" in detail
@@ -87,23 +91,23 @@ def test_inspect_capability_returns_detail_with_wrapper_hints(tmp_path: Path) ->
assert hints["output_map"] == {"echoed": "state.echoed"}
def test_inspect_capability_raises_on_unknown(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_inspect_capability_raises_on_unknown(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_inspect_unknown")
api, _service = _capability_api(artifact_store, register_echo=True)
with pytest.raises(KeyError, match="no.such.capability"):
asyncio.run(api.inspect_capability(qualified_name="no.such.capability"))
await api.inspect_capability(qualified_name="no.such.capability")
def test_call_capability_node_spec_success(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_call_capability_node_spec_success(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_call")
api, _service = _capability_api(artifact_store, register_echo=True)
result = asyncio.run(
api.call_capability(
qualified_name="demo.personal.echo_tool",
payload={"text": "hello"},
)
result = await api.call_capability(
qualified_name="demo.personal.echo_tool",
payload={"text": "hello"},
)
assert result["kind"] == "node_spec"
@@ -113,15 +117,14 @@ def test_call_capability_node_spec_success(tmp_path: Path) -> None:
assert result["deployment_id"] is None
def test_call_capability_node_spec_failure(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_call_capability_node_spec_failure(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_call_fail")
api, _service = _capability_api(artifact_store, register_failing=True)
result = asyncio.run(
api.call_capability(
qualified_name="demo.personal.failing_tool",
payload={"message": "boom"},
)
result = await api.call_capability(
qualified_name="demo.personal.failing_tool",
payload={"message": "boom"},
)
assert result["kind"] == "node_spec"
@@ -130,7 +133,8 @@ def test_call_capability_node_spec_failure(tmp_path: Path) -> None:
assert result["diagnostics"][0]["code"] == "capability_call_failed"
def test_list_capabilities_includes_saved_wrapper(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_list_capabilities_includes_saved_wrapper(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_wrapper_list")
artifact_store.save_artifact(
echo_artifact().model_copy(
@@ -143,7 +147,7 @@ def test_list_capabilities_includes_saved_wrapper(tmp_path: Path) -> None:
)
api, _service = _capability_api(artifact_store)
result = asyncio.run(api.list_capabilities(source_id="workflow", query="echo"))
result = await api.list_capabilities(source_id="workflow", query="echo")
names = [item["name"] for item in result["capabilities"]]
assert names == ["workflow.echo_wrapper.v1"]
@@ -159,16 +163,15 @@ def test_list_capabilities_includes_saved_wrapper(tmp_path: Path) -> None:
assert row["output_fields"] == ["echoed"]
def test_inspect_capability_saved_wrapper(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_inspect_capability_saved_wrapper(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_wrapper_inspect")
artifact_store.save_artifact(
echo_artifact().model_copy(update={"id": "echo_wrapper", "kind": "wrapper"})
)
api, _service = _capability_api(artifact_store)
detail = asyncio.run(
api.inspect_capability(qualified_name="workflow.echo_wrapper.v1")
)
detail = await api.inspect_capability(qualified_name="workflow.echo_wrapper.v1")
assert detail["name"] == "workflow.echo_wrapper.v1"
assert detail["source_id"] == "workflow"
@@ -186,18 +189,17 @@ def test_inspect_capability_saved_wrapper(tmp_path: Path) -> None:
assert hints["output_map"] == {"echoed": "state.echoed"}
def test_call_capability_saved_wrapper(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_call_capability_saved_wrapper(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_wrapper_call")
artifact_store.save_artifact(
echo_artifact().model_copy(update={"id": "echo_wrapper", "kind": "wrapper"})
)
api, service = _capability_api(artifact_store, register_echo=True)
result = asyncio.run(
api.call_capability(
qualified_name="workflow.echo_wrapper.v1",
payload={"text": "hi"},
)
result = await api.call_capability(
qualified_name="workflow.echo_wrapper.v1",
payload={"text": "hi"},
)
assert result["kind"] == "wrapper_artifact"
@@ -205,15 +207,14 @@ def test_call_capability_saved_wrapper(tmp_path: Path) -> None:
assert result["diagnostics"] == []
def test_create_draft_workspace_from_capability(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_create_draft_workspace_from_capability(tmp_path: Path) -> None:
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_draft_bootstrap")
api, _service = _capability_api(artifact_store, register_echo=True)
result = asyncio.run(
api.create_draft_workspace_from_capability(
workspace_id="echo_ws",
capability_name="demo.personal.echo_tool",
)
result = await api.create_draft_workspace_from_capability(
workspace_id="echo_ws",
capability_name="demo.personal.echo_tool",
)
assert result["workspace_id"] == "echo_ws"
@@ -222,13 +223,15 @@ def test_create_draft_workspace_from_capability(tmp_path: Path) -> None:
assert "next_actions" in result
assert result["wrapper_hints"]["capability_name"] == "demo.personal.echo_tool"
fetched = asyncio.run(
api.drafts.get_draft_workspace(workspace_id="echo_ws", include_draft=True)
fetched = await api.drafts.get_draft_workspace(
workspace_id="echo_ws", include_draft=True
)
assert fetched["draft"]["steps"]["call"]["use"] == "demo.personal.echo_tool"
def test_handler_delegates_to_capability_api(tmp_path: Path) -> None:
@pytest.mark.asyncio
async def test_handler_delegates_to_capability_api(tmp_path: Path) -> None:
"""WorkflowSurfaceHandlers methods produce the same result as direct API."""
artifact_store = FileWorkflowArtifactStore(tmp_path / "cap_api_delegation")
mcp_root = artifact_store.root / "delegation_mcp"
@@ -246,12 +249,10 @@ def test_handler_delegates_to_capability_api(tmp_path: Path) -> None:
context = context_from_service(service)
api = WorkflowCapabilityApi(context)
handler_result = asyncio.run(
h.inspect_capability(qualified_name="demo.personal.echo_tool")
)
api_result = asyncio.run(
api.inspect_capability(qualified_name="demo.personal.echo_tool")
handler_result = await h.inspect_capability(
qualified_name="demo.personal.echo_tool"
)
api_result = await api.inspect_capability(qualified_name="demo.personal.echo_tool")
assert handler_result["name"] == api_result["name"]
assert handler_result["wrapper_hints"] == api_result["wrapper_hints"]
+13 -13
View File
@@ -8,7 +8,7 @@ import pytest
from typer import Context as TyperContext
from typer.testing import CliRunner
from tests.wf_mcp.test_support import echo_tool, local_temp_root
from tests.wf_mcp.test_support import echo_tool
from tests.wf_mcp.workflow_surface.conftest import echo_artifact
from wf_artifacts import FileWorkflowArtifactStore, WorkflowDeployment
from wf_cli.app import app
@@ -144,8 +144,8 @@ def _write_cli_config(root: Path) -> Path:
return config_path
def test_wf_cap_list_outputs_json() -> None:
root = local_temp_root() / "wf_cli_cap_list"
def test_wf_cap_list_outputs_json(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_cap_list"
root.mkdir(parents=True, exist_ok=True)
config_path = _write_cli_config(root)
@@ -163,8 +163,8 @@ def test_wf_cap_list_outputs_json() -> None:
assert payload["capabilities"][0]["source_id"] == "demo.personal"
def test_wf_cap_list_ids_format() -> None:
root = local_temp_root() / "wf_cli_cap_list_ids"
def test_wf_cap_list_ids_format(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_cap_list_ids"
root.mkdir(parents=True, exist_ok=True)
config_path = _write_cli_config(root)
@@ -189,8 +189,8 @@ def test_wf_cap_list_ids_format() -> None:
assert result.output.strip() == "demo.personal.echo_tool"
def test_wf_cap_inspect_outputs_detail() -> None:
root = local_temp_root() / "wf_cli_cap_inspect"
def test_wf_cap_inspect_outputs_detail(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_cap_inspect"
root.mkdir(parents=True, exist_ok=True)
config_path = _write_cli_config(root)
@@ -229,8 +229,8 @@ def _seed_echo_deployment(root: Path) -> Path:
return config_path
def test_wf_artifact_list_and_inspect() -> None:
root = local_temp_root() / "wf_cli_artifacts"
def test_wf_artifact_list_and_inspect(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_artifacts"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_echo_artifact(root)
@@ -249,8 +249,8 @@ def test_wf_artifact_list_and_inspect() -> None:
assert payload["version"] == 1
def test_wf_deploy_list_inspect_save_delete() -> None:
root = local_temp_root() / "wf_cli_deploy_lifecycle"
def test_wf_deploy_list_inspect_save_delete(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_deploy_lifecycle"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_echo_deployment(root)
@@ -290,8 +290,8 @@ def test_wf_deploy_list_inspect_save_delete() -> None:
assert json.loads(deleted.output)["deleted"] is True
def test_wf_draft_create_patch_validate_save() -> None:
root = local_temp_root() / "wf_cli_draft_lifecycle"
def test_wf_draft_create_patch_validate_save(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_draft_lifecycle"
root.mkdir(parents=True, exist_ok=True)
config_path = _write_cli_config(root)
+13 -13
View File
@@ -8,7 +8,7 @@ from unittest.mock import patch
from typer import Context as TyperContext
from typer.testing import CliRunner
from tests.wf_mcp.test_support import echo_tool, input_binding, local_temp_root
from tests.wf_mcp.test_support import echo_tool, input_binding
from tests.wf_mcp.workflow_surface.conftest import echo_artifact
from wf_artifacts import FileWorkflowArtifactStore, WorkflowArtifact, WorkflowDeployment
from wf_cli.app import app
@@ -93,8 +93,8 @@ def _seed_interrupt_deployment(root: Path) -> Path:
return config_path
def test_wf_deploy_validate_outputs_json() -> None:
root = local_temp_root() / "wf_cli_deploy_validate"
def test_wf_deploy_validate_outputs_json(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_deploy_validate"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_echo_deployment(root)
@@ -115,8 +115,8 @@ def test_wf_deploy_validate_outputs_json() -> None:
)
def test_wf_run_start_accepts_inline_json_input() -> None:
root = local_temp_root() / "wf_cli_run_start"
def test_wf_run_start_accepts_inline_json_input(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_run_start"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_echo_deployment(root)
@@ -144,8 +144,8 @@ def test_wf_run_start_accepts_inline_json_input() -> None:
assert payload["next_actions"]["can_continue"] is False
def test_wf_run_start_accepts_input_file() -> None:
root = local_temp_root() / "wf_cli_run_start_file"
def test_wf_run_start_accepts_input_file(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_run_start_file"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_echo_deployment(root)
input_path = root / "input.json"
@@ -173,8 +173,8 @@ def test_wf_run_start_accepts_input_file() -> None:
assert payload["output"]["echoed"] == "from file"
def test_wf_run_inspect_and_trace_existing_run() -> None:
root = local_temp_root() / "wf_cli_run_inspect_trace"
def test_wf_run_inspect_and_trace_existing_run(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_run_inspect_trace"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_echo_deployment(root)
@@ -389,8 +389,8 @@ def test_wf_run_watch_times_out_for_unstopped_run() -> None:
assert "did not stop before timeout" in result.output
def test_wf_run_resume_interrupted_run() -> None:
root = local_temp_root() / "wf_cli_run_resume"
def test_wf_run_resume_interrupted_run(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_run_resume"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_interrupt_deployment(root)
@@ -432,8 +432,8 @@ def test_wf_run_resume_interrupted_run() -> None:
assert payload["resume_readiness"] == "not_applicable"
def test_wf_run_start_reports_bad_json() -> None:
root = local_temp_root() / "wf_cli_run_bad_json"
def test_wf_run_start_reports_bad_json(tmp_path: Path) -> None:
root = tmp_path / "wf_cli_run_bad_json"
root.mkdir(parents=True, exist_ok=True)
config_path = _seed_echo_deployment(root)
+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")
Generated
+24
View File
@@ -298,6 +298,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" },
]
[[package]]
name = "execnet"
version = "2.1.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" },
]
[[package]]
name = "fastapi"
version = "0.136.3"
@@ -661,6 +670,7 @@ dev = [
{ name = "basedpyright" },
{ name = "pytest" },
{ name = "pytest-asyncio" },
{ name = "pytest-xdist" },
{ name = "ruff" },
]
@@ -683,6 +693,7 @@ dev = [
{ name = "basedpyright", specifier = ">=1.39.6" },
{ name = "pytest", specifier = ">=8" },
{ name = "pytest-asyncio", specifier = ">=1.4.0" },
{ name = "pytest-xdist", specifier = ">=3.8.0" },
{ name = "ruff", specifier = ">=0.15.15" },
]
@@ -1079,6 +1090,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/03/e2/08a497ef684b88559c9cc5f4ad53a37e7b99e727094a86d6ea32536d5d3c/pytest_asyncio-1.4.0-py3-none-any.whl", hash = "sha256:933ca923a23075a87fb7070c0ec272a6848489824d887c85c812670932835aa1", size = 16930, upload-time = "2026-05-26T09:56:02.576Z" },
]
[[package]]
name = "pytest-xdist"
version = "3.8.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "execnet" },
{ name = "pytest" },
]
sdist = { url = "https://files.pythonhosted.org/packages/78/b4/439b179d1ff526791eb921115fca8e44e596a13efeda518b9d845a619450/pytest_xdist-3.8.0.tar.gz", hash = "sha256:7e578125ec9bc6050861aa93f2d59f1d8d085595d6551c2c90b6f4fad8d3a9f1", size = 88069, upload-time = "2025-07-01T13:30:59.346Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/31/d4e37e9e550c2b92a9cbc2e4d0b7420a27224968580b5a447f420847c975/pytest_xdist-3.8.0-py3-none-any.whl", hash = "sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88", size = 46396, upload-time = "2025-07-01T13:30:56.632Z" },
]
[[package]]
name = "python-dotenv"
version = "1.2.2"