feat: group draft add commands
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
import typer
|
||||
|
||||
from wf_cli.context import load_cli_context_from_typer as load_cli_context
|
||||
from wf_cli.io import emit_json
|
||||
from wf_cli.remote_errors import run_cli_operation
|
||||
|
||||
from .draft_options import _parse_map_flags, _parse_output_map_flags, _parse_route_flags
|
||||
|
||||
app = typer.Typer(
|
||||
name="add",
|
||||
help="Add one typed step to a draft workspace.",
|
||||
no_args_is_help=True,
|
||||
)
|
||||
|
||||
|
||||
@app.command("capability")
|
||||
def add_step_from_capability(
|
||||
ctx: typer.Context,
|
||||
workspace_id: Annotated[str, typer.Argument(help="Draft workspace id.")],
|
||||
revision: Annotated[
|
||||
int, typer.Option("--revision", min=1, help="Expected workspace revision.")
|
||||
],
|
||||
step_id: Annotated[str, typer.Option("--step", help="New draft step id.")],
|
||||
capability_name: Annotated[
|
||||
str, typer.Option("--capability", help="Qualified capability name.")
|
||||
],
|
||||
route_from_step: Annotated[
|
||||
str | None,
|
||||
typer.Option(
|
||||
"--from-step",
|
||||
help="Optional existing step whose outcome should route to this step.",
|
||||
),
|
||||
] = None,
|
||||
route_from_outcome: Annotated[
|
||||
str,
|
||||
typer.Option("--from-outcome", help="Outcome on --from-step."),
|
||||
] = "ok",
|
||||
route: Annotated[
|
||||
list[str] | None,
|
||||
typer.Option(
|
||||
"--route",
|
||||
help="Route mapping OUTCOME=TARGET. Repeat for multiple outcomes.",
|
||||
),
|
||||
] = None,
|
||||
input_mapping: Annotated[
|
||||
list[str] | None,
|
||||
typer.Option(
|
||||
"--input",
|
||||
help=(
|
||||
"Input binding SOURCE=LOCAL_TARGET. Repeat the flag for each "
|
||||
"input; do not put multiple mappings after one --input."
|
||||
),
|
||||
),
|
||||
] = None,
|
||||
output_mapping: Annotated[
|
||||
list[str] | None,
|
||||
typer.Option(
|
||||
"--bind-output",
|
||||
help=(
|
||||
"Output binding LOCAL_OUTPUT=STATE_TARGET with state schema "
|
||||
"projection. Repeat the flag for each output; do not put "
|
||||
"multiple mappings after one --bind-output."
|
||||
),
|
||||
),
|
||||
] = None,
|
||||
) -> None:
|
||||
"""Add a capability step; this also projects its schemas and bindings.
|
||||
|
||||
This command does not guess missing maps. Pass the route and bindings you
|
||||
want, then run `wf draft validate <workspace_id>`.
|
||||
|
||||
Repeat the flag for multiple bindings:
|
||||
`--input state.title=title --input state.summary=summary`
|
||||
`--bind-output title=state.title --bind-output summary=state.summary`
|
||||
"""
|
||||
input_map = _parse_map_flags(input_mapping)
|
||||
bind_outputs = _parse_output_map_flags(output_mapping)
|
||||
routes = _parse_route_flags(route)
|
||||
context = load_cli_context(ctx)
|
||||
emit_json(
|
||||
run_cli_operation(
|
||||
context,
|
||||
context.handlers.add_step_from_capability(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
capability_name=capability_name,
|
||||
route_from_step=route_from_step,
|
||||
route_from_outcome=route_from_outcome,
|
||||
routes=routes or None,
|
||||
input_map=input_map,
|
||||
bind_outputs=bind_outputs,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# These subgroups establish the public command boundary for later task slices.
|
||||
# They intentionally have no command bodies until their typed options are ready.
|
||||
for _name in (
|
||||
"interrupt",
|
||||
"foreach",
|
||||
"join",
|
||||
"end",
|
||||
"when",
|
||||
"choose",
|
||||
"match",
|
||||
"subgraph",
|
||||
):
|
||||
app.add_typer(
|
||||
typer.Typer(
|
||||
name=_name,
|
||||
help=f"Add a {_name} step (available in a later CLI task).",
|
||||
no_args_is_help=True,
|
||||
),
|
||||
name=_name,
|
||||
)
|
||||
@@ -0,0 +1,100 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import typer
|
||||
|
||||
from wf_api.surface import RouteSource
|
||||
from wf_core.paths import LocalPath, PathResolutionError, StatePath
|
||||
|
||||
|
||||
def _parse_assignment_flags(
|
||||
values: list[str] | None,
|
||||
*,
|
||||
option_name: str,
|
||||
expected: str,
|
||||
) -> dict[str, str]:
|
||||
parsed: dict[str, str] = {}
|
||||
for item in values or []:
|
||||
source, separator, target = item.partition("=")
|
||||
if separator != "=" or not source or not target:
|
||||
raise typer.BadParameter(f"{option_name} must use {expected}")
|
||||
if source in parsed:
|
||||
raise typer.BadParameter(f"duplicate {option_name} for {source!r}")
|
||||
parsed[source] = target
|
||||
return parsed
|
||||
|
||||
|
||||
def _parse_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
return _parse_assignment_flags(
|
||||
values,
|
||||
option_name="--map",
|
||||
expected="source=target",
|
||||
)
|
||||
|
||||
|
||||
def _parse_output_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
parsed = _parse_assignment_flags(
|
||||
values,
|
||||
option_name="--bind-output",
|
||||
expected="LOCAL_OUTPUT=STATE_TARGET",
|
||||
)
|
||||
for local_output, state_target in parsed.items():
|
||||
try:
|
||||
LocalPath.parse(local_output)
|
||||
except PathResolutionError as exc:
|
||||
raise typer.BadParameter(
|
||||
f"--bind-output source {local_output!r} must be a node-local "
|
||||
"output path such as value or ."
|
||||
) from exc
|
||||
try:
|
||||
StatePath.parse(state_target)
|
||||
except PathResolutionError as exc:
|
||||
raise typer.BadParameter(
|
||||
f"--bind-output target {state_target!r} must be a state path "
|
||||
"such as state.value"
|
||||
) from exc
|
||||
return parsed
|
||||
|
||||
|
||||
def _parse_step_input_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
"""Parse graph-source to bare-local input mappings for one draft step."""
|
||||
parsed = _parse_map_flags(values)
|
||||
for source, target in parsed.items():
|
||||
if target.startswith("local."):
|
||||
bare_target = target.removeprefix("local.")
|
||||
raise typer.BadParameter(
|
||||
"--map target must be a bare local field; "
|
||||
f"use {source}={bare_target}, not {source}={target}"
|
||||
)
|
||||
return parsed
|
||||
|
||||
|
||||
def _parse_route_flags(values: list[str] | None) -> dict[str, str]:
|
||||
return _parse_assignment_flags(
|
||||
values,
|
||||
option_name="--route",
|
||||
expected="OUTCOME=TARGET",
|
||||
)
|
||||
|
||||
|
||||
def parse_json_file(path: Path, *, option_name: str) -> Any:
|
||||
"""Read one structured CLI value and report file/JSON failures as option errors."""
|
||||
try:
|
||||
return json.loads(path.read_text(encoding="utf-8"))
|
||||
except OSError as exc:
|
||||
raise typer.BadParameter(f"{option_name}: cannot read {path}: {exc}") from exc
|
||||
except json.JSONDecodeError as exc:
|
||||
raise typer.BadParameter(
|
||||
f"{option_name}: invalid JSON in {path}: {exc.msg}"
|
||||
) from exc
|
||||
|
||||
|
||||
def route_source(from_step: str | None, from_outcome: str | None) -> RouteSource | None:
|
||||
if from_step is None:
|
||||
if from_outcome is not None:
|
||||
raise typer.BadParameter("--from-outcome requires --from-step")
|
||||
return None
|
||||
return RouteSource(step_id=from_step, outcome=from_outcome or "ok")
|
||||
@@ -6,88 +6,23 @@ from typing import Annotated, Literal
|
||||
|
||||
import typer
|
||||
|
||||
from wf_cli.commands import draft_add
|
||||
from wf_cli.commands.draft_options import (
|
||||
_parse_map_flags,
|
||||
_parse_route_flags,
|
||||
_parse_step_input_map_flags,
|
||||
)
|
||||
from wf_cli.context import load_cli_context_from_typer as load_cli_context
|
||||
from wf_cli.formats import ListOutputFormat, emit_list_payload
|
||||
from wf_cli.io import CliInputError, emit_json, parse_bindings, parse_json_value
|
||||
from wf_cli.remote_errors import run_cli_operation
|
||||
from wf_core.paths import LocalPath, PathResolutionError, StatePath
|
||||
|
||||
|
||||
def _parse_assignment_flags(
|
||||
values: list[str] | None,
|
||||
*,
|
||||
option_name: str,
|
||||
expected: str,
|
||||
) -> dict[str, str]:
|
||||
parsed: dict[str, str] = {}
|
||||
for item in values or []:
|
||||
source, separator, target = item.partition("=")
|
||||
if separator != "=" or not source or not target:
|
||||
raise typer.BadParameter(f"{option_name} must use {expected}")
|
||||
if source in parsed:
|
||||
raise typer.BadParameter(f"duplicate {option_name} for {source!r}")
|
||||
parsed[source] = target
|
||||
return parsed
|
||||
|
||||
|
||||
def _parse_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
return _parse_assignment_flags(
|
||||
values,
|
||||
option_name="--map",
|
||||
expected="source=target",
|
||||
)
|
||||
|
||||
|
||||
def _parse_output_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
parsed = _parse_assignment_flags(
|
||||
values,
|
||||
option_name="--bind-output",
|
||||
expected="LOCAL_OUTPUT=STATE_TARGET",
|
||||
)
|
||||
for local_output, state_target in parsed.items():
|
||||
try:
|
||||
LocalPath.parse(local_output)
|
||||
except PathResolutionError as exc:
|
||||
raise typer.BadParameter(
|
||||
f"--bind-output source {local_output!r} must be a node-local "
|
||||
"output path such as value or ."
|
||||
) from exc
|
||||
try:
|
||||
StatePath.parse(state_target)
|
||||
except PathResolutionError as exc:
|
||||
raise typer.BadParameter(
|
||||
f"--bind-output target {state_target!r} must be a state path "
|
||||
"such as state.value"
|
||||
) from exc
|
||||
return parsed
|
||||
|
||||
|
||||
def _parse_step_input_map_flags(values: list[str] | None) -> dict[str, str]:
|
||||
"""Parse graph-source to bare-local input mappings for one draft step."""
|
||||
parsed = _parse_map_flags(values)
|
||||
for source, target in parsed.items():
|
||||
if target.startswith("local."):
|
||||
bare_target = target.removeprefix("local.")
|
||||
raise typer.BadParameter(
|
||||
"--map target must be a bare local field; "
|
||||
f"use {source}={bare_target}, not {source}={target}"
|
||||
)
|
||||
return parsed
|
||||
|
||||
|
||||
def _parse_route_flags(values: list[str] | None) -> dict[str, str]:
|
||||
return _parse_assignment_flags(
|
||||
values,
|
||||
option_name="--route",
|
||||
expected="OUTCOME=TARGET",
|
||||
)
|
||||
|
||||
|
||||
app = typer.Typer(
|
||||
name="draft",
|
||||
help="Create, inspect, patch, validate, and save draft workflows.",
|
||||
no_args_is_help=True,
|
||||
)
|
||||
app.add_typer(draft_add.app, name="add")
|
||||
|
||||
|
||||
@app.command("list")
|
||||
@@ -443,88 +378,6 @@ def bind_draft(
|
||||
)
|
||||
|
||||
|
||||
@app.command("add-step")
|
||||
def add_step_from_capability(
|
||||
ctx: typer.Context,
|
||||
workspace_id: Annotated[str, typer.Argument(help="Draft workspace id.")],
|
||||
revision: Annotated[
|
||||
int, typer.Option("--revision", min=1, help="Expected workspace revision.")
|
||||
],
|
||||
step_id: Annotated[str, typer.Option("--step", help="New draft step id.")],
|
||||
capability_name: Annotated[
|
||||
str, typer.Option("--capability", help="Qualified capability name.")
|
||||
],
|
||||
route_from_step: Annotated[
|
||||
str | None,
|
||||
typer.Option(
|
||||
"--from-step",
|
||||
help="Optional existing step whose outcome should route to this step.",
|
||||
),
|
||||
] = None,
|
||||
route_from_outcome: Annotated[
|
||||
str,
|
||||
typer.Option("--from-outcome", help="Outcome on --from-step."),
|
||||
] = "ok",
|
||||
route: Annotated[
|
||||
list[str] | None,
|
||||
typer.Option(
|
||||
"--route",
|
||||
help="Route mapping OUTCOME=TARGET. Repeat for multiple outcomes.",
|
||||
),
|
||||
] = None,
|
||||
input_mapping: Annotated[
|
||||
list[str] | None,
|
||||
typer.Option(
|
||||
"--input",
|
||||
help=(
|
||||
"Input binding SOURCE=LOCAL_TARGET. Repeat the flag for each "
|
||||
"input; do not put multiple mappings after one --input."
|
||||
),
|
||||
),
|
||||
] = None,
|
||||
output_mapping: Annotated[
|
||||
list[str] | None,
|
||||
typer.Option(
|
||||
"--bind-output",
|
||||
help=(
|
||||
"Output binding LOCAL_OUTPUT=STATE_TARGET with state schema "
|
||||
"projection. Repeat the flag for each output; do not put "
|
||||
"multiple mappings after one --bind-output."
|
||||
),
|
||||
),
|
||||
] = None,
|
||||
) -> None:
|
||||
"""Add one capability-backed step with explicit route, input, and output wiring.
|
||||
|
||||
This command does not guess missing maps. Pass the route and bindings you
|
||||
want, then run `wf draft validate <workspace_id>`.
|
||||
|
||||
Repeat the flag for multiple bindings:
|
||||
`--input state.title=title --input state.summary=summary`
|
||||
`--bind-output title=state.title --bind-output summary=state.summary`
|
||||
"""
|
||||
input_map = _parse_map_flags(input_mapping)
|
||||
bind_outputs = _parse_output_map_flags(output_mapping)
|
||||
routes = _parse_route_flags(route)
|
||||
context = load_cli_context(ctx)
|
||||
emit_json(
|
||||
run_cli_operation(
|
||||
context,
|
||||
context.handlers.add_step_from_capability(
|
||||
workspace_id=workspace_id,
|
||||
revision=revision,
|
||||
step_id=step_id,
|
||||
capability_name=capability_name,
|
||||
route_from_step=route_from_step,
|
||||
route_from_outcome=route_from_outcome,
|
||||
routes=routes or None,
|
||||
input_map=input_map,
|
||||
bind_outputs=bind_outputs,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@app.command("branch")
|
||||
def branch_draft(
|
||||
ctx: typer.Context,
|
||||
|
||||
+101
-3
@@ -1,12 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
import typer
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from wf_cli.app import app
|
||||
from wf_cli.commands.draft_options import parse_json_file, route_source
|
||||
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
def test_draft_options_parse_json_file_reports_invalid_input(tmp_path) -> None:
|
||||
path = tmp_path / "invalid.json"
|
||||
path.write_text("{", encoding="utf-8")
|
||||
|
||||
with pytest.raises(typer.BadParameter, match="--schema-file: invalid JSON"):
|
||||
parse_json_file(path, option_name="--schema-file")
|
||||
|
||||
|
||||
def test_draft_options_route_source_requires_an_incoming_step() -> None:
|
||||
with pytest.raises(typer.BadParameter, match="--from-outcome requires --from-step"):
|
||||
route_source(None, "error")
|
||||
|
||||
incoming = route_source("lookup", None)
|
||||
assert incoming is not None
|
||||
assert incoming.step_id == "lookup"
|
||||
assert incoming.outcome == "ok"
|
||||
|
||||
|
||||
def test_wf_help_lists_lifecycle_groups() -> None:
|
||||
result = runner.invoke(app, ["--help"])
|
||||
|
||||
@@ -137,6 +161,27 @@ def test_wf_draft_help_does_not_list_old_create_from_capability() -> None:
|
||||
assert "create-from-capability" not in result.output
|
||||
|
||||
|
||||
def test_wf_draft_add_help_lists_typed_step_commands_and_removes_flat_command() -> None:
|
||||
result = runner.invoke(app, ["draft", "add", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
for name in (
|
||||
"capability",
|
||||
"interrupt",
|
||||
"foreach",
|
||||
"join",
|
||||
"end",
|
||||
"when",
|
||||
"choose",
|
||||
"match",
|
||||
"subgraph",
|
||||
):
|
||||
assert name in result.output
|
||||
|
||||
removed = runner.invoke(app, ["draft", "add-step", "--help"])
|
||||
assert removed.exit_code != 0
|
||||
|
||||
|
||||
def test_wf_draft_map_help_explains_replace_merge_and_validate() -> None:
|
||||
input_result = runner.invoke(app, ["draft", "set-input", "--help"])
|
||||
output_result = runner.invoke(app, ["draft", "set-output", "--help"])
|
||||
@@ -177,8 +222,8 @@ def test_wf_draft_bind_help_explains_direction() -> None:
|
||||
assert "set-input --merge" in help_text
|
||||
|
||||
|
||||
def test_wf_draft_add_step_help_explains_explicit_wiring() -> None:
|
||||
result = runner.invoke(app, ["draft", "add-step", "--help"])
|
||||
def test_wf_draft_add_capability_help_explains_explicit_wiring() -> None:
|
||||
result = runner.invoke(app, ["draft", "add", "capability", "--help"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
output = " ".join(result.output.split())
|
||||
@@ -186,6 +231,8 @@ def test_wf_draft_add_step_help_explains_explicit_wiring() -> None:
|
||||
assert "--from-step" in output
|
||||
assert "--bind-output" in output
|
||||
assert "does not guess" in output
|
||||
assert "projects its schemas and bindings" in output
|
||||
assert "draft validate" in output
|
||||
assert "Repeat the flag" in output
|
||||
assert "--input state.title=title --input state.summary=summary" in output
|
||||
assert (
|
||||
@@ -193,6 +240,56 @@ def test_wf_draft_add_step_help_explains_explicit_wiring() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_wf_draft_add_capability_calls_composed_local_handler(monkeypatch) -> None:
|
||||
calls: list[dict[str, Any]] = []
|
||||
|
||||
class FakeHandlers:
|
||||
async def add_step_from_capability(self, **kwargs: Any) -> dict[str, Any]:
|
||||
calls.append(kwargs)
|
||||
return {"revision": 2, "status": "valid"}
|
||||
|
||||
context = SimpleNamespace(handlers=FakeHandlers(), verbose=False)
|
||||
monkeypatch.setattr(
|
||||
"wf_cli.commands.draft_add.load_cli_context", lambda _ctx: context
|
||||
)
|
||||
|
||||
result = runner.invoke(
|
||||
app,
|
||||
[
|
||||
"draft",
|
||||
"add",
|
||||
"capability",
|
||||
"workspace",
|
||||
"--revision",
|
||||
"1",
|
||||
"--step",
|
||||
"call",
|
||||
"--capability",
|
||||
"demo.call",
|
||||
"--from-step",
|
||||
"start",
|
||||
"--route",
|
||||
"ok=__end__",
|
||||
"--input",
|
||||
"input.text=text",
|
||||
"--bind-output",
|
||||
"value=state.value",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
call = calls[0]
|
||||
assert call["workspace_id"] == "workspace"
|
||||
assert call["revision"] == 1
|
||||
assert call["step_id"] == "call"
|
||||
assert call["capability_name"] == "demo.call"
|
||||
assert call["route_from_step"] == "start"
|
||||
assert call["route_from_outcome"] == "ok"
|
||||
assert call["routes"] == {"ok": "__end__"}
|
||||
assert call["input_map"] == {"input.text": "text"}
|
||||
assert call["bind_outputs"] == {"value": "state.value"}
|
||||
|
||||
|
||||
def test_wf_draft_help_does_not_list_old_add_step_from_capability() -> None:
|
||||
result = runner.invoke(app, ["draft", "--help"])
|
||||
|
||||
@@ -221,7 +318,8 @@ def test_wf_draft_route_flags_reject_duplicate_outcomes() -> None:
|
||||
app,
|
||||
[
|
||||
"draft",
|
||||
"add-step",
|
||||
"add",
|
||||
"capability",
|
||||
"ws",
|
||||
"--revision",
|
||||
"1",
|
||||
|
||||
@@ -15,6 +15,7 @@ from wf_cli.context import CliContext, load_cli_context, load_local_cli_context
|
||||
from wf_core import END
|
||||
from wf_server import build_local_static_workflow_server
|
||||
from wf_transport_rpc_http import RpcWorkflowApiClient, create_rpc_app
|
||||
from wf_transport_rpc_http.client.base import RpcClientTransport
|
||||
from wf_transport_rpc_http.client.sources import RpcSourceAdminClientMixin
|
||||
|
||||
from .conftest import write_python_source_config
|
||||
@@ -1324,11 +1325,19 @@ def test_wf_draft_bind_uses_rpc_target(monkeypatch, tmp_path) -> None:
|
||||
]
|
||||
|
||||
|
||||
def test_wf_draft_add_step_from_capability_uses_rpc_target(
|
||||
monkeypatch, tmp_path
|
||||
) -> None:
|
||||
def test_wf_draft_add_capability_uses_rpc_target(monkeypatch, tmp_path) -> None:
|
||||
server = build_local_static_workflow_server(tmp_path / "store")
|
||||
_patch_rpc_client_to_server(monkeypatch, server)
|
||||
rpc_methods: list[str] = []
|
||||
original_call = RpcClientTransport._call
|
||||
|
||||
async def recording_call(
|
||||
self: RpcClientTransport, method: str, params: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
rpc_methods.append(method)
|
||||
return await original_call(self, method, params)
|
||||
|
||||
monkeypatch.setattr(RpcClientTransport, "_call", recording_call)
|
||||
config_path = tmp_path / "wf.json"
|
||||
config_path.write_text('{"version": 1}', encoding="utf-8")
|
||||
runner = CliRunner()
|
||||
@@ -1354,7 +1363,8 @@ def test_wf_draft_add_step_from_capability_uses_rpc_target(
|
||||
[
|
||||
*base_args,
|
||||
"draft",
|
||||
"add-step",
|
||||
"add",
|
||||
"capability",
|
||||
"add_step_ws",
|
||||
"--revision",
|
||||
"1",
|
||||
@@ -1379,9 +1389,11 @@ def test_wf_draft_add_step_from_capability_uses_rpc_target(
|
||||
payload = json.loads(result.output)
|
||||
assert payload["revision"] == 2
|
||||
assert payload["status"] == "valid"
|
||||
assert rpc_methods[-1] == "workflow.draft_workspaces.add_step_from_capability"
|
||||
assert "workflow.draft_workspaces.add_step" not in rpc_methods
|
||||
|
||||
|
||||
def test_wf_draft_add_step_reports_bare_output_target_without_traceback(
|
||||
def test_wf_draft_add_capability_reports_bare_output_target_without_traceback(
|
||||
monkeypatch, tmp_path
|
||||
) -> None:
|
||||
server = build_local_static_workflow_server(tmp_path / "store")
|
||||
@@ -1411,7 +1423,8 @@ def test_wf_draft_add_step_reports_bare_output_target_without_traceback(
|
||||
[
|
||||
*base_args,
|
||||
"draft",
|
||||
"add-step",
|
||||
"add",
|
||||
"capability",
|
||||
"add_step_ws",
|
||||
"--revision",
|
||||
"1",
|
||||
@@ -1576,7 +1589,8 @@ def test_wf_draft_forward_route_invalid_via_rpc(monkeypatch, tmp_path) -> None:
|
||||
[
|
||||
*base_args,
|
||||
"draft",
|
||||
"add-step",
|
||||
"add",
|
||||
"capability",
|
||||
"fwd_ws",
|
||||
"--revision",
|
||||
"1",
|
||||
|
||||
Reference in New Issue
Block a user