"""Unit tests for AP2.0d NextAction execution-ready strategies.""" from __future__ import annotations from steering.graph.execution_engine import compute_execution_graph_state from steering.methods.registry import list_methods from steering.strategies.next_action import register_builtin_strategies from steering.strategies.next_action.execution_ready import ( build_execution_ready_candidates, merge_candidates, rank_ready_action_ids, ) from steering.strategies.next_action.registry import get_next_action_strategy def _action(action_id: str, status: str = "open", **kwargs): return { "id": action_id, "initiative_id": kwargs.get("initiative_id", "init-1"), "status": status, "sort_order": kwargs.get("sort_order", 0), "title": kwargs.get("title", action_id), "action_kind": kwargs.get("action_kind", "delivery"), "roadmap_item_id": kwargs.get("roadmap_item_id"), } def test_rank_ready_prefers_critical_path(): actions = [ _action("a", "done", sort_order=0), _action("b", "open", sort_order=1), _action("c", "open", sort_order=2), ] deps = [ { "predecessor_action_id": "a", "successor_action_id": "b", "dependency_kind": "requires", }, { "predecessor_action_id": "b", "successor_action_id": "c", "dependency_kind": "requires", }, ] state = compute_execution_graph_state(actions=actions, dependencies=deps) ranked = rank_ready_action_ids(state, prefer_critical_path=True) assert ranked == ["b"] def test_build_execution_ready_skips_blocked(): actions = [ _action("first", "open", sort_order=0), _action("second", "open", sort_order=1), ] state = compute_execution_graph_state(actions=actions, dependencies=[]) candidates = build_execution_ready_candidates( actions=actions, graph_state=state, limit=5 ) assert len(candidates) == 1 assert candidates[0]["action_id"] == "first" assert candidates[0]["reason_code"] in ("execution_ready", "execution_critical_path") def test_critical_path_reason_code(): actions = [ _action("a", "done", sort_order=0), _action("b", "open", sort_order=1), _action("c", "open", sort_order=2), ] deps = [ { "predecessor_action_id": "a", "successor_action_id": "b", "dependency_kind": "requires", }, { "predecessor_action_id": "b", "successor_action_id": "c", "dependency_kind": "requires", }, ] state = compute_execution_graph_state(actions=actions, dependencies=deps) candidates = build_execution_ready_candidates( actions=actions, graph_state=state, limit=5, prefer_critical_path=True ) assert candidates[0]["reason_code"] == "execution_critical_path" def test_merge_candidates_deduplicates_actions(): first = [ { "kind": "action", "action_id": "a1", "title": "A", } ] second = [ { "kind": "action", "action_id": "a1", "title": "A duplicate", }, { "kind": "review_milestone", "title": "Gate", }, ] merged = merge_candidates(first, second, limit=5) assert len(merged) == 2 assert merged[0]["action_id"] == "a1" assert merged[1]["kind"] == "review_milestone" def test_ap20d_strategies_registered(): register_builtin_strategies() for key in ( "sequential_dependency", "maturity_progression", "recurring_control", "queue_pull", "continuous_product", "program_delivery", ): assert get_next_action_strategy(key) is not None def test_ap20d_methods_wire_strategies(): register_builtin_strategies() methods = {m.key: m for m in list_methods()} assert methods["sequential_dependency"].next_action_strategy_key == "sequential_dependency" assert methods["maturity_progression"].next_action_strategy_key == "maturity_progression" assert methods["recurring_control"].next_action_strategy_key == "recurring_control" assert methods["queue_pull"].next_action_strategy_key == "queue_pull" assert methods["continuous_product"].next_action_strategy_key == "continuous_product" assert methods["program_delivery"].next_action_strategy_key == "program_delivery"