feat(adr-075): 修復 Telegram 動態按鈕 4 個斷點 + 新增 7 種告警分類
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled

斷點 A: decision_manager 提取 alert_category/notification_type 傳入 send_approval_card
斷點 B: send_approval_card 新增參數並傳遞至 _build_inline_keyboard
斷點 C: 互動型通知 (TYPE-3/4/4D/8M) 禁止發 SRE 群組,防 nonce 洩漏
斷點 D: _CATEGORY_BUTTONS k8s_workload → kubernetes + 新增 6 類按鈕組

classify_alert_early 新增: alertchain_health, flywheel_health, storage,
devops_tool, external_site, ssl_cert, host_resource (從 infrastructure 分離)
Test: 52 classify + 664 total passed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-12 18:35:56 +08:00
parent db282cd0e9
commit 2cef2098d3
4 changed files with 226 additions and 12 deletions

View File

@@ -76,17 +76,144 @@ 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"
assert ac == "infrastructure" # Docker → 保留 infrastructure
def test_docker_restart(self):
ac, nt = classify_alert_early("DockerContainerRestarting", "warning", {})
assert ac == "infrastructure"
# ADR-075: Host* 從 infrastructure 分離為 host_resource
def test_host_prefix_is_host_resource(self):
ac, nt = classify_alert_early("HostHighCpuLoad", "warning", {})
assert nt == "TYPE-3"
assert ac == "host_resource"
def test_host_down(self):
ac, nt = classify_alert_early("HostDown", "critical", {})
assert ac == "host_resource"
def test_host_memory(self):
ac, nt = classify_alert_early("HostOutOfMemory", "warning", {})
assert ac == "host_resource"
def test_host_disk(self):
ac, nt = classify_alert_early("HostOutOfDiskSpace", "warning", {})
assert ac == "host_resource"
# --------------------------------------------------------------------------- #
# ADR-075: alertchain_health (TYPE-8M)
# --------------------------------------------------------------------------- #
class TestAlertchainHealth:
@pytest.mark.parametrize("alertname", [
"AlertChainBroken_Alertmanager",
"AlertChainBroken_Sentry",
"NoAlertsReceived2Hours",
"AlertChainUnhealthy",
])
def test_alertchain_alerts(self, alertname):
ac, nt = classify_alert_early(alertname, "critical", {})
assert ac == "alertchain_health"
assert nt == "TYPE-8M"
def test_alertchain_beats_severity_info(self):
# 即使 severity=infoAlertChainBroken 也必須是 alertchain_health
ac, nt = classify_alert_early("AlertChainBroken_Alertmanager", "info", {})
assert ac == "alertchain_health"
assert nt == "TYPE-8M"
# --------------------------------------------------------------------------- #
# ADR-075: flywheel_health (TYPE-8M)
# --------------------------------------------------------------------------- #
class TestFlywheelHealth:
def test_auto_repair_low_success(self):
ac, nt = classify_alert_early("AutoRepairLowSuccessRate", "warning", {})
assert ac == "flywheel_health"
assert nt == "TYPE-8M"
def test_permanent_fix_required(self):
ac, nt = classify_alert_early("PermanentFixRequired", "warning", {})
assert ac == "flywheel_health"
assert nt == "TYPE-8M"
def test_flywheel_prefix(self):
ac, nt = classify_alert_early("FlywheelPlaybookZero", "critical", {})
assert ac == "flywheel_health"
assert nt == "TYPE-8M"
def test_flywheel_beats_severity_info(self):
ac, nt = classify_alert_early("AutoRepairLowSuccessRate", "info", {})
assert ac == "flywheel_health"
assert nt == "TYPE-8M"
# --------------------------------------------------------------------------- #
# ADR-075: storage (TYPE-3)
# --------------------------------------------------------------------------- #
class TestStorage:
def test_minio_down(self):
ac, nt = classify_alert_early("MinIODown", "critical", {})
assert ac == "storage"
assert nt == "TYPE-3"
# --------------------------------------------------------------------------- #
# ADR-075: devops_tool (TYPE-3)
# --------------------------------------------------------------------------- #
class TestDevopsTool:
@pytest.mark.parametrize("alertname", [
"OpenClawDown",
"SignOzDown",
"GiteaDown",
"HarborDown",
"SentryDown",
"AlertmanagerDown",
"KaliScannerDown",
"GiteaCIPipelineFailed",
])
def test_devops_tools(self, alertname):
ac, nt = classify_alert_early(alertname, "critical", {})
assert ac == "devops_tool"
assert nt == "TYPE-3"
# --------------------------------------------------------------------------- #
# ADR-075: external_site (TYPE-3)
# --------------------------------------------------------------------------- #
class TestExternalSite:
@pytest.mark.parametrize("alertname", [
"MoWoooWorkDown",
"TsenyangWebsiteDown",
"StockWoooWorkDown",
"BitanWoooWorkDown",
])
def test_external_sites(self, alertname):
ac, nt = classify_alert_early(alertname, "critical", {})
assert ac == "external_site"
assert nt == "TYPE-3"
# --------------------------------------------------------------------------- #
# ADR-075: ssl_cert (TYPE-3)
# --------------------------------------------------------------------------- #
class TestSslCert:
def test_external_site_ssl(self):
ac, nt = classify_alert_early("ExternalSiteSSLExpiringSoon", "warning", {})
assert ac == "ssl_cert"
assert nt == "TYPE-3"
def test_tls_cert(self):
ac, nt = classify_alert_early("TLSCertExpiryCritical", "critical", {})
assert ac == "ssl_cert"
assert nt == "TYPE-3"
# --------------------------------------------------------------------------- #
# TYPE-3: Kubernetes