Files
awoooi/apps/api/tests/test_phase25_auto_harvesting.py
Your Name 341c3b6523
All checks were successful
Code Review / ai-code-review (push) Successful in 10s
CD Pipeline / tests (push) Successful in 1m13s
CD Pipeline / build-and-deploy (push) Successful in 3m22s
CD Pipeline / post-deploy-checks (push) Successful in 1m28s
fix(telegram): format governance and runbook alerts
2026-05-07 00:58:20 +08:00

181 lines
7.3 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.
"""
Phase 25: Auto-Harvesting 測試
================================
ADR-052 / Phase 25 P1: NemotronRunbookGenerator + AntiPattern Gate
測試策略: Source code inspection (禁止 Mock禁止 import 模組)
- 驗證 runbook_generator.py 實作結構
- 驗證 auto_repair_service.py fire-and-forget 模式
- 驗證 knowledge.py 模型欄位
- 驗證 knowledge_service.py check_anti_pattern 邏輯
建立時間: 2026-04-05 (台北時區)
建立者: Claude Code (Phase 25 Auto-Harvesting 測試)
"""
from pathlib import Path
from types import SimpleNamespace
# Source file paths
_BASE = Path(__file__).parent.parent / "src"
_RUNBOOK_GEN = _BASE / "services" / "runbook_generator.py"
_AUTO_REPAIR = _BASE / "services" / "auto_repair_service.py"
_PLAYBOOK = _BASE / "models" / "playbook.py"
_KNOWLEDGE = _BASE / "models" / "knowledge.py"
_KNOWLEDGE_SVC = _BASE / "services" / "knowledge_service.py"
# =============================================================================
# TestRunbookGeneratorModule — 模組存在與結構
# =============================================================================
class TestRunbookGeneratorModule:
"""驗證 runbook_generator.py 模組存在並有正確結構"""
def test_runbook_generator_module_exists(self):
"""src/services/runbook_generator.py 存在"""
assert _RUNBOOK_GEN.exists(), f"找不到 {_RUNBOOK_GEN}"
def test_runbook_generator_class_exists(self):
"""NemotronRunbookGenerator class 已定義"""
source = _RUNBOOK_GEN.read_text()
assert "class NemotronRunbookGenerator" in source
def test_generate_runbook_method_exists(self):
"""generate_runbook() 方法存在"""
source = _RUNBOOK_GEN.read_text()
assert "async def generate_runbook" in source or "def generate_runbook" in source
def test_generate_anti_pattern_method_exists(self):
"""generate_anti_pattern() 方法存在"""
source = _RUNBOOK_GEN.read_text()
assert "async def generate_anti_pattern" in source or "def generate_anti_pattern" in source
def test_runbook_uses_auto_runbook_type(self):
"""runbook_generator.py 使用 'auto_runbook' entry type"""
source = _RUNBOOK_GEN.read_text()
assert "auto_runbook" in source
def test_anti_pattern_uses_anti_pattern_type(self):
"""runbook_generator.py 使用 'anti_pattern' entry type"""
source = _RUNBOOK_GEN.read_text()
assert "anti_pattern" in source
def test_runbook_status_is_draft(self):
"""成功修復 → AUTO_RUNBOOK 狀態為 DRAFT需人工審核"""
source = _RUNBOOK_GEN.read_text()
assert "DRAFT" in source or "draft" in source
def test_anti_pattern_status_is_published(self):
"""失敗修復 → ANTI_PATTERN 狀態為 PUBLISHED直接發布"""
source = _RUNBOOK_GEN.read_text()
assert "PUBLISHED" in source or "published" in source
def test_runbook_uses_nvidia_chat(self):
"""runbook_generator.py 使用 nvidia provider"""
source = _RUNBOOK_GEN.read_text()
assert "nvidia" in source
def test_minimal_fallback_exists(self):
"""Nemotron 失敗時有 fallback 邏輯"""
source = _RUNBOOK_GEN.read_text()
assert "fallback" in source
def test_runbook_review_card_is_structured_html(self):
"""Telegram Runbook 審核訊息必須是可掃描治理卡片,不直接傾倒 Markdown 原文"""
from src.services.runbook_generator import format_runbook_review_card
incident = SimpleNamespace(
incident_id="INC-20260506-E54736",
affected_services=["node-exporter-110"],
)
content = (
"## 症狀描述\n"
"Incident INC-20260506-E54736受影響服務node-exporter-110\n\n"
"## 執行步驟\n"
"- Step 1: ssh{host} echo '=== LOAD ===' -> FAILED: Unsupported scheme\n"
)
card = format_runbook_review_card(incident, "ff5eff01-7243-44bf", content)
assert "<b>RUNBOOK REVIEW待審核</b>" in card
assert "<code>INC-20260506-E54736</code>" in card
assert "🧾 <b>內容摘要</b>" in card
assert "placeholder 或不支援的執行步驟" in card
assert "## 症狀描述" not in card
assert "ssh{host}" not in card
# =============================================================================
# TestAutoRepairService — fire-and-forget 與 GC 防洩漏
# =============================================================================
class TestAutoRepairService:
"""驗證 auto_repair_service.py 的 fire-and-forget 模式與 GC 防洩漏"""
def test_fire_and_forget_pattern(self):
"""auto_repair_service.py 使用 asyncio.create_task() 實現 fire-and-forget"""
source = _AUTO_REPAIR.read_text()
assert "create_task" in source
def test_pending_tasks_gc_prevention(self):
"""auto_repair_service.py 持有 _pending_tasks 防止 Task 被 GC 回收"""
source = _AUTO_REPAIR.read_text()
assert "_pending_tasks" in source
def test_anti_pattern_gate_in_auto_repair(self):
"""auto_repair_service.py 有 check_anti_pattern 攔截呼叫AntiPattern Gate"""
source = _AUTO_REPAIR.read_text()
assert "check_anti_pattern" in source
# =============================================================================
# TestKnowledgeModels — 知識庫模型結構
# =============================================================================
class TestKnowledgeModels:
"""驗證 models/knowledge.py 的 EntryType 與 KnowledgeEntry 欄位"""
def test_auto_runbook_entrytype_value(self):
"""EntryType 有 AUTO_RUNBOOK = 'auto_runbook'"""
source = _KNOWLEDGE.read_text()
assert 'AUTO_RUNBOOK' in source
assert '"auto_runbook"' in source or "'auto_runbook'" in source
def test_anti_pattern_entrytype_value(self):
"""EntryType 有 ANTI_PATTERN = 'anti_pattern'"""
source = _KNOWLEDGE.read_text()
assert 'ANTI_PATTERN' in source
assert '"anti_pattern"' in source or "'anti_pattern'" in source
def test_knowledge_entry_has_symptoms_hash(self):
"""KnowledgeEntry 有 symptoms_hash 欄位AntiPattern 閉環用)"""
source = _KNOWLEDGE.read_text()
assert "symptoms_hash" in source
# =============================================================================
# TestPlaybookModel — 症狀 hash 計算
# =============================================================================
class TestPlaybookModel:
"""驗證 models/playbook.py 的 compute_hash 方法"""
def test_symptom_hash_compute_method(self):
"""SymptomPattern 有 compute_hash() 方法"""
source = _PLAYBOOK.read_text()
assert "compute_hash" in source
# =============================================================================
# TestKnowledgeService — check_anti_pattern 查詢
# =============================================================================
class TestKnowledgeService:
"""驗證 knowledge_service.py 的 check_anti_pattern 方法"""
def test_check_anti_pattern_in_knowledge_service(self):
"""knowledge_service.py 有 check_anti_pattern 方法"""
source = _KNOWLEDGE_SVC.read_text()
assert "check_anti_pattern" in source