""" Tests for postmortem_draft_graph. Mocks the GatewayClient — no real network calls. """ import asyncio import base64 import json import sys from pathlib import Path from unittest.mock import patch import pytest sys.path.insert(0, str(Path(__file__).parent.parent)) from tests.conftest import MockGatewayClient, _run # ─── Mock data ──────────────────────────────────────────────────────────────── _INCIDENT_DATA = { "id": "inc_20260223_1000_abc123", "service": "router", "env": "prod", "severity": "P1", "status": "open", "title": "Router OOM", "summary": "Router pods running out of memory under high load", "started_at": "2026-02-23T10:00:00Z", "ended_at": None, "created_by": "sofiia", "events": [ {"ts": "2026-02-23T10:01:00Z", "type": "note", "message": "Memory usage >90%"}, {"ts": "2026-02-23T10:10:00Z", "type": "action", "message": "Restarted pods"}, ], "artifacts": [], } _INCIDENT_WITH_TRIAGE = { **_INCIDENT_DATA, "artifacts": [ {"kind": "triage_report", "format": "json", "path": "ops/incidents/inc_test/triage_report.json"}, ], } _OVERVIEW_DATA = { "status": "degraded", "alerts": [{"name": "OOMKilled", "severity": "critical"}], } _HEALTH_DATA = {"status": "unhealthy", "error": "OOM"} _KB_DATA = {"results": [ {"path": "docs/runbooks/oom.md", "content": "## OOM Runbook\n- Check memory limits\n- Restart pods"} ]} # ─── Tests ──────────────────────────────────────────────────────────────────── class TestPostmortemDraftGraph: """Happy path: incident exists, triage exists, postmortem generated.""" def test_happy_path_with_triage(self): from app.graphs.postmortem_draft_graph import build_postmortem_draft_graph mock_gw = MockGatewayClient() mock_gw.register("oncall_tool", "incident_get", _INCIDENT_WITH_TRIAGE) mock_gw.register("oncall_tool", "incident_attach_artifact", {"artifact": {"path": "test", "sha256": "abc"}}) mock_gw.register("oncall_tool", "incident_append_event", {"event": {"ts": "now", "type": "followup"}}) graph = build_postmortem_draft_graph() with patch("app.graphs.postmortem_draft_graph.GatewayClient", return_value=mock_gw): result = _run(graph.ainvoke({ "run_id": "gr_test_01", "agent_id": "sofiia", "workspace_id": "ws1", "user_id": "u1", "input": { "incident_id": "inc_20260223_1000_abc123", "service": "router", }, })) assert result["graph_status"] == "succeeded" pm = result["result"] assert pm["incident_id"] == "inc_20260223_1000_abc123" assert pm["artifacts_count"] >= 2 # md + json assert "postmortem" in result["postmortem_md"].lower() def test_triage_missing_triggers_generation(self): """When incident has no triage artifact, the graph generates one.""" from app.graphs.postmortem_draft_graph import build_postmortem_draft_graph mock_gw = MockGatewayClient() mock_gw.register("oncall_tool", "incident_get", _INCIDENT_DATA) # no triage artifact mock_gw.register("observability_tool", "service_overview", _OVERVIEW_DATA) mock_gw.register("oncall_tool", "service_health", _HEALTH_DATA) mock_gw.register("kb_tool", "search", _KB_DATA) mock_gw.register("oncall_tool", "incident_attach_artifact", {"artifact": {"path": "t", "sha256": "x"}}) mock_gw.register("oncall_tool", "incident_append_event", {"event": {}}) graph = build_postmortem_draft_graph() with patch("app.graphs.postmortem_draft_graph.GatewayClient", return_value=mock_gw): result = _run(graph.ainvoke({ "run_id": "gr_test_02", "agent_id": "sofiia", "workspace_id": "ws1", "user_id": "u1", "input": {"incident_id": "inc_20260223_1000_abc123"}, })) assert result["graph_status"] == "succeeded" assert result.get("triage_was_generated") is True # Should have triage + postmortem artifacts (3 total) assert result["result"]["artifacts_count"] >= 2 def test_incident_not_found_fails_gracefully(self): from app.graphs.postmortem_draft_graph import build_postmortem_draft_graph mock_gw = MockGatewayClient() mock_gw.register("oncall_tool", "incident_get", None, error="Incident not found") graph = build_postmortem_draft_graph() with patch("app.graphs.postmortem_draft_graph.GatewayClient", return_value=mock_gw): result = _run(graph.ainvoke({ "run_id": "gr_test_03", "agent_id": "sofiia", "workspace_id": "ws1", "user_id": "u1", "input": {"incident_id": "inc_nonexistent"}, })) assert result["graph_status"] == "failed" assert "not found" in (result.get("error") or "").lower() def test_missing_incident_id_fails(self): from app.graphs.postmortem_draft_graph import build_postmortem_draft_graph mock_gw = MockGatewayClient() graph = build_postmortem_draft_graph() with patch("app.graphs.postmortem_draft_graph.GatewayClient", return_value=mock_gw): result = _run(graph.ainvoke({ "run_id": "gr_test_04", "agent_id": "sofiia", "workspace_id": "ws1", "user_id": "u1", "input": {}, })) assert result["graph_status"] == "failed" assert "incident_id" in (result.get("validation_error") or "").lower() def test_gateway_error_on_followup_nonfatal(self): """If follow-up append fails, graph still succeeds.""" from app.graphs.postmortem_draft_graph import build_postmortem_draft_graph mock_gw = MockGatewayClient() mock_gw.register("oncall_tool", "incident_get", _INCIDENT_WITH_TRIAGE) mock_gw.register("oncall_tool", "incident_attach_artifact", {"artifact": {"path": "t", "sha256": "x"}}) mock_gw.register("oncall_tool", "incident_append_event", None, error="gateway timeout") graph = build_postmortem_draft_graph() with patch("app.graphs.postmortem_draft_graph.GatewayClient", return_value=mock_gw): result = _run(graph.ainvoke({ "run_id": "gr_test_05", "agent_id": "sofiia", "workspace_id": "ws1", "user_id": "u1", "input": {"incident_id": "inc_20260223_1000_abc123"}, })) assert result["graph_status"] == "succeeded" # followups may be 0 due to error, but graph still completed assert result["result"]["followups_count"] == 0 def test_correlation_ids_present(self): from app.graphs.postmortem_draft_graph import build_postmortem_draft_graph mock_gw = MockGatewayClient() mock_gw.register("oncall_tool", "incident_get", _INCIDENT_WITH_TRIAGE) mock_gw.register("oncall_tool", "incident_attach_artifact", {"artifact": {}}) mock_gw.register("oncall_tool", "incident_append_event", {"event": {}}) graph = build_postmortem_draft_graph() with patch("app.graphs.postmortem_draft_graph.GatewayClient", return_value=mock_gw): _run(graph.ainvoke({ "run_id": "gr_corr_01", "agent_id": "sofiia", "workspace_id": "ws1", "user_id": "u1", "input": {"incident_id": "inc_20260223_1000_abc123"}, })) # All calls should have graph_run_id for call in mock_gw.calls: assert call["graph_run_id"] == "gr_corr_01"