generated from coulomb/repo-seed
171 lines
5.0 KiB
Python
171 lines
5.0 KiB
Python
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="finished",
|
|
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", "finished"]
|
|
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 not in assertion.value for value in values
|
|
)
|
|
}
|
|
).evaluate(
|
|
{
|
|
"status": "active",
|
|
"tasks": [{"status": "done"}],
|
|
"dependencies": [{"workstation": "finished"}],
|
|
},
|
|
flows["workstream"],
|
|
)
|
|
assert "finished" 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
|