Files
awoooi/apps/api/tests/test_alert_grouping_service.py
Your Name 6ac61ab6d7
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 1m2s
CD Pipeline / build-and-deploy (push) Successful in 3m39s
CD Pipeline / post-deploy-checks (push) Successful in 1m18s
fix(telegram): digest grouped alert storms
2026-05-07 01:51:31 +08:00

152 lines
5.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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,
_decode_redis_member,
)
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_second_alert_is_now_grouped(self):
"""門檻為 2第一張保留主卡第二張同組告警開始收斂。"""
result = GroupingResult(
is_grouped=True,
group_key="PodCrash:awoooi-prod",
count=2,
parent_fingerprint="fp-001",
is_parent=False,
)
assert result.is_grouped is True
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 TestRedisMemberDecode:
"""測試 Redis zrange member 正規化。"""
def test_decode_bytes_member(self):
assert _decode_redis_member(b"fp-parent", "fallback") == "fp-parent"
def test_decode_none_uses_fallback(self):
assert _decode_redis_member(None, "fallback") == "fallback"
class TestAlertGroupingServiceConstants:
"""測試服務常量設定"""
def test_window_seconds(self):
"""視窗應為 5 分鐘 (300 秒)"""
assert AlertGroupingService.WINDOW_SECONDS == 300
def test_group_threshold(self):
"""聚合閾值應為 2保留第一張主卡第二張同組告警開始收斂。"""
assert AlertGroupingService.GROUP_THRESHOLD == 2
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:")