Files
awoooi/apps/api/tests/test_classify_alert_early.py
OG T 1074936e54
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 2m38s
fix(classify): backup/heartbeat severity=warning/critical 告警恢復告警卡片格式
根因:classify_alert_early() backup 規則無 severity 條件,導致
VeleroBackupFailed / HostBackupFailed (warning/critical) 被分為 TYPE-1
(純資訊無按鈕),告警卡片格式遺失。

修復:
- backup/heartbeat 關鍵字只在 severity=info/none 才命中 TYPE-1
- severity=warning/critical 的 backup 告警走正確 prefix 規則
  (Velero→kubernetes TYPE-3, HostBackup→infrastructure TYPE-3)
- Watchdog (severity=none) 由 severity 規則先命中,維持 TYPE-1
- 補強測試:25 cases,含 VeleroBackupFailed critical → kubernetes TYPE-3

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 15:24:00 +08:00

166 lines
6.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.
"""
classify_alert_early() 分類函數單元測試 — ADR-073 Phase 2-2
覆蓋 7 條分類規則的正常路徑與邊界情況:
config_drift, info, backup, infrastructure, kubernetes, database, general
2026-04-12 ogt (ADR-073 P2-2 測試補強)
"""
import pytest
from src.services.incident_service import classify_alert_early
# --------------------------------------------------------------------------- #
# TYPE-4D: Config Drift
# --------------------------------------------------------------------------- #
class TestConfigDrift:
def test_configuration_drift(self):
ac, nt = classify_alert_early("ConfigurationDrift", "critical", {})
assert nt == "TYPE-4D"
assert ac == "config_drift"
def test_kube_config_drift(self):
ac, nt = classify_alert_early("KubeConfigDrift", "warning", {})
assert nt == "TYPE-4D"
assert ac == "config_drift"
def test_config_drift_case_sensitive(self):
# 不在白名單裡的變體 → 不應命中 config_drift
ac, nt = classify_alert_early("configurationdrift", "critical", {})
assert ac != "config_drift"
# --------------------------------------------------------------------------- #
# TYPE-1: Info / Heartbeat
# --------------------------------------------------------------------------- #
class TestInfoAlerts:
def test_severity_info(self):
ac, nt = classify_alert_early("SomeAlert", "info", {})
assert nt == "TYPE-1"
assert ac == "info"
def test_severity_none(self):
ac, nt = classify_alert_early("SomeAlert", "none", {})
assert nt == "TYPE-1"
assert ac == "info"
def test_backup_keyword_info_only(self):
# severity=info → severity 規則先命中TYPE-1
ac, nt = classify_alert_early("BackupJobComplete", "info", {})
assert nt == "TYPE-1"
def test_backup_keyword_warning_not_type1(self):
# BackupJobFailed severity=warning → 繼續走 prefix 規則,不應是 TYPE-1
ac, nt = classify_alert_early("BackupJobFailed", "warning", {})
assert nt == "TYPE-3"
def test_watchdog_heartbeat(self):
# Watchdog (Alertmanager 心跳) severity=none → severity 規則先命中TYPE-1
ac, nt = classify_alert_early("Watchdog", "none", {})
assert nt == "TYPE-1"
def test_backup_critical_not_type1(self):
# critical backup 告警應走各自 prefix不是純資訊
ac, nt = classify_alert_early("BACKUP_MISSING", "critical", {})
assert nt == "TYPE-3"
# --------------------------------------------------------------------------- #
# TYPE-3: Infrastructure (Docker / Host)
# --------------------------------------------------------------------------- #
class TestInfrastructure:
def test_docker_prefix(self):
ac, nt = classify_alert_early("DockerContainerOOM", "critical", {})
assert nt == "TYPE-3"
assert ac == "infrastructure"
def test_host_prefix(self):
ac, nt = classify_alert_early("HostHighCpuLoad", "warning", {})
assert nt == "TYPE-3"
assert ac == "infrastructure"
def test_docker_restart(self):
ac, nt = classify_alert_early("DockerContainerRestarting", "warning", {})
assert ac == "infrastructure"
# --------------------------------------------------------------------------- #
# TYPE-3: Kubernetes
# --------------------------------------------------------------------------- #
class TestKubernetes:
@pytest.mark.parametrize("alertname", [
"KubePodCrashLooping",
"PodHighMemory",
"DeploymentReplicasMismatch",
"NodeNotReady",
"ArgoCDSyncFailed",
])
def test_k8s_prefixes(self, alertname):
ac, nt = classify_alert_early(alertname, "critical", {})
assert nt == "TYPE-3"
assert ac == "kubernetes"
def test_velero_backup_failed_is_kubernetes(self):
# VeleroBackupFailed severity=critical → backup 規則不命中,走 Velero prefix → kubernetes TYPE-3
ac, nt = classify_alert_early("VeleroBackupFailed", "critical", {})
assert nt == "TYPE-3"
assert ac == "kubernetes"
def test_velero_backup_success_info_is_type1(self):
# VeleroBackupSuccess severity=info → TYPE-1
ac, nt = classify_alert_early("VeleroBackupSuccess", "info", {})
assert nt == "TYPE-1"
# --------------------------------------------------------------------------- #
# TYPE-3: Database
# --------------------------------------------------------------------------- #
class TestDatabase:
def test_postgres(self):
ac, nt = classify_alert_early("PostgresDown", "critical", {})
assert nt == "TYPE-3"
assert ac == "database"
def test_redis(self):
ac, nt = classify_alert_early("RedisMemoryHigh", "warning", {})
assert nt == "TYPE-3"
assert ac == "database"
# --------------------------------------------------------------------------- #
# TYPE-3: General (fallback)
# --------------------------------------------------------------------------- #
class TestGeneral:
def test_unknown_alert(self):
ac, nt = classify_alert_early("SomeUnknownAlert", "warning", {})
assert nt == "TYPE-3"
assert ac == "general"
def test_empty_alertname(self):
ac, nt = classify_alert_early("", "warning", {})
assert nt == "TYPE-3"
assert ac == "general"
# --------------------------------------------------------------------------- #
# 優先順序驗證 — config_drift 和 info 應優先於 prefix 規則
# --------------------------------------------------------------------------- #
class TestPriority:
def test_config_drift_beats_severity(self):
# ConfigurationDrift 即使 severity=warning 也應命中 config_drift
ac, nt = classify_alert_early("ConfigurationDrift", "warning", {})
assert ac == "config_drift"
def test_info_severity_beats_docker_prefix(self):
# Docker 前綴 + severity=info → info 規則先命中
ac, nt = classify_alert_early("DockerContainerOOM", "info", {})
assert ac == "info"