import asyncio import json import unittest from unittest.mock import patch import backend.main as main def parse_sse(event): return json.loads(event[6:].strip()) class AgentApiTest(unittest.TestCase): def test_agent_stream_forwards_agent_events(self): async def fake_agent(report, question="", max_steps=6, execution_mode="ai"): self.assertEqual(execution_mode, "manual") yield {"type": "thinking", "step": 1, "message": "规划"} yield {"type": "final", "content": "完成"} async def run(): main._last_diagnosis = {"score": 60} main._last_report = "report" with patch.object(main, "run_diagnostic_agent", fake_agent): response = await main.agent_diagnose_stream(question="检查 Pod", execution_mode="manual") return [parse_sse(event) async for event in response.body_iterator] events = asyncio.run(run()) self.assertEqual(events[0]["type"], "start") self.assertEqual(events[-1]["type"], "done") self.assertEqual(events[-2]["type"], "final") def test_config_view_is_read_only_and_does_not_require_approval(self): from backend.agent_tools import classify_kubectl_command decision = classify_kubectl_command("kubectl config view") self.assertEqual(decision.mode, "read") self.assertFalse(decision.requires_approval) def test_execute_approved_command_continues_agent_with_command_result(self): approval = { "command": "kubectl rollout restart deployment/api -n prod", "reason": "重启工作负载", "run_id": "", "report": "diagnosis report", "question": "修复 API", "execution_mode": "ai", "messages": [{"role": "system", "content": "context"}], "next_step": 2, "max_steps": 30, "command_counts": {"kubectl rollout restart deployment/api -n prod": 1}, } async def fake_execute(command): return {"command": command, "mode": "write", "returncode": 0, "stdout": "restarted", "stderr": ""} async def fake_resume(pending, result): self.assertEqual(result["stdout"], "restarted") yield {"type": "thinking", "step": 2, "message": "继续验证"} yield {"type": "command", "command": "kubectl get pods -n prod", "mode": "read", "reason": "验证恢复"} yield {"type": "result", "command": "kubectl get pods -n prod", "mode": "read", "returncode": 0, "stdout": "Running", "stderr": ""} yield {"type": "final", "content": "故障已恢复", "model": "test"} async def run(): with patch.object(main, "take_pending_approval", return_value=approval), patch.object(main, "execute_command", fake_execute), patch.object(main, "resume_diagnostic_agent", fake_resume): response = await main.execute_approved_agent_command(main.AgentApprovalRequest(approval_id="abc", approved=True)) return [parse_sse(event) async for event in response.body_iterator] events = asyncio.run(run()) self.assertEqual(events[0]["type"], "approved_result") self.assertEqual(events[1]["type"], "thinking") self.assertEqual(events[-2]["type"], "final") self.assertEqual(events[-1]["type"], "done") def test_execute_approved_command_once(self): approval = {"command": "kubectl scale deployment/api --replicas=2", "reason": "恢复副本"} async def fake_execute(command): return {"command": command, "mode": "write", "returncode": 0, "stdout": "scaled", "stderr": ""} async def fake_resume(pending, result): yield {"type": "final", "content": "验证完成", "model": "test"} async def run(): with patch.object(main, "take_pending_approval", return_value=approval), patch.object(main, "execute_command", fake_execute), patch.object(main, "resume_diagnostic_agent", fake_resume): response = await main.execute_approved_agent_command(main.AgentApprovalRequest(approval_id="abc", approved=True)) return [parse_sse(event) async for event in response.body_iterator] events = asyncio.run(run()) self.assertEqual(events[0]["type"], "approved_result") self.assertEqual(events[0]["returncode"], 0) self.assertEqual(events[0]["stdout"], "scaled") self.assertEqual(events[-1]["type"], "done") if __name__ == "__main__": unittest.main()