Files
lda-wf/tests/wf_api/test_run_admission_path.py
T

118 lines
4.0 KiB
Python

"""Admission path through the run API: persist before dispatch (T06)."""
from __future__ import annotations
import asyncio
from pathlib import Path
from tests.wf_mcp.test_support import echo_tool
from tests.wf_mcp.workflow_surface.conftest import echo_artifact
from wf_api.runs import WorkflowRunApi
from wf_artifacts import (
FileRunStore,
FileWorkflowArtifactStore,
StoredRunStatus,
WorkflowDeployment,
)
from wf_mcp.broker import WfMcpService
from wf_mcp.broker.service.workflow_operation_context import context_from_service
from wf_mcp.models import ConnectionConfig
from wf_mcp.storage import FileStore
def _api_with_echo(root: Path) -> tuple[WorkflowRunApi, FileRunStore]:
artifact_store = FileWorkflowArtifactStore(root)
artifact_store.save_artifact(echo_artifact())
artifact_store.save_deployment(
WorkflowDeployment(
id="echo.personal",
artifact_id="echo",
artifact_version=1,
bindings=[{"logical_source": "demo", "concrete_source": "demo.personal"}],
)
)
service = WfMcpService(
store=FileStore(root / "mcp"),
artifact_store=artifact_store,
run_store=FileRunStore(root / "mcp"),
)
service.register_connection(
ConnectionConfig(id="demo.personal", server="demo", account="personal")
)
service.register_specs("demo.personal", echo_tool)
context = context_from_service(service)
assert context.run_store is not None
assert isinstance(context.run_store, FileRunStore)
return WorkflowRunApi(context), context.run_store
def test_manual_run_persists_admission_before_dispatch(tmp_path: Path) -> None:
api, store = _api_with_echo(tmp_path / "admit")
result = asyncio.run(
api.run_deployment(
deployment_id="echo.personal",
workflow_input={"text": "hi"},
)
)
assert result["run_id"] is not None
admission = store.get_admission(result["run_id"])
assert admission.resolved_input == {"text": "hi"}
assert admission.environment.deployment.id == "echo.personal"
record = store.get_run(result["run_id"])
assert record.status is not StoredRunStatus.ADMITTED
assert record.latest_checkpoint_id is not None
def test_fault_before_admission_never_dispatches(tmp_path: Path, monkeypatch) -> None:
import wf_api.runs as runs_module
api, store = _api_with_echo(tmp_path / "fault-before")
real_write = store._write_json
dispatched: list[str] = []
real_plan = runs_module.raw_plan_from_artifact
def _fail_on_admission(path: Path, payload: object) -> None:
if path.name == "admission.json":
raise OSError("injected admission failure")
real_write(path, payload)
def _spy_plan(artifact): # type: ignore[no-untyped-def]
dispatched.append("dispatch")
return real_plan(artifact)
# Real serialization fault at the admission file: nothing after it
# (plan building, dispatch, stopped persist) may run.
monkeypatch.setattr(store, "_write_json", _fail_on_admission)
monkeypatch.setattr(runs_module, "raw_plan_from_artifact", _spy_plan)
try:
asyncio.run(
api.run_deployment(
deployment_id="echo.personal",
workflow_input={"text": "hi"},
)
)
raise AssertionError("fault must propagate")
except OSError:
pass
assert dispatched == []
fresh = FileRunStore(store.root)
assert fresh.list_admissions() == []
assert fresh.list_runs() == []
def test_captured_invocation_freezes_input(tmp_path: Path) -> None:
api, store = _api_with_echo(tmp_path / "freeze")
payload = {"text": "hi", "nested": {"n": 1}}
result = asyncio.run(
api.run_deployment(
deployment_id="echo.personal",
workflow_input=payload,
)
)
assert result["run_id"] is not None
payload["text"] = "mutated"
payload["nested"]["n"] = 999
admission = store.get_admission(result["run_id"])
assert admission.resolved_input == {"text": "hi", "nested": {"n": 1}}