Files
awoooi/apps/api/tests/test_alert_grouping_service.py
OG T 684d6cfb43
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 17m34s
feat(adr-076): 戰術 B 四大 Task 全部完成 — 告警聚合+重試+自動報告
Task 2: AlertGroupingService — Redis 5分鐘滑動視窗,防告警風暴
- apps/api/src/services/alert_grouping_service.py (新增)
- webhooks.py 整合:指紋生成後/LLM前短路子告警
- Threshold=3,Graceful Degradation,16 tests

Task 3: approval_execution.py 執行失敗重試
- MAX_RETRY=2, RETRY_DELAY_SECONDS=30
- _is_transient_error() 瞬態/永久分類,永久錯誤不重試
- Timeline 記錄重試進度,成功後標注重試次數,29 tests

Task 4: report_generation_service.py 自動報告
- 日度巡檢報告:每日 08:00 台北時間,Telegram SRE 群組推送
- Postmortem:Incident resolved + duration > 10 分鐘自動觸發
- main.py lifespan 掛載 run_daily_report_loop(),30 tests

測試: 600 → 675 通過 (+75),0 failed

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-04-14 14:39:14 +08:00

138 lines
5.0 KiB
Python
Raw 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
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:")