import unittest from detector.converger import AlertConverger from detector.models import Event from detector.notifier import Notifier from detector.storage import InMemoryAlertStore, InMemoryCache, InMemoryMessageBus def make_event(status="firing", host_id="h-1", rule_id="r-1", fired_at=100.0): return Event( event_id=f"e-{status}-{host_id}", host_id=host_id, rule_id=rule_id, metric="cpu_usage", agg_value=95.0, threshold=90.0, operator="gt", status=status, severity="critical", fired_at=fired_at, ) class TestAlertConverger(unittest.TestCase): def setUp(self): self.store = InMemoryAlertStore() self.cache = InMemoryCache() self.bus = InMemoryMessageBus() self.converger = AlertConverger( self.store, self.cache, self.bus, dedup_window="5m", aggregate_window="1m", host_group_resolver=lambda h: "web", ) def test_dedup_suppresses_duplicate(self): first = self.converger.handle_event(make_event()) self.assertIsNotNone(first) second = self.converger.handle_event(make_event()) self.assertIsNone(second) self.assertEqual(len(self.store.list_alerts()[1]), 1) def test_resolve_clears_dedup(self): self.converger.handle_event(make_event(fired_at=100.0)) resolved = self.converger.handle_event(make_event(status="resolved", fired_at=200.0)) self.assertIsNotNone(resolved) self.assertEqual(resolved.status, "resolved") # 去重键已清除,恢复后再次 firing 可以产生新告警 again = self.converger.handle_event(make_event(fired_at=300.0)) self.assertIsNotNone(again) def test_aggregate_same_bucket(self): a1 = self.converger.handle_event(make_event(host_id="h-1", fired_at=100.0)) a2 = self.converger.handle_event(make_event(host_id="h-2", fired_at=110.0)) self.assertIsNotNone(a1) self.assertIsNotNone(a2) alerts = self.store.list_alerts()[1] # 同一 aggregate_key(同 bucket/severity/rule/group)聚合为一条 self.assertEqual(len(alerts), 1) self.assertEqual(alerts[0].count, 2) self.assertIn("h-1", alerts[0].detail["hosts"]) self.assertIn("h-2", alerts[0].detail["hosts"]) def test_publish_to_bus(self): self.converger.handle_event(make_event()) self.assertEqual(len(self.bus.messages), 1) topic, key, value = self.bus.messages[0] self.assertEqual(topic, "alerts.converged") self.assertIn("alert_id", value) def test_notify_channels_propagated(self): notifier = Notifier.memory(["email", "webhook"]) conv = AlertConverger( self.store, self.cache, self.bus, dedup_window="5m", aggregate_window="1m", host_group_resolver=lambda h: "web", notifier=notifier, ) event = make_event() event.notify_channels = ["email"] conv.handle_event(event) self.assertEqual(notifier.sent[0]["channel"], "email") if __name__ == "__main__": unittest.main()