import pytest from unittest.mock import MagicMock, patch import json from typing import Any import orchestrator_pm import conductor_tech_lead import multi_agent_conductor from models import Track, Ticket @pytest.fixture def mock_ai_client() -> Any: with patch("ai_client.send") as mock_send: yield mock_send def test_generate_tracks(mock_ai_client: Any) -> None: # Tier 1 (PM) response mock mock_ai_client.return_value = json.dumps([ {"id": "track_1", "title": "Infrastructure Setup", "description": "Setup basic project structure"}, {"id": "track_2", "title": "Feature implementation", "description": "Implement core feature"} ]) user_request = "Build a new app" project_config = {} file_items = [] tracks = orchestrator_pm.generate_tracks(user_request, project_config, file_items) assert len(tracks) == 2 assert tracks[0]["id"] == "track_1" assert tracks[1]["id"] == "track_2" mock_ai_client.assert_called_once() def test_generate_tickets(mock_ai_client: Any) -> None: mock_ai_client.return_value = json.dumps([ {"id": "T-001", "description": "Define interfaces", "depends_on": []}, {"id": "T-002", "description": "Implement interfaces", "depends_on": ["T-001"]} ]) track_brief = "Implement a new feature." module_skeletons = "class Feature: pass" tickets = conductor_tech_lead.generate_tickets(track_brief, module_skeletons) assert len(tickets) == 2 assert tickets[0]["id"] == "T-001" assert tickets[1]["id"] == "T-002" assert tickets[1]["depends_on"] == ["T-001"] def test_topological_sort() -> None: tickets = [ {"id": "T-002", "description": "Dep on 001", "depends_on": ["T-001"]}, {"id": "T-001", "description": "Base", "depends_on": []}, {"id": "T-003", "description": "Dep on 002", "depends_on": ["T-002"]} ] sorted_tickets = conductor_tech_lead.topological_sort(tickets) assert sorted_tickets[0]["id"] == "T-001" assert sorted_tickets[1]["id"] == "T-002" assert sorted_tickets[2]["id"] == "T-003" def test_topological_sort_circular() -> None: tickets = [ {"id": "T-001", "depends_on": ["T-002"]}, {"id": "T-002", "depends_on": ["T-001"]} ] with pytest.raises(ValueError, match="Circular dependency detected"): conductor_tech_lead.topological_sort(tickets) def test_track_executable_tickets() -> None: t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="user") t2 = Ticket(id="T2", description="desc", status="todo", assigned_to="user", depends_on=["T1"]) track = Track(id="track_1", description="desc", tickets=[t1, t2]) executable = track.get_executable_tickets() assert len(executable) == 1 assert executable[0].id == "T1" # Complete T1 t1.status = "completed" executable = track.get_executable_tickets() assert len(executable) == 1 assert executable[0].id == "T2" @pytest.mark.asyncio async def test_conductor_engine_run_linear() -> None: t1 = Ticket(id="T1", description="desc", status="todo", assigned_to="user") t2 = Ticket(id="T2", description="desc", status="todo", assigned_to="user", depends_on=["T1"]) track = Track(id="track_1", description="desc", tickets=[t1, t2]) engine = multi_agent_conductor.ConductorEngine(track) with patch("multi_agent_conductor.run_worker_lifecycle") as mock_worker: # Mock worker to complete tickets def complete_ticket(ticket, context, **kwargs): ticket.status = "completed" mock_worker.side_effect = complete_ticket await engine.run_linear() assert t1.status == "completed" assert t2.status == "completed" assert mock_worker.call_count == 2 def test_conductor_engine_parse_json_tickets() -> None: track = Track(id="track_1", description="desc") engine = multi_agent_conductor.ConductorEngine(track) json_data = json.dumps([ {"id": "T1", "description": "desc 1", "depends_on": []}, {"id": "T2", "description": "desc 2", "depends_on": ["T1"]} ]) engine.parse_json_tickets(json_data) assert len(track.tickets) == 2 assert track.tickets[0].id == "T1" assert track.tickets[1].id == "T2" assert track.tickets[1].depends_on == ["T1"] def test_run_worker_lifecycle_blocked(mock_ai_client: Any) -> None: ticket = Ticket(id="T1", description="desc", status="todo", assigned_to="user") context = multi_agent_conductor.WorkerContext(ticket_id="T1", model_name="model", messages=[]) 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"