import unittest from unittest.mock import patch, MagicMock import json import conductor_tech_lead class TestConductorTechLead(unittest.TestCase): @patch('ai_client.send') @patch('ai_client.set_provider') @patch('ai_client.reset_session') def test_generate_tickets_success(self, mock_reset_session, mock_set_provider, mock_send): # Setup mock response mock_tickets = [ { "id": "ticket_1", "type": "Ticket", "goal": "Test goal", "target_file": "test.py", "depends_on": [], "context_requirements": [] } ] mock_send.return_value = "```json\n" + json.dumps(mock_tickets) + "\n```" track_brief = "Test track brief" module_skeletons = "Test skeletons" # Call the function tickets = conductor_tech_lead.generate_tickets(track_brief, module_skeletons) # Verify set_provider was called mock_set_provider.assert_called_with('gemini', 'gemini-2.5-flash-lite') mock_reset_session.assert_called_once() # Verify send was called mock_send.assert_called_once() args, kwargs = mock_send.call_args self.assertEqual(kwargs['md_content'], "") self.assertIn(track_brief, kwargs['user_message']) self.assertIn(module_skeletons, kwargs['user_message']) # Verify tickets were parsed correctly self.assertEqual(tickets, mock_tickets) @patch('ai_client.send') @patch('ai_client.set_provider') @patch('ai_client.reset_session') def test_generate_tickets_parse_error(self, mock_reset_session, mock_set_provider, mock_send): # Setup mock invalid response mock_send.return_value = "Invalid JSON" # Call the function tickets = conductor_tech_lead.generate_tickets("brief", "skeletons") # Verify it returns an empty list on parse error self.assertEqual(tickets, []) class TestTopologicalSort(unittest.TestCase): def test_topological_sort_empty(self) -> None: tickets = [] sorted_tickets = conductor_tech_lead.topological_sort(tickets) self.assertEqual(sorted_tickets, []) def test_topological_sort_linear(self) -> None: tickets = [ {"id": "t2", "depends_on": ["t1"]}, {"id": "t1", "depends_on": []}, {"id": "t3", "depends_on": ["t2"]}, ] sorted_tickets = conductor_tech_lead.topological_sort(tickets) ids = [t["id"] for t in sorted_tickets] self.assertEqual(ids, ["t1", "t2", "t3"]) def test_topological_sort_complex(self): # t1 # | \ # t2 t3 # | / # t4 tickets = [ {"id": "t4", "depends_on": ["t2", "t3"]}, {"id": "t3", "depends_on": ["t1"]}, {"id": "t2", "depends_on": ["t1"]}, {"id": "t1", "depends_on": []}, ] sorted_tickets = conductor_tech_lead.topological_sort(tickets) ids = [t["id"] for t in sorted_tickets] # Possible valid orders: [t1, t2, t3, t4] or [t1, t3, t2, t4] self.assertEqual(ids[0], "t1") self.assertEqual(ids[-1], "t4") self.assertSetEqual(set(ids[1:3]), {"t2", "t3"}) def test_topological_sort_cycle(self) -> None: tickets = [ {"id": "t1", "depends_on": ["t2"]}, {"id": "t2", "depends_on": ["t1"]}, ] with self.assertRaises(ValueError) as cm: conductor_tech_lead.topological_sort(tickets) self.assertIn("Circular dependency detected", str(cm.exception)) def test_topological_sort_missing_dependency(self): # If a ticket depends on something not in the list, we should probably handle it or let it fail. # Usually in our context, we only care about dependencies within the same track. tickets = [ {"id": "t1", "depends_on": ["missing"]}, ] # For now, let's assume it should raise an error if a dependency is missing within the set we are sorting, # OR it should just treat it as "ready" if it's external? # Actually, let's just test that it doesn't crash if it's not a cycle. # But if 'missing' is not in tickets, it will never be satisfied. # Let's say it raises ValueError for missing internal dependencies. with self.assertRaises(ValueError): conductor_tech_lead.topological_sort(tickets) if __name__ == '__main__': unittest.main()