- Python: ruff --fix 修復 280 個 lint 錯誤 - lewooogo-core: src/ 目錄未追蹤,導致 CI eslint 失敗 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
180 lines
5.9 KiB
Python
Executable File
180 lines
5.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Phase 6.3 聚合測試腳本
|
||
=======================
|
||
|
||
功能:
|
||
1. 連續打入 3 筆「同源但不同名」的測試告警
|
||
2. 證明這 3 筆告警被成功「聚合」進同一個 Incident 的 signals 陣列中
|
||
3. 驗證 affected_services 有被正確填入
|
||
|
||
使用方式:
|
||
cd apps/api
|
||
python scripts/test_phase63_aggregation.py
|
||
|
||
預期結果:
|
||
- 3 筆告警全部聚合到 1 個 Incident
|
||
- signals 陣列長度 = 3
|
||
- affected_services 包含 "payment-service"
|
||
"""
|
||
|
||
import asyncio
|
||
from datetime import datetime
|
||
|
||
import httpx
|
||
|
||
# API 端點
|
||
API_BASE = "http://localhost:8000"
|
||
SIGNALS_ENDPOINT = f"{API_BASE}/api/v1/webhooks/signals"
|
||
|
||
# 測試告警: 同 namespace + 同 target,不同 alert_name
|
||
# 模擬: payment-service 發生一連串相關問題
|
||
# 測試告警: 同 namespace + 同 target,不同 alert_name
|
||
# 模擬: payment-service 發生一連串相關問題
|
||
# 注意: severity 只能是 info | warning | critical (SignalPayload 定義)
|
||
TEST_ALERTS = [
|
||
{
|
||
"alert_name": "PaymentServiceHighLatency",
|
||
"severity": "warning",
|
||
"source": "prometheus",
|
||
"namespace": "payment-prod",
|
||
"target": "payment-service",
|
||
"fingerprint": "fp_latency_001",
|
||
"labels": {"namespace": "payment-prod", "pod": "payment-service-abc123"},
|
||
"annotations": {"summary": "Payment service latency > 500ms"},
|
||
},
|
||
{
|
||
"alert_name": "PaymentServiceErrorRate",
|
||
"severity": "warning", # 原本是 high,但 API 只接受 info|warning|critical
|
||
"source": "prometheus",
|
||
"namespace": "payment-prod",
|
||
"target": "payment-service",
|
||
"fingerprint": "fp_error_001",
|
||
"labels": {"namespace": "payment-prod", "pod": "payment-service-abc123"},
|
||
"annotations": {"summary": "Payment service error rate > 5%"},
|
||
},
|
||
{
|
||
"alert_name": "PaymentServicePodCrash",
|
||
"severity": "critical",
|
||
"source": "alertmanager",
|
||
"namespace": "payment-prod",
|
||
"target": "payment-service",
|
||
"fingerprint": "fp_crash_001",
|
||
"labels": {"namespace": "payment-prod", "pod": "payment-service-abc123"},
|
||
"annotations": {"summary": "Payment service pod crashed"},
|
||
},
|
||
]
|
||
|
||
|
||
async def send_alert(client: httpx.AsyncClient, alert: dict, index: int) -> dict:
|
||
"""發送單一告警"""
|
||
print(f"\n[{index+1}/3] 發送告警: {alert['alert_name']}")
|
||
print(f" severity: {alert['severity']}")
|
||
print(f" namespace: {alert['namespace']}")
|
||
print(f" target: {alert['target']}")
|
||
|
||
response = await client.post(
|
||
SIGNALS_ENDPOINT,
|
||
json=alert,
|
||
timeout=10.0,
|
||
)
|
||
|
||
result = response.json()
|
||
print(f" status_code: {response.status_code}")
|
||
print(f" message_id: {result.get('message_id', 'N/A')}")
|
||
|
||
return result
|
||
|
||
|
||
async def check_redis_incident(client: httpx.AsyncClient) -> dict | None:
|
||
"""檢查 Redis 中的 Incident"""
|
||
# 使用 health endpoint 確認 API 運作
|
||
try:
|
||
# 直接查詢 Redis (透過 API 或直接)
|
||
# 這裡我們用 curl 模擬,但實際應該有 API
|
||
return None
|
||
except Exception as e:
|
||
print(f"Error checking Redis: {e}")
|
||
return None
|
||
|
||
|
||
async def main():
|
||
"""主測試流程"""
|
||
print("=" * 60)
|
||
print("Phase 6.3 聚合測試")
|
||
print("=" * 60)
|
||
print(f"時間: {datetime.now().isoformat()}")
|
||
print("目標: 驗證 3 筆同源告警聚合到 1 個 Incident")
|
||
print()
|
||
|
||
async with httpx.AsyncClient() as client:
|
||
# 1. 確認 API 運作
|
||
print("[0] 檢查 API 健康狀態...")
|
||
try:
|
||
health = await client.get(f"{API_BASE}/api/v1/health", timeout=5.0)
|
||
print(f" API status: {health.status_code}")
|
||
except Exception as e:
|
||
print(f" API 連線失敗: {e}")
|
||
print(" 請確認 API 已啟動: docker compose up -d")
|
||
return
|
||
|
||
# 2. 連續發送 3 筆告警
|
||
print("\n" + "-" * 60)
|
||
print("階段一: 連續發送 3 筆告警")
|
||
print("-" * 60)
|
||
|
||
results = []
|
||
for i, alert in enumerate(TEST_ALERTS):
|
||
result = await send_alert(client, alert, i)
|
||
results.append(result)
|
||
# 短暫等待,確保 Consumer 有時間處理
|
||
await asyncio.sleep(0.5)
|
||
|
||
# 3. 等待 Consumer 處理完成
|
||
print("\n" + "-" * 60)
|
||
print("階段二: 等待 Consumer 處理 (3 秒)")
|
||
print("-" * 60)
|
||
await asyncio.sleep(3)
|
||
|
||
# 4. 輸出驗證指令
|
||
print("\n" + "-" * 60)
|
||
print("階段三: 驗證指令")
|
||
print("-" * 60)
|
||
print()
|
||
print("請執行以下 Redis 指令檢查聚合結果:")
|
||
print()
|
||
print("# 1. 查看所有 Incident keys")
|
||
print("docker exec -it awoooi-redis redis-cli KEYS 'incident:*'")
|
||
print()
|
||
print("# 2. 查看特定 Incident 的 JSON (取代 <INCIDENT_ID>)")
|
||
print("docker exec -it awoooi-redis redis-cli GET 'incident:INC-XXXXXXXX-XXXXXX'")
|
||
print()
|
||
print("# 3. 或直接用以下指令掃描並輸出所有 Incident:")
|
||
print("""docker exec -it awoooi-redis redis-cli --no-raw KEYS 'incident:INC-*' | xargs -I {} docker exec -i awoooi-redis redis-cli GET {}""")
|
||
print()
|
||
|
||
# 5. 輸出 API 日誌指令
|
||
print("-" * 60)
|
||
print("檢查 API 日誌:")
|
||
print("-" * 60)
|
||
print("docker logs awoooi-api --tail 50 | grep -E '(signal_|incident_|aggregat)'")
|
||
print()
|
||
|
||
# 6. 驗證標準
|
||
print("-" * 60)
|
||
print("驗證標準 (PASS/FAIL)")
|
||
print("-" * 60)
|
||
print("[ ] 只有 1 個 Incident 被建立 (非 3 個)")
|
||
print("[ ] signals 陣列長度 = 3")
|
||
print("[ ] affected_services 包含 'payment-service'")
|
||
print("[ ] severity 升級為 'P0' (因為第三筆是 critical)")
|
||
print()
|
||
|
||
print("=" * 60)
|
||
print("測試腳本執行完成")
|
||
print("=" * 60)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
asyncio.run(main())
|