"""Helper script for Strategy Actor Robot Framework integration tests. Usage: python helper_strategy_actor.py stub-mode python helper_strategy_actor.py llm-json python helper_strategy_actor.py llm-fallback python helper_strategy_actor.py cycle-detection python helper_strategy_actor.py resolve-actor python helper_strategy_actor.py decision-conversion python helper_strategy_actor.py prompt-construction Forgejo: #828 """ from __future__ import annotations import sys from pathlib import Path # Ensure source tree is importable _SRC = str(Path(__file__).resolve().parents[1] / "src") sys.path.insert(0, _SRC) # Also ensure features/ is importable for mocks _FEATURES = str(Path(__file__).resolve().parents[1]) sys.path.insert(0, _FEATURES) from features.mocks.mock_strategy_llm import ( # noqa: E402 STRATEGY_JSON_RESPONSE, make_failing_registry, make_mock_lifecycle, make_mock_registry, ) from cleveragents.application.services.strategy_actor import ( # noqa: E402 StrategyAction, StrategyActor, StrategyTree, build_strategy_prompt, resolve_strategy_actor, validate_no_cycles, ) from cleveragents.core.exceptions import PlanError # noqa: E402 from cleveragents.domain.models.core.decision import DecisionType # noqa: E402 VALID_PLAN_ID = "01HX0000000000STRATEGY0001" def test_stub_mode() -> None: """Test strategy actor in stub mode (no LLM).""" actor = StrategyActor() assert not actor.has_llm, "Expected no LLM capability" result = actor.execute( plan_id=VALID_PLAN_ID, definition_of_done="- Set up project\n- Implement feature\n- Write tests", ) assert len(result.decisions) == 3, ( f"Expected 3 decisions, got {len(result.decisions)}" ) assert result.decision_root_id is not None assert "Set up project" in result.decisions[0].step_text print("strategy-actor-stub-ok") def test_llm_json() -> None: """Test strategy actor with mock LLM returning JSON strategy.""" registry = make_mock_registry(STRATEGY_JSON_RESPONSE) lifecycle = make_mock_lifecycle() actor = StrategyActor( provider_registry=registry, lifecycle_service=lifecycle, ) assert actor.has_llm, "Expected LLM capability" result = actor.execute( plan_id=VALID_PLAN_ID, definition_of_done="Build a REST API with authentication", ) assert len(result.decisions) == 5, ( f"Expected 5 decisions, got {len(result.decisions)}" ) assert "scaffolding" in result.decisions[0].step_text.lower() print("strategy-actor-llm-json-ok") def test_llm_fallback() -> None: """Test strategy actor falls back to stub on LLM error.""" registry = make_failing_registry() lifecycle = make_mock_lifecycle() actor = StrategyActor( provider_registry=registry, lifecycle_service=lifecycle, ) result = actor.execute( plan_id=VALID_PLAN_ID, definition_of_done="- Step A\n- Step B", ) assert len(result.decisions) == 2, ( f"Expected 2 decisions (fallback), got {len(result.decisions)}" ) print("strategy-actor-llm-fallback-ok") def test_cycle_detection() -> None: """Test dependency graph cycle detection.""" # Acyclic should pass assert validate_no_cycles([("A", "B"), ("B", "C")]) is True # Empty should pass assert validate_no_cycles([]) is True # Cyclic should raise PlanError try: validate_no_cycles([("A", "B"), ("B", "C"), ("C", "A")]) print("FAIL: Expected PlanError for cyclic graph") sys.exit(1) except PlanError as exc: assert "Circular dependency" in str(exc) print("strategy-actor-cycle-detection-ok") def test_resolve_actor() -> None: """Test resolve_strategy_actor integration function.""" # With config=llm actor = resolve_strategy_actor(config_value="llm") assert isinstance(actor, StrategyActor), "Expected StrategyActor for llm config" # With config=stub actor = resolve_strategy_actor(config_value="stub") assert actor is None, "Expected None for stub config" # With registry registry = make_mock_registry(STRATEGY_JSON_RESPONSE) actor = resolve_strategy_actor(provider_registry=registry) assert isinstance(actor, StrategyActor), "Expected StrategyActor with registry" # No config, no registry actor = resolve_strategy_actor() assert actor is None, "Expected None with no config" print("strategy-actor-resolve-ok") def test_decision_conversion() -> None: """Test conversion of strategy tree to Decision objects.""" actor = StrategyActor() result = actor.execute( plan_id=VALID_PLAN_ID, definition_of_done="- Step 1\n- Step 2", ) tree = StrategyTree( root_id=result.decision_root_id, actions=[ StrategyAction( action_id=d.decision_id, description=d.step_text, sequence=d.sequence, parent_id=d.parent_id, ) for d in result.decisions ], dependency_edges=[], ) decisions = actor.build_decisions(plan_id=VALID_PLAN_ID, strategy_tree=tree) assert len(decisions) == 2 assert decisions[0].decision_type == DecisionType.PROMPT_DEFINITION assert decisions[1].decision_type == DecisionType.STRATEGY_CHOICE print("strategy-actor-decision-conversion-ok") def test_prompt_construction() -> None: """Test strategy prompt construction.""" prompt = build_strategy_prompt( definition_of_done="Build API", resources=["source-code"], project_context="FastAPI", acms_context="Python 3.12", ) assert "Build API" in prompt assert "source-code" in prompt assert "FastAPI" in prompt assert "Python 3.12" in prompt # Minimal prompt prompt_min = build_strategy_prompt(definition_of_done="Build API") assert "Build API" in prompt_min assert "" not in prompt_min print("strategy-actor-prompt-ok") COMMANDS = { "stub-mode": test_stub_mode, "llm-json": test_llm_json, "llm-fallback": test_llm_fallback, "cycle-detection": test_cycle_detection, "resolve-actor": test_resolve_actor, "decision-conversion": test_decision_conversion, "prompt-construction": test_prompt_construction, } def main() -> None: """Entry point.""" if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS: print(f"Usage: {sys.argv[0]} <{'|'.join(COMMANDS.keys())}>") sys.exit(1) COMMANDS[sys.argv[1]]() if __name__ == "__main__": main()