import pytest from typing import Optional pytestmark = pytest.mark.unit from src.daemon.spawner import AgentSpawner as Spawner class TestClassifyCompleted: """A1: 正常完成""" def test_ok_completed(self): result = Spawner._classify_outcome( 0, {"status": "ok", "summary": "completed"}, "", None ) assert result == {"outcome": "completed", "should_retry": False} def test_ok_completed_with_fallback(self): """A5/A6: status=ok + fallback_used""" result = Spawner._classify_outcome( 0, {"status": "ok", "summary": "completed", "fallback_used": True}, "", None, ) assert result["outcome"] == "fallback_timeout" assert result["should_retry"] is False class TestClassifyAgentFailed: """A4: Agent 自标 failed""" def test_task_status_failed(self): result = Spawner._classify_outcome( 0, {"status": "ok"}, "", "failed" ) assert result["outcome"] == "agent_failed" assert result["should_retry"] is False class TestClassifyTimeout: """A2/A3: gateway timeout""" def test_timeout_should_retry(self): result = Spawner._classify_outcome(0, {"status": "timeout"}, "", None) assert result["outcome"] == "gateway_timeout" assert result["should_retry"] is True assert result["retry_field"] == "retry_count" class TestClassifyInterrupted: """A14: SIGINT / SIGTERM 外部中断""" def test_sigint_exit_130(self): result = Spawner._classify_outcome(130, {}, "", None, "") assert result["outcome"] == "interrupted" assert result["should_retry"] is True assert result["retry_field"] == "retry_count" assert result["cooldown_seconds"] == 60 def test_sigterm_exit_143(self): result = Spawner._classify_outcome(143, {}, "", None, "") assert result["outcome"] == "interrupted" assert result["should_retry"] is True assert result["retry_field"] == "retry_count" assert result["cooldown_seconds"] == 60 class TestClassifyGatewayUnreachableNoJson: """A15: 无 JSON + exit≠0 + stderr 含 network 关键字""" def test_stderr_econnrefused(self): result = Spawner._classify_outcome( 1, {}, "Error: ECONNREFUSED connection refused", None, "" ) assert result["outcome"] == "gateway_unreachable" assert result["should_retry"] is True assert result["cooldown_seconds"] == 60 def test_stderr_gateway_closed(self): result = Spawner._classify_outcome( 1, {}, "gateway closed unexpectedly", None, "" ) assert result["outcome"] == "gateway_unreachable" assert result["should_retry"] is True assert result["cooldown_seconds"] == 60 class TestClassifyCompactInterruptedNoJson: """A16: 无 JSON + exit≠0 + stderr 含 compact 关键字""" def test_stderr_compaction_diag(self): result = Spawner._classify_outcome( 1, {}, "compaction-diag: overflow detected", None, "" ) assert result["outcome"] == "compact_interrupted" assert result["should_retry"] is True assert result["cooldown_seconds"] == 60 class TestClassifyCrashed: """A17: 无 JSON + exit≠0 + 无匹配 stderr → crash""" def test_crash_no_stderr(self): result = Spawner._classify_outcome(1, {}, "", None, "") assert result["outcome"] == "crashed" assert result["should_retry"] is False assert result["original"] == "process_crash" class TestClassifyNoJsonExit0: """exit=0 + 空 stdout → 根据 task_status 判定""" def test_task_status_done(self): result = Spawner._classify_outcome(0, {}, "", "done", "") assert result["outcome"] == "completed" assert result["should_retry"] is False def test_task_status_review(self): result = Spawner._classify_outcome(0, {}, "", "review", "") assert result["outcome"] == "completed" assert result["should_retry"] is False def test_task_status_pending(self): result = Spawner._classify_outcome(0, {}, "", "pending", "") assert result["outcome"] == "completed" assert result["should_retry"] is False class TestClassifyErrorAuth: """A7: status=error + stderr 含 auth 关键字""" def test_stderr_401(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "HTTP 401 Unauthorized", None ) assert result["outcome"] == "auth_failed" assert result["should_retry"] is False def test_stderr_unauthorized(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "unauthorized access", None ) assert result["outcome"] == "auth_failed" assert result["should_retry"] is False class TestClassifyErrorGateway: """A8: status=error + stderr 含 network 关键字""" def test_stderr_econnrefused(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "ECONNREFUSED 127.0.0.1:8083", None ) assert result["outcome"] == "gateway_unreachable" assert result["should_retry"] is True assert result["cooldown_seconds"] == 60 class TestClassifyErrorApi: """A9: status=error + stderr 含 API 错误关键字""" def test_stderr_rate_limit(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "rate_limit exceeded", None ) assert result["outcome"] == "api_error" assert result["should_retry"] is True assert result["cooldown_seconds"] == 60 def test_stderr_500(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "HTTP 500 Internal Server Error", None ) assert result["outcome"] == "api_error" assert result["should_retry"] is True assert result["cooldown_seconds"] == 60 class TestClassifyErrorCompact: """A10: status=error + stderr 含 compact 关键字""" def test_stderr_compaction_diag(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "compaction-diag report", None ) assert result["outcome"] == "compact_failed" assert result["should_retry"] is False class TestClassifyErrorLock: """A11: status=error + stderr 含 lock 关键字""" def test_stderr_lock(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "lock acquisition failed", None ) assert result["outcome"] == "lock_conflict" assert result["should_retry"] is True assert result["cooldown_seconds"] == 60 def test_stderr_busy(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "resource is busy", None ) assert result["outcome"] == "lock_conflict" assert result["should_retry"] is True class TestClassifyErrorFallback: """A12: status=error + stderr 无匹配""" def test_no_matching_stderr(self): result = Spawner._classify_outcome( 1, {"status": "error"}, "some unknown error", None ) assert result["outcome"] == "agent_error" assert result["should_retry"] is False class TestClassifyUnknownStatus: """兜底: status 为未知值""" def test_unknown_status_value(self): result = Spawner._classify_outcome( 0, {"status": "weird_value"}, "", None ) assert result["outcome"] == "agent_error" assert result["should_retry"] is False assert result["original"] == "unknown_status"