import pytest from unittest.mock import patch import json from typing import Any from src import orchestrator_pm from src import multi_agent_conductor from src import conductor_tech_lead from src.models import Ticket, Track, WorkerContext def test_generate_tracks() -> None: mock_response = """ [ {"id": "track_1", "title": "Setup", "goal": "init project", "type": "setup"}, {"id": "track_2", "title": "Refactor", "goal": "decouple modules", "type": "refactor"} ] """ with patch("src.ai_client.send", return_value=mock_response): tracks = orchestrator_pm.generate_tracks("Develop feature X", {}, []) assert len(tracks) == 2 assert tracks[0]["id"] == "track_1" assert tracks[1]["type"] == "refactor" def test_generate_tickets() -> None: mock_response = """ [ {"id": "T1", "description": "task 1", "depends_on": []}, {"id": "T2", "description": "task 2", "depends_on": ["T1"]} ] """ with patch("src.ai_client.send", return_value=mock_response): tickets = conductor_tech_lead.generate_tickets("Track goal", "code skeletons") assert len(tickets) == 2 assert tickets[0]["id"] == "T1" assert tickets[1]["depends_on"] == ["T1"] def test_topological_sort() -> None: tickets = [ {"id": "T2", "depends_on": ["T1"]}, {"id": "T1", "depends_on": []} ] sorted_tickets = conductor_tech_lead.topological_sort(tickets) assert sorted_tickets[0]["id"] == "T1" assert sorted_tickets[1]["id"] == "T2" def test_topological_sort_circular() -> None: tickets = [ {"id": "T1", "depends_on": ["T2"]}, {"id": "T2", "depends_on": ["T1"]} ] with pytest.raises(ValueError, match="DAG Validation Error"): conductor_tech_lead.topological_sort(tickets) def test_track_executable_tickets() -> None: t1 = Ticket(id="T1", description="d1", status="completed", assigned_to="worker1") t2 = Ticket(id="T2", description="d2", status="todo", assigned_to="worker1", depends_on=["T1"]) t3 = Ticket(id="T3", description="d3", status="todo", assigned_to="worker1", depends_on=["T2"]) track = Track(id="TR1", description="track", tickets=[t1, t2, t3]) # T2 should be executable because T1 is completed executable = track.get_executable_tickets() assert len(executable) == 1 assert executable[0].id == "T2" def test_conductor_engine_run() -> None: t1 = Ticket(id="T1", description="d1", status="todo", assigned_to="worker1") track = Track(id="TR1", description="track", tickets=[t1]) engine = multi_agent_conductor.ConductorEngine(track, auto_queue=True) with patch("src.multi_agent_conductor.run_worker_lifecycle") as mock_run: def side_effect(ticket, context, *args, **kwargs): ticket.mark_complete() return "Success" mock_run.side_effect = side_effect engine.run() assert t1.status == "completed" assert mock_run.called def test_conductor_engine_parse_json_tickets() -> None: track = Track(id="TR1", description="track", tickets=[]) engine = multi_agent_conductor.ConductorEngine(track) json_data = '[{"id": "T1", "description": "desc", "depends_on": []}]' engine.parse_json_tickets(json_data) assert len(track.tickets) == 1 assert track.tickets[0].id == "T1" def test_run_worker_lifecycle_blocked() -> None: ticket = Ticket(id="T1", description="desc", status="todo", assigned_to="worker1") context = WorkerContext(ticket_id="T1", model_name="model", messages=[]) with patch("src.ai_client.send") as mock_ai_client, \ patch("src.ai_client.reset_session"), \ patch("src.ai_client.set_provider"), \ patch("src.multi_agent_conductor.confirm_spawn", return_value=(True, "p", "c")): mock_ai_client.return_value = "BLOCKED because of missing info" multi_agent_conductor.run_worker_lifecycle(ticket, context) assert ticket.status == "blocked" assert ticket.blocked_reason == "BLOCKED because of missing info"