Files
lda-wf/tests/core/test_run_codec.py
T

141 lines
4.3 KiB
Python

from __future__ import annotations
from wf_core import RunState, RunStatus, dump_run_state, load_run_state
from wf_core.models.reducers import ReducerRef
from wf_core.models.workflow_refs import WorkflowRef
from wf_core.paths import StatePath
from wf_core.run_state import (
ROOT_SCOPE_ID,
InterruptRequest,
InterruptRoute,
LineageState,
RuntimeScope,
StateWrite,
)
def test_run_state_codec_round_trips_completed_output() -> None:
run = RunState(
workflow_name="echo",
status=RunStatus.COMPLETED,
workflow_input={"text": "hi"},
state={"echoed": "hi"},
outcome="ok",
output={"echoed": "hi"},
)
stored = dump_run_state(run)
restored = load_run_state(stored)
assert stored["version"] == 2
assert restored.status is RunStatus.COMPLETED
assert restored.output["echoed"] == "hi"
def test_run_state_codec_round_trips_child_interrupt_lineage_types() -> None:
run = RunState(
workflow_name="parent",
status=RunStatus.INTERRUPTED,
workflow_input={},
state={},
)
run.scopes["child"] = RuntimeScope(
id="child",
workflow_name="child",
workflow_ref=WorkflowRef(name="child"),
)
run.lineages["child-lineage"] = LineageState(
id="child-lineage",
scope_id="child",
writes=[
StateWrite(
path=StatePath(("count",)),
incoming_value=1,
visible_value=2,
reducer=ReducerRef.model_validate("wf.std.add"),
)
],
)
run.interrupt = InterruptRequest(
id="interrupt:child",
frame_id="parent-step",
node_id="child_step",
kind="approval",
route=InterruptRoute(
frame_id="child-frame",
node_id="ask",
scope_id="child",
lineage_id="child-lineage",
parent_frame_id="parent-step",
workflow_ref=WorkflowRef(name="child"),
),
)
restored = load_run_state(dump_run_state(run))
write = restored.lineages["child-lineage"].writes[0]
assert isinstance(write.path, StatePath)
assert str(write.reducer.ref) == "wf.std.add"
assert restored.interrupt is not None
assert restored.interrupt.route is not None
assert isinstance(restored.interrupt.route.workflow_ref, WorkflowRef)
def test_run_state_codec_round_trips_interrupt_contract_fields() -> None:
run = RunState(
workflow_name="approval",
status=RunStatus.INTERRUPTED,
workflow_input={},
state={},
interrupt=InterruptRequest(
id="interrupt:approval",
frame_id="frame_1",
node_id="approval",
kind="approval",
payload={"message": "approve?"},
outcomes=["submitted", "cancelled"],
request_schema={
"type": "object",
"properties": {"message": {"type": "string"}},
"required": ["message"],
},
resume_schema={
"type": "object",
"properties": {"approved": {"type": "boolean"}},
"required": ["approved"],
},
typed=True,
),
)
restored = load_run_state(dump_run_state(run))
assert restored.interrupt is not None
assert restored.interrupt.outcomes == ["submitted", "cancelled"]
assert restored.interrupt.request_schema["required"] == ["message"]
assert restored.interrupt.resume_schema["required"] == ["approved"]
assert restored.interrupt.typed is True
def test_run_state_codec_restores_root_state_alias_for_resume_writes() -> None:
"""Root-scope commits after restore must be visible to final output."""
state = {"text": "before"}
run = RunState(
workflow_name="root",
status=RunStatus.INTERRUPTED,
workflow_input={"text": "before"},
state=state,
scopes={
ROOT_SCOPE_ID: RuntimeScope(
id=ROOT_SCOPE_ID,
workflow_name="root",
committed_state=state,
)
},
)
restored = load_run_state(dump_run_state(run))
restored.scopes[ROOT_SCOPE_ID].committed_state["after_resume"] = "visible"
assert restored.state["after_resume"] == "visible"