fix: validate incoming draft route outcomes
This commit is contained in:
@@ -4,6 +4,8 @@ from collections.abc import Mapping, Sequence
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from wf_artifacts.draft_workspaces.models import (
|
||||
WorkflowDraftWorkspace,
|
||||
summarize_draft_workspace,
|
||||
@@ -185,8 +187,18 @@ class WorkflowDraftAuthoringApi:
|
||||
f"{sorted(unknown_outcomes)!r}"
|
||||
)
|
||||
|
||||
if incoming is not None and incoming.step_id not in steps:
|
||||
raise ValueError(f"unknown incoming source step {incoming.step_id!r}")
|
||||
if incoming is not None:
|
||||
if incoming.step_id not in steps:
|
||||
raise ValueError(f"unknown incoming source step {incoming.step_id!r}")
|
||||
source_step = TypeAdapter(DraftStep).validate_python(
|
||||
steps[incoming.step_id]
|
||||
)
|
||||
source_outcomes = self._draft_step_route_outcomes(source_step)
|
||||
if source_outcomes is None or incoming.outcome not in source_outcomes:
|
||||
raise ValueError(
|
||||
f"unknown incoming route outcome {incoming.outcome!r} for "
|
||||
f"source step {incoming.step_id!r}"
|
||||
)
|
||||
|
||||
patch: list[dict[str, Any]] = [
|
||||
{
|
||||
|
||||
@@ -961,6 +961,86 @@ async def test_add_step_adds_missing_incoming_route_parent_atomically(
|
||||
assert workspace["draft"]["routes"]["echo"] == {"ok": "new_step"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_step_distinguishes_missing_and_explicit_empty_routes(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "draft_add_empty_routes")
|
||||
draft_api, _service, authoring = _draft_api(artifact_store, register_echo=True)
|
||||
api = WorkflowApi(authoring.context)
|
||||
await draft_api.create_draft_workspace(workspace_id="draft_ws", draft=_echo_draft())
|
||||
|
||||
step_adapter = TypeAdapter(DraftStep)
|
||||
step_without_routes = step_adapter.validate_python(
|
||||
{"use": "demo.personal.echo_tool", "input": [], "output": []}
|
||||
)
|
||||
first = await api.add_step(
|
||||
workspace_id="draft_ws",
|
||||
revision=1,
|
||||
step_id="no_routes",
|
||||
step=step_without_routes,
|
||||
routes=None,
|
||||
)
|
||||
assert first["revision"] == 2
|
||||
after_none = await draft_api.get_draft_workspace(
|
||||
workspace_id="draft_ws", include_draft=True
|
||||
)
|
||||
assert "no_routes" not in after_none["draft"]["routes"]
|
||||
|
||||
step_with_empty_routes = step_adapter.validate_python(
|
||||
{"use": "demo.personal.echo_tool", "input": [], "output": []}
|
||||
)
|
||||
second = await api.add_step(
|
||||
workspace_id="draft_ws",
|
||||
revision=2,
|
||||
step_id="empty_routes",
|
||||
step=step_with_empty_routes,
|
||||
routes={},
|
||||
)
|
||||
assert second["revision"] == 3
|
||||
after_empty = await draft_api.get_draft_workspace(
|
||||
workspace_id="draft_ws", include_draft=True
|
||||
)
|
||||
assert after_empty["draft"]["routes"]["empty_routes"] == {}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_step_rejects_unknown_incoming_outcome_without_mutation(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
artifact_store = FileWorkflowArtifactStore(tmp_path / "draft_add_bad_incoming")
|
||||
draft_api, _service, authoring = _draft_api(artifact_store, register_echo=True)
|
||||
api = WorkflowApi(authoring.context)
|
||||
await draft_api.create_draft_workspace(workspace_id="draft_ws", draft=_echo_draft())
|
||||
|
||||
draft_store = authoring.drafts._draft_store()
|
||||
assert isinstance(draft_store, FileDraftWorkspaceStore)
|
||||
workspace_path = draft_store._workspace_path("draft_ws")
|
||||
before_bytes = workspace_path.read_bytes()
|
||||
before = await draft_api.get_draft_workspace(
|
||||
workspace_id="draft_ws", include_draft=True
|
||||
)
|
||||
step = TypeAdapter(DraftStep).validate_python(
|
||||
{"use": "demo.personal.echo_tool", "input": [], "output": []}
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError, match="unknown incoming route outcome"):
|
||||
await api.add_step(
|
||||
workspace_id="draft_ws",
|
||||
revision=1,
|
||||
step_id="new_step",
|
||||
step=step,
|
||||
incoming=RouteSource("echo", "missing"),
|
||||
)
|
||||
|
||||
after = await draft_api.get_draft_workspace(
|
||||
workspace_id="draft_ws", include_draft=True
|
||||
)
|
||||
assert workspace_path.read_bytes() == before_bytes
|
||||
assert after["revision"] == before["revision"]
|
||||
assert after["draft"] == before["draft"]
|
||||
|
||||
|
||||
async def _assert_add_step_rejected_without_mutation(
|
||||
api: WorkflowApi,
|
||||
draft_api: WorkflowDraftApi,
|
||||
|
||||
Reference in New Issue
Block a user