generated from coulomb/repo-seed
Implemented foundation of task-flow-engine
This commit is contained in:
170
tests/test_task_flow_engine.py
Normal file
170
tests/test_task_flow_engine.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
from task_flow_engine import AssertionDef, FlowDef, FlowEngine, WorkstationDef, resolve_target
|
||||
|
||||
|
||||
def test_all_assertions_satisfied_reports_reachable_workstations():
|
||||
flow = FlowDef(
|
||||
id="workstream.test",
|
||||
entity_type="workstream",
|
||||
workstations=[
|
||||
WorkstationDef(name="active"),
|
||||
WorkstationDef(
|
||||
name="completed",
|
||||
entry_assertions=[
|
||||
AssertionDef(
|
||||
id="tasks.all_done",
|
||||
target="tasks.*.status",
|
||||
op="all_eq",
|
||||
value=["done", "cancelled"],
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
result = FlowEngine().evaluate(
|
||||
{"status": "active", "tasks": [{"status": "done"}, {"status": "cancelled"}]},
|
||||
flow,
|
||||
)
|
||||
|
||||
assert result.exit_blocked is False
|
||||
assert result.reachable == ["active", "completed"]
|
||||
assert result.unreachable == []
|
||||
|
||||
|
||||
def test_failing_exit_assertion_identifies_blocking_assertion():
|
||||
flow = FlowDef(
|
||||
id="workstream.test",
|
||||
entity_type="workstream",
|
||||
workstations=[
|
||||
WorkstationDef(
|
||||
name="active",
|
||||
exit_assertions=[
|
||||
AssertionDef(
|
||||
id="tasks.all_done",
|
||||
target="tasks.*.status",
|
||||
op="all_eq",
|
||||
value=["done", "cancelled"],
|
||||
)
|
||||
],
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
result = FlowEngine().evaluate(
|
||||
{"status": "active", "tasks": [{"status": "done"}, {"status": "todo"}]},
|
||||
flow,
|
||||
)
|
||||
|
||||
assert result.exit_blocked is True
|
||||
assert [item.id for item in result.blocking_assertions] == ["tasks.all_done"]
|
||||
assert result.blocking_assertions[0].actual == ["done", "todo"]
|
||||
|
||||
|
||||
def test_custom_op_callable_is_invoked():
|
||||
calls = []
|
||||
|
||||
def has_review(assertion, obj, values):
|
||||
calls.append((assertion.id, values))
|
||||
return bool(obj.get("review"))
|
||||
|
||||
flow = FlowDef(
|
||||
id="review.test",
|
||||
entity_type="contribution",
|
||||
workstations=[
|
||||
WorkstationDef(name="draft"),
|
||||
WorkstationDef(
|
||||
name="accepted",
|
||||
entry_assertions=[
|
||||
AssertionDef(id="contribution.has_review", target="review", op="custom")
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
result = FlowEngine(custom_ops={"contribution.has_review": has_review}).evaluate(
|
||||
{"status": "draft", "review": {"approved_by": "bernd"}},
|
||||
flow,
|
||||
)
|
||||
|
||||
assert "accepted" in result.reachable
|
||||
assert calls == [("contribution.has_review", [{"approved_by": "bernd"}])]
|
||||
|
||||
|
||||
def test_empty_assertions_make_all_workstations_reachable():
|
||||
flow = FlowDef(
|
||||
id="empty.test",
|
||||
entity_type="task",
|
||||
workstations=[
|
||||
WorkstationDef(name="todo"),
|
||||
WorkstationDef(name="in_progress"),
|
||||
WorkstationDef(name="done"),
|
||||
],
|
||||
)
|
||||
|
||||
result = FlowEngine().evaluate({"status": "todo"}, flow)
|
||||
|
||||
assert result.exit_blocked is False
|
||||
assert result.reachable == ["todo", "in_progress", "done"]
|
||||
|
||||
|
||||
def test_circular_reference_in_target_path_does_not_loop_forever():
|
||||
obj = {"status": "active"}
|
||||
obj["self"] = obj
|
||||
|
||||
assert resolve_target(obj, "self.self.self.status") == []
|
||||
|
||||
|
||||
def test_yaml_flow_definitions_load_and_evaluate_representative_entities():
|
||||
flow_dir = Path(__file__).resolve().parents[1] / "flows"
|
||||
flows = {
|
||||
path.stem: FlowDef.from_dict(yaml.safe_load(path.read_text()))
|
||||
for path in sorted(flow_dir.glob("*.yaml"))
|
||||
}
|
||||
|
||||
assert sorted(flows) == [
|
||||
"capability_request",
|
||||
"contribution",
|
||||
"task",
|
||||
"workstream",
|
||||
]
|
||||
|
||||
workstream_result = FlowEngine(
|
||||
custom_ops={
|
||||
"dependencies.any_incomplete": lambda assertion, obj, values: any(
|
||||
value != assertion.value for value in values
|
||||
)
|
||||
}
|
||||
).evaluate(
|
||||
{
|
||||
"status": "active",
|
||||
"tasks": [{"status": "done"}],
|
||||
"dependencies": [{"workstation": "completed"}],
|
||||
},
|
||||
flows["workstream"],
|
||||
)
|
||||
assert "completed" in workstream_result.reachable
|
||||
assert "blocked" in [item.workstation for item in workstream_result.unreachable]
|
||||
|
||||
task_result = FlowEngine().evaluate(
|
||||
{"status": "blocked", "needs_human": False},
|
||||
flows["task"],
|
||||
)
|
||||
assert "in_progress" in task_result.reachable
|
||||
|
||||
contribution_result = FlowEngine().evaluate(
|
||||
{"status": "acknowledged", "previous_workstation": "acknowledged"},
|
||||
flows["contribution"],
|
||||
)
|
||||
assert "accepted" in contribution_result.reachable
|
||||
assert "merged" not in contribution_result.reachable
|
||||
|
||||
capability_result = FlowEngine().evaluate(
|
||||
{"status": "ready_for_review", "previous_workstation": "ready_for_review"},
|
||||
flows["capability_request"],
|
||||
)
|
||||
assert "completed" in capability_result.reachable
|
||||
assert "rejected" not in capability_result.reachable
|
||||
Reference in New Issue
Block a user