fix: harden Python workflow client boundary

This commit is contained in:
lda
2026-08-31 19:51:43 +07:00 Verified
parent b377311a6e
commit 4f946a35ef
35 changed files with 1257 additions and 302 deletions
+12 -32
View File
@@ -1,6 +1,5 @@
from __future__ import annotations
import sys
from typing import Any
import httpx
@@ -32,25 +31,6 @@ from wf_transport_rpc_http.client.drafts import RpcDraftClientMixin
from wf_transport_rpc_http.client.sources import RpcSourceAdminClientMixin
@pytest.fixture(autouse=True)
def _draft_enabled_composition(monkeypatch: pytest.MonkeyPatch) -> None:
"""Opt draft-focused RPC client tests into the explicit draft surface."""
build_local = build_local_static_workflow_server
create_app = create_rpc_app
def draft_local(root, *args, **kwargs):
kwargs.setdefault("drafts", True)
return build_local(root, *args, **kwargs)
def draft_app(server, *args, **kwargs):
kwargs.setdefault("drafts", True)
return create_app(server, *args, **kwargs)
module = sys.modules[__name__]
monkeypatch.setattr(module, "build_local_static_workflow_server", draft_local)
monkeypatch.setattr(module, "create_rpc_app", draft_app)
async def test_rpc_client_preserves_structured_jsonrpc_error() -> None:
def handler(request: httpx.Request) -> httpx.Response:
return httpx.Response(
@@ -354,8 +334,8 @@ async def test_rpc_workflow_client_lists_inspects_validates_and_deletes_deployme
async def test_rpc_workflow_client_draft_workspace_lifecycle(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
server = build_local_static_workflow_server(tmp_path / "store", drafts=True)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
transport=transport, base_url="http://test"
@@ -550,8 +530,8 @@ async def test_rpc_client_sends_exact_replace_document_payload() -> None:
async def test_rpc_client_builds_capability_free_draft_lifecycle(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
server = build_local_static_workflow_server(tmp_path / "store", drafts=True)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
transport=transport,
@@ -625,8 +605,8 @@ def test_rpc_client_satisfies_draft_surface_static_shape() -> None:
async def test_rpc_workflow_client_deletes_draft_workspace(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
server = build_local_static_workflow_server(tmp_path / "store", drafts=True)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
transport=transport, base_url="http://test"
@@ -772,8 +752,8 @@ async def test_rpc_client_validates_artifact_plan_without_persisting(tmp_path) -
async def test_rpc_client_set_workflow_output_map(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
server = build_local_static_workflow_server(tmp_path / "store", drafts=True)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
transport=transport, base_url="http://test"
@@ -807,8 +787,8 @@ async def test_rpc_client_set_workflow_output_map(tmp_path) -> None:
async def test_rpc_client_draft_workspace_focused_edit_methods(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
server = build_local_static_workflow_server(tmp_path / "store", drafts=True)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
transport=transport, base_url="http://test"
@@ -1069,8 +1049,8 @@ async def test_rpc_client_draft_remove_methods(tmp_path) -> None:
async def test_rpc_client_draft_workspace_add_step_from_capability(tmp_path) -> None:
server = build_local_static_workflow_server(tmp_path / "store")
app = create_rpc_app(server)
server = build_local_static_workflow_server(tmp_path / "store", drafts=True)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
transport=transport, base_url="http://test"
@@ -200,7 +200,7 @@ def _runtime_reuse_server(
],
)
store_roots = config.store_roots
workflow_stores = file_workflow_stores(store_roots.workflow_root)
workflow_stores = file_workflow_stores(store_roots.workflow_root, drafts=True)
auth_store = FileAuthStore(store_roots.auth_root)
catalog_store = FileCatalogStore(store_roots.catalog_cache_root)
factory = _RecordingSessionFactory()
@@ -232,7 +232,7 @@ async def test_mcp_backed_rpc_lists_and_mutates_source_registry(tmp_path) -> Non
SourceRegistryFile(sources=[_registry_entry("demo.registry")])
)
server = build_workflow_server_from_config(config)
app = create_rpc_app(server)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
@@ -255,7 +255,7 @@ async def test_mcp_backed_rpc_lists_and_mutates_source_registry(tmp_path) -> Non
async def test_mcp_backed_rpc_capability_list_filters_by_source(tmp_path) -> None:
config = BrokerConfig(store_root=tmp_path / "store", connections=[])
server = build_workflow_server_from_config(config)
app = create_rpc_app(server)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
@@ -285,7 +285,7 @@ async def test_mcp_backed_rpc_reports_connections_and_events(tmp_path) -> None:
],
)
server = build_workflow_server_from_config(config)
app = create_rpc_app(server)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
@@ -299,7 +299,7 @@ async def test_mcp_backed_rpc_reports_connections_and_events(tmp_path) -> None:
async def test_mcp_backed_rpc_applies_source_registry_changes(tmp_path) -> None:
config = BrokerConfig(store_root=tmp_path / "store", connections=[])
server = build_workflow_server_from_config(config)
app = create_rpc_app(server)
app = create_rpc_app(server, drafts=True)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=app),
@@ -368,7 +368,7 @@ async def test_mcp_backed_rpc_can_be_built_from_neutral_workflow_config(
}
)
server = build_workflow_server_from_workflow_config(workflow_config)
app = create_rpc_app(server)
app = create_rpc_app(server, drafts=True)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
@@ -408,7 +408,7 @@ async def test_mcp_backed_rpc_resumes_interrupted_run_after_server_rebuild(
}
)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=create_rpc_app(first_server)),
transport=httpx.ASGITransport(app=create_rpc_app(first_server, drafts=True)),
base_url="http://test",
) as http_client:
first_client = RpcWorkflowApiClient(
@@ -433,7 +433,7 @@ async def test_mcp_backed_rpc_resumes_interrupted_run_after_server_rebuild(
rebuilt_server = build_workflow_server_from_workflow_config(workflow_config)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=create_rpc_app(rebuilt_server)),
transport=httpx.ASGITransport(app=create_rpc_app(rebuilt_server, drafts=True)),
base_url="http://test",
) as http_client:
rebuilt_client = RpcWorkflowApiClient(
@@ -463,7 +463,7 @@ async def test_mcp_backed_rpc_workflow_reuses_runtime_session_across_runs(
assert factory.created_connections[0].id == "fixture.default"
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=create_rpc_app(server)),
transport=httpx.ASGITransport(app=create_rpc_app(server, drafts=True)),
base_url="http://test",
) as http_client:
client = RpcWorkflowApiClient(url="http://test/rpc", http_client=http_client)
@@ -597,7 +597,7 @@ async def test_mcp_backed_rpc_workflow_reuses_runtime_session_direct_setup(
)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=create_rpc_app(server)),
transport=httpx.ASGITransport(app=create_rpc_app(server, drafts=True)),
base_url="http://test",
) as http_client:
client = RpcWorkflowApiClient(url="http://test/rpc", http_client=http_client)
@@ -696,7 +696,7 @@ async def test_mcp_backed_rpc_deployment_becomes_unrunnable_after_source_removed
)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=create_rpc_app(server)),
transport=httpx.ASGITransport(app=create_rpc_app(server, drafts=True)),
base_url="http://test",
) as http_client:
client = RpcWorkflowApiClient(url="http://test/rpc", http_client=http_client)
@@ -748,7 +748,7 @@ async def test_mcp_backed_rpc_workflow_reuses_real_stdio_fixture_session(
)
async with httpx.AsyncClient(
transport=httpx.ASGITransport(app=create_rpc_app(server)),
transport=httpx.ASGITransport(app=create_rpc_app(server, drafts=True)),
base_url="http://test",
) as http_client:
client = RpcWorkflowApiClient(url="http://test/rpc", http_client=http_client)