""" AlertGroupingService 單元測試 ============================== ADR-076: 告警聚合引擎 — 告警風暴防禦 🔴🔴 遵循「禁止 Mock 測試鐵律」 - build_group_key / GroupingResult 邏輯測試:純 Python,無需 Redis - Redis 整合部分標記 @pytest.mark.integration,正常 CI 跳過 建立: 2026-04-14 (台北時區) Claude Haiku 4.5 """ import pytest from src.services.alert_grouping_service import AlertGroupingService, GroupingResult class TestBuildGroupKey: """測試聚合分組 key 生成邏輯""" def test_basic_key(self): """基本 alertname + namespace → group_key""" key = AlertGroupingService.build_group_key("PodCrashLoopBackOff", "awoooi-prod") assert key == "PodCrashLoopBackOff:awoooi-prod" def test_strips_numeric_suffix(self): """帶數字後綴的 alertname 應取前綴""" key = AlertGroupingService.build_group_key("PodCrashLoopBackOff-3", "awoooi-prod") assert key == "PodCrashLoopBackOff:awoooi-prod" def test_strips_long_numeric_suffix(self): """帶長數字後綴的 alertname 應取前綴""" key = AlertGroupingService.build_group_key("HostHighCpuLoad-1234567", "default") assert key == "HostHighCpuLoad:default" def test_same_prefix_same_key(self): """相同前綴、相同 namespace → 相同 group_key(聚合生效)""" key1 = AlertGroupingService.build_group_key("PodOOMKilled-1", "awoooi-prod") key2 = AlertGroupingService.build_group_key("PodOOMKilled-2", "awoooi-prod") key3 = AlertGroupingService.build_group_key("PodOOMKilled-3", "awoooi-prod") assert key1 == key2 == key3 def test_different_namespace_different_key(self): """相同 alertname、不同 namespace → 不同 group_key""" key1 = AlertGroupingService.build_group_key("PodCrash", "awoooi-prod") key2 = AlertGroupingService.build_group_key("PodCrash", "awoooi-staging") assert key1 != key2 def test_different_alertname_different_key(self): """不同 alertname、相同 namespace → 不同 group_key""" key1 = AlertGroupingService.build_group_key("PodCrash", "awoooi-prod") key2 = AlertGroupingService.build_group_key("HostHighCpu", "awoooi-prod") assert key1 != key2 def test_empty_namespace(self): """namespace 為空字串時應正常處理""" key = AlertGroupingService.build_group_key("PodCrash", "") assert key == "PodCrash:" def test_no_suffix_unchanged(self): """無數字後綴的 alertname 應保持不變""" key = AlertGroupingService.build_group_key("HostHighCpuLoad", "default") assert key == "HostHighCpuLoad:default" class TestGroupingResultDataclass: """測試 GroupingResult dataclass""" def test_child_alert(self): """子告警:is_grouped=True, is_parent=False""" result = GroupingResult( is_grouped=True, group_key="PodCrash:awoooi-prod", count=5, parent_fingerprint="fp-001", is_parent=False, ) assert result.is_grouped is True assert result.is_parent is False assert result.count == 5 def test_parent_alert(self): """父告警:is_grouped=False, is_parent=True""" result = GroupingResult( is_grouped=False, group_key="PodCrash:awoooi-prod", count=1, parent_fingerprint="fp-001", is_parent=True, ) assert result.is_grouped is False assert result.is_parent is True def test_below_threshold_not_grouped(self): """未達閾值:count=2, threshold=3 → is_grouped=False""" result = GroupingResult( is_grouped=False, group_key="PodCrash:awoooi-prod", count=2, parent_fingerprint="fp-001", is_parent=False, ) assert result.is_grouped is False def test_group_key_format(self): """group_key 格式應為 {alertname_prefix}:{namespace}""" result = GroupingResult( is_grouped=True, group_key="PodOOMKilled:awoooi-prod", count=4, parent_fingerprint=None, is_parent=False, ) assert ":" in result.group_key parts = result.group_key.split(":") assert len(parts) == 2 class TestAlertGroupingServiceConstants: """測試服務常量設定""" def test_window_seconds(self): """視窗應為 5 分鐘 (300 秒)""" assert AlertGroupingService.WINDOW_SECONDS == 300 def test_group_threshold(self): """聚合閾值應為 3""" assert AlertGroupingService.GROUP_THRESHOLD == 3 def test_ttl_seconds(self): """TTL 應長於視窗""" assert AlertGroupingService.TTL_SECONDS > AlertGroupingService.WINDOW_SECONDS def test_redis_key_prefix(self): """Redis key 前綴應符合規範""" assert AlertGroupingService.PREFIX_WINDOW.startswith("alert_group:") assert AlertGroupingService.PREFIX_META.startswith("alert_group:")