Spaces:
Sleeping
Sleeping
File size: 6,371 Bytes
127bfda 2641e6e 127bfda 2641e6e 127bfda 37ee895 127bfda 2641e6e 127bfda 2641e6e 127bfda 2641e6e 127bfda 2641e6e 127bfda 37ee895 127bfda 37ee895 127bfda | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import asyncio
import unittest
from unittest.mock import AsyncMock, MagicMock, patch
from bot import autonomy_runtime
class TestAutonomyRuntime(unittest.IsolatedAsyncioTestCase):
async def test_bind_unbind(self):
runtime = autonomy_runtime.AutonomyRuntime()
loop = asyncio.get_running_loop()
with patch.object(runtime, "_ensure_loop_task") as mock_ensure:
runtime.bind(loop=loop, mode="irc")
self.assertEqual(runtime._mode, "irc")
runtime.unbind()
self.assertIsNone(runtime._loop)
self.assertIsNone(runtime._loop_task)
@patch("bot.autonomy_runtime.control_plane")
@patch("bot.autonomy_runtime.process_autonomy_goal", new_callable=AsyncMock)
async def test_run_tick(self, mock_process, mock_cp):
runtime = autonomy_runtime.AutonomyRuntime()
mock_cp.consume_due_goals.return_value = ["goal1"]
mock_cp.get_config.return_value = {"twitch_channel_login": "default"}
mock_process.return_value = {"status": "success"}
result = await runtime._run_tick(force=True, reason="test")
self.assertTrue(result["ok"])
self.assertEqual(result["due_goals"], 1)
mock_cp.register_tick.assert_called_with(reason="test")
# Deve agora incluir o channel_id
mock_process.assert_called_once_with("goal1", None, channel_id="default")
@patch("bot.autonomy_runtime.observability")
@patch("bot.autonomy_runtime.control_plane")
@patch("bot.autonomy_runtime.process_autonomy_goal", new_callable=AsyncMock)
async def test_run_tick_registers_goal_session_kpi(self, mock_process, mock_cp, mock_obs):
runtime = autonomy_runtime.AutonomyRuntime()
mock_cp.consume_due_goals.return_value = [{"id": "g1", "risk": "suggest_streamer"}]
mock_cp.get_config.return_value = {"twitch_channel_login": "default"}
mock_cp.register_goal_session_result.return_value = {"met": True}
mock_process.return_value = {
"goal_id": "g1",
"risk": "suggest_streamer",
"outcome": "queued",
}
result = await runtime._run_tick(force=True, reason="test")
mock_cp.register_goal_session_result.assert_called_once_with(
goal_id="g1",
outcome="queued",
)
self.assertTrue(result["processed"][0]["session_kpi"]["met"])
mock_obs.record_autonomy_goal.assert_called_once()
@patch("bot.autonomy_runtime.control_plane")
@patch("bot.autonomy_runtime.asyncio.sleep", new_callable=AsyncMock)
async def test_heartbeat_loop(self, mock_sleep, mock_cp):
runtime = autonomy_runtime.AutonomyRuntime()
loop = asyncio.get_running_loop()
runtime.bind(loop=loop, mode="test")
# Mock _run_tick to stop the loop after one call
with patch.object(runtime, "_run_tick", new_callable=AsyncMock) as mock_tick:
def side_effect(*args, **kwargs):
runtime.unbind()
return {"ok": True}
mock_tick.side_effect = side_effect
await runtime._heartbeat_loop()
mock_tick.assert_called_once()
mock_cp.set_loop_running.assert_called()
@patch("bot.autonomy_runtime.control_plane")
@patch("bot.autonomy_runtime.sentiment_engine")
@patch("bot.autonomy_runtime.process_autonomy_goal", new_callable=AsyncMock)
async def test_run_tick_with_sentiment_triggers(self, mock_process, mock_sent, mock_cp):
runtime = autonomy_runtime.AutonomyRuntime()
mock_cp.consume_due_goals.return_value = []
mock_cp.get_config.return_value = {"twitch_channel_login": "default"}
mock_sent.should_trigger_anti_boredom.return_value = True
mock_sent.should_trigger_anti_confusion.return_value = True
mock_process.return_value = {"status": "success"}
result = await runtime._run_tick(force=False, reason="test")
self.assertTrue(result["ok"])
self.assertEqual(len(result["processed"]), 2)
# Verifica chamadas com channel_id
mock_sent.should_trigger_anti_boredom.assert_called_with("default")
mock_sent.should_trigger_anti_confusion.assert_called_with("default")
# Verify call arguments
calls = [call[0][0] for call in mock_process.call_args_list]
self.assertTrue(any(g["id"] == "dynamic-anti-boredom" for g in calls))
self.assertTrue(any(g["id"] == "dynamic-anti-confusion" for g in calls))
@patch("bot.autonomy_runtime.control_plane")
@patch("bot.autonomy_runtime.sentiment_engine")
@patch("bot.autonomy_runtime.process_autonomy_goal", new_callable=AsyncMock)
async def test_run_tick_skips_sentiment_on_force(self, mock_process, mock_sent, mock_cp):
runtime = autonomy_runtime.AutonomyRuntime()
mock_cp.consume_due_goals.return_value = []
mock_cp.get_config.return_value = {"twitch_channel_login": "default"}
mock_sent.should_trigger_anti_boredom.return_value = True
await runtime._run_tick(force=True, reason="test")
# Should NOT have added dynamic goals because force=True
mock_process.assert_not_called()
@patch("bot.autonomy_runtime.asyncio.run")
def test_run_manual_tick_no_loop(self, mock_run):
runtime = autonomy_runtime.AutonomyRuntime()
runtime._loop = None # Ensure no loop
def run_side_effect(coroutine):
coroutine.close()
return {"ok": True}
mock_run.side_effect = run_side_effect
result = runtime.run_manual_tick(force=True)
self.assertTrue(result["ok"])
mock_run.assert_called_once()
@patch("bot.autonomy_runtime.asyncio.run_coroutine_threadsafe")
def test_run_manual_tick_threadsafe(self, mock_run):
runtime = autonomy_runtime.AutonomyRuntime()
loop = MagicMock()
loop.is_closed.return_value = False
loop.is_running.return_value = True
runtime._loop = loop
mock_future = MagicMock()
mock_future.result.return_value = {"ok": True}
def submit_side_effect(coroutine, _loop):
coroutine.close()
return mock_future
mock_run.side_effect = submit_side_effect
result = runtime.run_manual_tick(force=True)
self.assertTrue(result["ok"])
mock_run.assert_called_once()
|