Files
awoooi/apps/api/tests/test_phase25_drift_detection.py
OG T 4bc4757fdc test(phase25): Phase 25 P1/P2 source code inspection tests (36 tests)
- test_phase25_auto_harvesting.py: 18 tests for NemotronRunbookGenerator,
  AntiPattern gate, fire-and-forget pattern, symptoms_hash
- test_phase25_drift_detection.py: 18 tests for DriftDetector, NemotronDriftInterpreter
  (read-only), DriftRemediator, local fallback chain for DIAGNOSE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 00:14:50 +08:00

159 lines
6.1 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: Config Drift Detection 測試
=======================================
ADR-052 / Phase 25 P2: DriftDetector + NemotronDriftInterpreter + DriftRemediator
測試策略: Source code inspection (禁止 Mock禁止 import 模組)
- 驗證 drift_detector.py 結構DriftDetector, GitStateReader, K8sStateReader
- 驗證 drift_interpreter.py (NemotronDriftInterpreter, read-only)
- 驗證 drift_remediator.py (kubectl apply / git push)
- 驗證 api/v1/drift.py 端點
- 驗證 ai_router.py DIAGNOSE local-only 鏈路
建立時間: 2026-04-05 (台北時區)
建立者: Claude Code (Phase 25 Drift Detection 測試)
"""
from pathlib import Path
# Source file paths
_BASE = Path(__file__).parent.parent / "src"
_DETECTOR = _BASE / "services" / "drift_detector.py"
_ANALYZER = _BASE / "services" / "drift_analyzer.py"
_INTERPRETER = _BASE / "services" / "drift_interpreter.py"
_REMEDIATOR = _BASE / "services" / "drift_remediator.py"
_DRIFT_API = _BASE / "api" / "v1" / "drift.py"
_AI_ROUTER = _BASE / "services" / "ai_router.py"
# =============================================================================
# TestDriftDetector — 偵測器結構
# =============================================================================
class TestDriftDetector:
"""驗證 drift_detector.py 的核心結構"""
def test_drift_detector_module_exists(self):
"""src/services/drift_detector.py 存在"""
assert _DETECTOR.exists(), f"找不到 {_DETECTOR}"
def test_drift_detector_class_exists(self):
"""DriftDetector class 已定義"""
source = _DETECTOR.read_text()
assert "class DriftDetector" in source
def test_drift_scan_method_exists(self):
"""DriftDetector 有 scan() 方法"""
source = _DETECTOR.read_text()
assert "async def scan" in source or "def scan" in source
def test_git_state_reader_exists(self):
"""GitStateReader class 已定義"""
source = _DETECTOR.read_text()
assert "class GitStateReader" in source
def test_k8s_state_reader_exists(self):
"""K8sStateReader class 已定義"""
source = _DETECTOR.read_text()
assert "class K8sStateReader" in source
def test_drift_level_high_defined(self):
"""DriftLevel.HIGH 已定義"""
source = _DETECTOR.read_text()
assert "HIGH" in source
def test_drift_level_medium_defined(self):
"""DriftLevel.MEDIUM 已定義"""
source = _DETECTOR.read_text()
assert "MEDIUM" in source
# =============================================================================
# TestDriftInterpreter — 解讀器 (read-only不執行修復)
# =============================================================================
class TestDriftInterpreter:
"""驗證 drift_interpreter.py 的 NemotronDriftInterpreter且不含 kubectl apply"""
def test_drift_interpreter_module_exists(self):
"""src/services/drift_interpreter.py 存在"""
assert _INTERPRETER.exists(), f"找不到 {_INTERPRETER}"
def test_drift_interpreter_class_exists(self):
"""NemotronDriftInterpreter class 已定義"""
source = _INTERPRETER.read_text()
assert "class NemotronDriftInterpreter" in source
def test_drift_interpreter_no_fix_commands(self):
"""drift_interpreter.py 不含 'kubectl apply'(只分析,不執行修復)"""
source = _INTERPRETER.read_text()
assert "kubectl apply" not in source, (
"drift_interpreter.py 不應含 kubectl apply — 解讀器只做 read-only 分析"
)
# =============================================================================
# TestDriftRemediator — 修復器 (kubectl apply / git push)
# =============================================================================
class TestDriftRemediator:
"""驗證 drift_remediator.py 的 rollback 與 adopt 操作"""
def test_drift_remediator_module_exists(self):
"""src/services/drift_remediator.py 存在"""
assert _REMEDIATOR.exists(), f"找不到 {_REMEDIATOR}"
def test_drift_remediator_rollback_uses_kubectl(self):
"""rollback() 使用 kubectl apply 覆蓋回 Git 狀態"""
source = _REMEDIATOR.read_text()
assert "kubectl apply" in source
def test_drift_remediator_adopt_uses_git_push(self):
"""adopt() 使用 git push 承認變更並更新 Git"""
source = _REMEDIATOR.read_text()
assert "git push" in source
# =============================================================================
# TestDriftApi — API 端點
# =============================================================================
class TestDriftApi:
"""驗證 api/v1/drift.py 端點路徑"""
def test_drift_api_router_exists(self):
"""src/api/v1/drift.py 存在"""
assert _DRIFT_API.exists(), f"找不到 {_DRIFT_API}"
def test_drift_scan_endpoint_exists(self):
"""drift.py 有 /scan 端點"""
source = _DRIFT_API.read_text()
assert '"/scan"' in source or "'/scan'" in source
def test_drift_reports_endpoint_exists(self):
"""drift.py 有 /reports 端點"""
source = _DRIFT_API.read_text()
assert '"/reports"' in source or "'/reports'" in source
# =============================================================================
# TestAiRouterDiagnose — DIAGNOSE local-only 鏈路
# =============================================================================
class TestAiRouterDiagnose:
"""驗證 ai_router.py 的 DIAGNOSE 隱私邊界 local-only fallback"""
def test_local_fallback_chain_exists(self):
"""ai_router.py 有 _local_fallback_chainDIAGNOSE 隱私邊界用)"""
source = _AI_ROUTER.read_text()
assert "_local_fallback_chain" in source
def test_diagnose_uses_local_chain(self):
"""DIAGNOSE task type 使用 local fallback chain"""
source = _AI_ROUTER.read_text()
assert "DIAGNOSE" in source
# DIAGNOSE 分支必須引用 _local_fallback_chain
idx_diagnose = source.find("DIAGNOSE")
remaining = source[idx_diagnose:]
assert "_local_fallback_chain" in remaining