test_scheduler_robustness.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import unittest
  2. from unittest.mock import MagicMock, patch, AsyncMock
  3. import sys
  4. # Mock missing dependencies
  5. sys.modules['libsql_client'] = MagicMock()
  6. import asyncio
  7. from datetime import datetime, timezone
  8. from app.services.forum_scheduler import ForumScheduler
  9. from app.agent.agent import ParticipantAgent
  10. class TestSchedulerRobustness(unittest.IsolatedAsyncioTestCase):
  11. async def test_error_broadcasting(self):
  12. scheduler = ForumScheduler()
  13. forum_id = 1
  14. # Mock dependencies
  15. mock_db = MagicMock()
  16. mock_forum = MagicMock()
  17. mock_forum.id = forum_id
  18. mock_forum.status = "running"
  19. mock_forum.duration_minutes = 10
  20. mock_forum.start_time = datetime.now(timezone.utc)
  21. mock_forum.moderator = None
  22. mock_forum.summary_history = []
  23. # Mock participant
  24. p1 = MagicMock()
  25. p1.persona.name = "Alice"
  26. p1.persona.system_prompt = "sys"
  27. p1.persona_id = 101
  28. # Mock Agent
  29. mock_agent = MagicMock(spec=ParticipantAgent)
  30. mock_agent.name = "Alice"
  31. mock_agent.private_memory = MagicMock()
  32. mock_agent.private_memory.speech_history = []
  33. mock_agent.ablation_flags = {}
  34. # Mock think to succeed
  35. mock_agent.think.return_value = {
  36. "action": "apply_to_speak",
  37. "mind": "I want to speak",
  38. "previous": "None",
  39. "benefit": "Insight"
  40. }
  41. # Mock speak to RAISE EXCEPTION
  42. async def mock_speak_error(*args, **kwargs):
  43. raise Exception("API Timeout")
  44. # Note: speak is called via asyncio.to_thread, so it should be a sync function or mocked such that to_thread handles it.
  45. # But here we mock to_thread or the method itself?
  46. # In the code: await asyncio.to_thread(agent.speak, ...)
  47. # So agent.speak should be a sync function that raises.
  48. def mock_speak_sync_error(*args, **kwargs):
  49. raise Exception("API Timeout")
  50. mock_agent.speak.side_effect = mock_speak_sync_error
  51. with patch('app.services.forum_scheduler.db_manager.get_connection', return_value=mock_db), \
  52. patch('app.services.forum_scheduler.get_forum', side_effect=[mock_forum, mock_forum, None]), \
  53. patch('app.services.forum_scheduler.get_forum_participants', return_value=[p1]), \
  54. patch('app.services.forum_scheduler.get_forum_messages', return_value=[]), \
  55. patch('app.services.forum_scheduler.update_forum'), \
  56. patch('app.services.forum_scheduler.update_forum_participant'), \
  57. patch('app.services.forum_scheduler.create_message'), \
  58. patch('app.services.forum_scheduler.manager') as mock_manager, \
  59. patch('asyncio.sleep', new_callable=AsyncMock), \
  60. patch('app.services.forum_scheduler.ForumScheduler._broadcast_system_message', new_callable=AsyncMock), \
  61. patch('app.services.forum_scheduler.ForumScheduler._broadcast_system_log', new_callable=AsyncMock) as mock_broadcast_log, \
  62. patch('app.services.forum_scheduler.ForumScheduler._moderator_speak', new_callable=AsyncMock), \
  63. patch.object(scheduler, '_is_forum_running', return_value=True), \
  64. patch('app.services.forum_scheduler.ParticipantAgent', return_value=mock_agent), \
  65. patch('app.services.forum_scheduler.ModeratorAgent'), \
  66. patch('app.services.forum_scheduler.SharedMemory'):
  67. # Run loop
  68. # We set get_forum side_effect to return None eventually to break the loop
  69. await scheduler._run_forum_loop(forum_id)
  70. # Verify that _agent_speak was called (implied by the flow reaching speak)
  71. # But _agent_speak is internal method. We didn't patch it, so it runs.
  72. # It calls agent.speak (mocked to fail).
  73. # Then it should call _broadcast_system_log with error.
  74. # Check calls to broadcast_log
  75. # We expect:
  76. # 1. Start loop
  77. # 2. Moderator ready
  78. # 3. Opening
  79. # 4. Thinking...
  80. # 5. Error log for agent speak
  81. error_logs = [call for call in mock_broadcast_log.call_args_list if "发言生成失败" in str(call)]
  82. self.assertTrue(len(error_logs) > 0, "Should have broadcasted the API error")
  83. print("Found error logs:", error_logs)
  84. if __name__ == '__main__':
  85. unittest.main()