""" Tests for /v1/alerts/dashboard and /v1/incidents/open endpoints. Uses MemoryAlertStore + MemoryIncidentStore injected directly. """ import os import sys from datetime import datetime from pathlib import Path from unittest.mock import patch, MagicMock ROOT = Path(__file__).resolve().parent.parent ROUTER = ROOT / "services" / "router" if str(ROUTER) not in sys.path: sys.path.insert(0, str(ROUTER)) def _ingest_n(store, n=3): from alert_ingest import ingest_alert refs = [] for i in range(n): r = ingest_alert(store, { "source": "monitor@node1", "service": "gateway", "env": "prod", "severity": "P1", "kind": "slo_breach", "title": f"Alert {i}", "summary": f"Issue {i}", "started_at": datetime.utcnow().isoformat(), "labels": {"fingerprint": f"fp{i}"}, "metrics": {}, }) refs.append(r["alert_ref"]) return refs class TestDashboardCountsAndSigs: def setup_method(self): from alert_store import MemoryAlertStore, set_alert_store self.store = MemoryAlertStore() set_alert_store(self.store) def teardown_method(self): from alert_store import set_alert_store set_alert_store(None) def test_counts_reflect_state(self): refs = _ingest_n(self.store, 3) self.store.claim_next_alerts(limit=1, owner="loop") self.store.mark_acked(refs[2], "test") counts = self.store.dashboard_counts() assert counts["new"] >= 1 assert counts["processing"] >= 1 assert counts["acked"] >= 1 assert counts["failed"] == 0 def test_top_signatures_sorted_by_occurrences(self): from alert_ingest import ingest_alert # Ingest same fingerprint 5 times → occurrences should be 5 for _ in range(5): ingest_alert(self.store, { "source": "test", "service": "gateway", "env": "prod", "severity": "P1", "kind": "slo_breach", "title": "High latency", "summary": "latency spike", "started_at": datetime.utcnow().isoformat(), "labels": {"fingerprint": "repeated_fp"}, "metrics": {}, }) top = self.store.top_signatures() assert len(top) >= 1 assert top[0]["occurrences"] >= 5 assert top[0]["service"] == "gateway" def test_latest_alerts_in_result(self): refs = _ingest_n(self.store, 3) latest = self.store.list_alerts({"window_minutes": 60}, limit=50) assert len(latest) >= 3 def test_counts_all_zeros_empty_store(self): from alert_store import MemoryAlertStore, set_alert_store empty_store = MemoryAlertStore() set_alert_store(empty_store) counts = empty_store.dashboard_counts() assert all(v == 0 for v in counts.values()) class TestIncidentsOpenEndpoint: """Test /v1/incidents/open logic directly (no HTTP).""" def setup_method(self): from incident_store import MemoryIncidentStore, set_incident_store self.istore = MemoryIncidentStore() set_incident_store(self.istore) def teardown_method(self): from incident_store import set_incident_store set_incident_store(None) def _create_incident(self, service="gateway", status="open", severity="P1"): return self.istore.create_incident({ "service": service, "env": "prod", "severity": severity, "title": f"{service} incident", "summary": "test", "started_at": datetime.utcnow().isoformat(), "created_by": "test", }) def test_list_open_incidents(self): inc1 = self._create_incident("gateway") inc2 = self._create_incident("router") self.istore.close_incident(inc2["id"], datetime.utcnow().isoformat(), "resolved") all_incs = self.istore.list_incidents({}, limit=100) open_only = [i for i in all_incs if i.get("status") in ("open", "mitigating")] assert len(open_only) >= 1 assert all(i["status"] in ("open", "mitigating") for i in open_only) def test_filter_by_service(self): self._create_incident("gateway") self._create_incident("router") all_incs = self.istore.list_incidents({}, limit=100) gw_only = [i for i in all_incs if i["service"] == "gateway"] assert len(gw_only) >= 1 for i in gw_only: assert i["service"] == "gateway" class TestSignatureStateDashboardIntegration: """Verify signature state store integrates with claims.""" def setup_method(self): from signature_state_store import MemorySignatureStateStore, set_signature_state_store self.sig_store = MemorySignatureStateStore() set_signature_state_store(self.sig_store) def teardown_method(self): from signature_state_store import set_signature_state_store set_signature_state_store(None) def test_mark_and_check(self): sig = "aabbccdd" * 4 assert self.sig_store.should_run_triage(sig) is True self.sig_store.mark_triage_run(sig) assert self.sig_store.should_run_triage(sig, cooldown_minutes=15) is False def test_state_has_expected_fields(self): sig = "sig12345" self.sig_store.mark_alert_seen(sig) self.sig_store.mark_triage_run(sig) state = self.sig_store.get_state(sig) assert "last_triage_at" in state assert "last_alert_at" in state assert "triage_count_24h" in state