fix: close Python workflow client review
This commit is contained in:
@@ -236,6 +236,29 @@ async def test_validate_artifact_plan_projects_invalid_plan_diagnostic(
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_artifact_plan_roots_capability_diagnostic_at_request_field(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_requirement")
|
||||
api, _service = _artifact_api(artifact_store)
|
||||
|
||||
result = await api.validate_artifact_plan(
|
||||
plan=_echo_artifact().plan,
|
||||
outcomes=("completed",),
|
||||
required_capabilities={
|
||||
"broken": {
|
||||
"ref": {"source": "demo", "capability_key": "echo"},
|
||||
"kind": "not-a-capability-kind",
|
||||
}
|
||||
},
|
||||
source_bindings={},
|
||||
)
|
||||
|
||||
assert result["status"] == "invalid"
|
||||
assert result["diagnostics"][0]["path"] == "required_capabilities.broken.kind"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_artifact_plan_propagates_unexpected_value_error(
|
||||
tmp_path: Path,
|
||||
@@ -290,6 +313,41 @@ async def test_validate_artifact_plan_derives_saved_workflow_dependencies(
|
||||
assert result["workflow_dependencies"] == {"child_workflow": 7}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_validate_artifact_plan_rejects_conflicting_child_version_pins(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "artifacts_pin_conflict")
|
||||
api, _service = _artifact_api(artifact_store)
|
||||
plan = _echo_artifact().plan
|
||||
plan["start"] = "child_v1"
|
||||
plan["nodes"] = [
|
||||
{
|
||||
"id": node_id,
|
||||
"type": "subgraph",
|
||||
"workflow": {"artifact_id": "child_workflow", "version": version},
|
||||
"input_schema": {"type": "object"},
|
||||
"output_schema": {"type": "object"},
|
||||
"outcomes": ["completed"],
|
||||
}
|
||||
for node_id, version in (("child_v1", 1), ("child_v2", 2))
|
||||
]
|
||||
plan["edges"] = [
|
||||
{"from": "child_v1", "outcome": "completed", "to": "child_v2"},
|
||||
{"from": "child_v2", "outcome": "completed", "to": "__end__"},
|
||||
]
|
||||
|
||||
result = await api.validate_artifact_plan(
|
||||
plan=plan,
|
||||
outcomes=("completed",),
|
||||
source_bindings={},
|
||||
)
|
||||
|
||||
assert result["status"] == "invalid"
|
||||
assert result["diagnostics"][0]["path"] == "plan"
|
||||
assert "conflicting versions 1 and 2" in result["diagnostics"][0]["message"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_artifact_from_workspace_suggests_exact_available_source_binding(
|
||||
tmp_path: Path,
|
||||
|
||||
@@ -150,19 +150,25 @@ async def test_http_app_translates_http_and_json_failures(
|
||||
async def test_http_app_translates_known_workflow_protocol_error(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
async def error_post(*args: object, **kwargs: object) -> httpx.Response:
|
||||
async def error_post(
|
||||
_client: object,
|
||||
_url: object,
|
||||
*,
|
||||
json: dict[str, object],
|
||||
**_kwargs: object,
|
||||
) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
200,
|
||||
request=httpx.Request("POST", "http://test/rpc"),
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "request",
|
||||
"id": json["id"],
|
||||
"error": {
|
||||
"code": 5000,
|
||||
"message": "Workflow operation failed",
|
||||
"data": {
|
||||
"code": "capability_not_found",
|
||||
"message": "unknown capability app.default.search",
|
||||
"code": "KeyError",
|
||||
"message": "unknown workflow capability 'app.default.search'",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -175,7 +181,12 @@ async def test_http_app_translates_known_workflow_protocol_error(
|
||||
await app.capability("app.default.search")
|
||||
|
||||
assert isinstance(raised.value, CapabilityNotFound)
|
||||
assert "unknown capability" in str(raised.value)
|
||||
assert "unknown workflow capability" in str(raised.value)
|
||||
assert raised.value.code == 5000
|
||||
assert raised.value.data == {
|
||||
"code": "KeyError",
|
||||
"message": "unknown workflow capability 'app.default.search'",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -184,13 +195,19 @@ async def test_http_app_preserves_unknown_protocol_error_details(
|
||||
) -> None:
|
||||
data = {"code": "future_workflow_error", "message": "future detail", "retry": 3}
|
||||
|
||||
async def error_post(*args: object, **kwargs: object) -> httpx.Response:
|
||||
async def error_post(
|
||||
_client: object,
|
||||
_url: object,
|
||||
*,
|
||||
json: dict[str, object],
|
||||
**_kwargs: object,
|
||||
) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
200,
|
||||
request=httpx.Request("POST", "http://test/rpc"),
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "request",
|
||||
"id": json["id"],
|
||||
"error": {
|
||||
"code": 5999,
|
||||
"message": "Future workflow error",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
@@ -33,11 +34,12 @@ from wf_transport_rpc_http.client.sources import RpcSourceAdminClientMixin
|
||||
|
||||
async def test_rpc_client_preserves_structured_jsonrpc_error() -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
request_id = json.loads(request.content)["id"]
|
||||
return httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"jsonrpc": "2.0",
|
||||
"id": "request",
|
||||
"id": request_id,
|
||||
"error": {
|
||||
"code": "missing_source",
|
||||
"message": "workflow operation failed",
|
||||
@@ -61,6 +63,31 @@ async def test_rpc_client_preserves_structured_jsonrpc_error() -> None:
|
||||
assert str(raised.value) == ("workflow operation failed: source is not configured")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("jsonrpc", "response_id"),
|
||||
[(None, "echo"), ("1.0", "echo"), ("2.0", "wrong")],
|
||||
)
|
||||
async def test_rpc_client_rejects_malformed_response_envelope(
|
||||
jsonrpc: str | None,
|
||||
response_id: str,
|
||||
) -> None:
|
||||
def handler(request: httpx.Request) -> httpx.Response:
|
||||
request_id = json.loads(request.content)["id"]
|
||||
payload: dict[str, object] = {
|
||||
"id": request_id if response_id == "echo" else response_id,
|
||||
"result": {},
|
||||
}
|
||||
if jsonrpc is not None:
|
||||
payload["jsonrpc"] = jsonrpc
|
||||
return httpx.Response(200, json=payload)
|
||||
|
||||
async with httpx.AsyncClient(transport=httpx.MockTransport(handler)) as http_client:
|
||||
client = RpcWorkflowApiClient(url="http://test/rpc", http_client=http_client)
|
||||
with pytest.raises(RuntimeError, match="JSON-RPC response"):
|
||||
await client.list_capabilities()
|
||||
|
||||
|
||||
def _constant_plan() -> RawWorkflowPlan:
|
||||
return RawWorkflowPlan.model_validate(
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user