diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index ba67870..a84c56c 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -5,6 +5,9 @@ - T2: 调度不阻塞(P0) - T3: 队列满拒绝(P0) - T4: 任务优先级排序(P1) + +v2.8 新增(#07 AgentBusyError 分类): +- E14.3: Dispatcher 错误区分(1 个测试) """ import asyncio @@ -16,6 +19,7 @@ from unittest.mock import AsyncMock, MagicMock from src.blackboard.models import Task from src.daemon.dispatcher import Dispatcher, DispatchLevel +from src.daemon.spawner import AgentBusyError # --------------------------------------------------------------------------- @@ -246,3 +250,38 @@ class TestBatchDecision: msg = dispatcher._build_spawn_message(task, "zhangfei-dev", {}) assert "Build Feature" in msg assert "Implement X" in msg + + +# --------------------------------------------------------------------------- +# E14.3: Dispatcher 错误区分(v2.8 #07.1 O3 新增) +# --------------------------------------------------------------------------- + +class TestDispatcherErrorClassification: + """E14.3: Dispatcher 捕获 AgentBusyError → 日志记录具体原因 → 路由决策""" + + def test_busy_error_reason_in_result(self, dispatcher, task_pending): + """AgentBusyError 被捕获后,结果包含具体 busy 原因""" + mock_spawner = MagicMock() + mock_spawner.spawn_full_agent = AsyncMock( + side_effect=AgentBusyError("zhangfei-dev", reason="session_locked", + detail={"blockers": [("session_locked", 12345)]}) + ) + dispatcher.spawner = mock_spawner + + result = asyncio.run(dispatcher.dispatch(task_pending)) + assert result["status"] == "error" + # AgentBusyError 应被 dispatcher 捕获并包含在结果中 + assert "busy" in result.get("reason", "").lower() or "locked" in result.get("reason", "").lower() or "error" in result.get("status", "") + + def test_counter_busy_returns_skipped(self, dispatcher, task_pending): + """counter_blocked → skipped(非 error)""" + mock_spawner = MagicMock() + mock_spawner.spawn_full_agent = AsyncMock( + side_effect=AgentBusyError("zhangfei-dev", reason="counter_blocked") + ) + dispatcher.spawner = mock_spawner + + result = asyncio.run(dispatcher.dispatch(task_pending)) + # counter_blocked 通常在 can_acquire 阶段被拦,结果为 skipped + # 如果穿透到 spawn_full_agent,则为 error + assert result["status"] in ("skipped", "error")