All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 14m24s
CRITICAL → 嚴重 (ADR-075 中文風險等級) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
313 lines
8.9 KiB
Python
313 lines
8.9 KiB
Python
"""
|
|
test_telegram_message_templates.py - Telegram 訊息模板測試
|
|
|
|
2026-03-29 ogt: 新增,驗證 6 種新訊息模板格式正確
|
|
符合 feedback_no_mock_testing.md 規範 - 測試實際格式化輸出
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from src.services.telegram_gateway import (
|
|
DailySummaryMessage,
|
|
DeploySuccessMessage,
|
|
RateLimitMessage,
|
|
RepairReportMessage,
|
|
ResourceWarnMessage,
|
|
SentryErrorMessage,
|
|
TelegramMessage,
|
|
)
|
|
|
|
|
|
class TestTelegramMessageFormat:
|
|
"""測試現有 TelegramMessage 格式化"""
|
|
|
|
def test_telegram_message_format_basic(self):
|
|
"""測試基本訊息格式化"""
|
|
msg = TelegramMessage(
|
|
status_emoji="🚨",
|
|
risk_level="CRITICAL",
|
|
resource_name="test-pod-123",
|
|
root_cause="Test root cause",
|
|
suggested_action="Restart pod",
|
|
estimated_downtime="~30s",
|
|
approval_id="INC-20260329-0001",
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "🚨" in result
|
|
assert "嚴重" in result
|
|
assert "test-pod-123" in result
|
|
assert len(result) <= 900 # SOUL.md 限制
|
|
|
|
def test_telegram_message_with_token_cost(self):
|
|
"""測試含 Token/Cost 的訊息"""
|
|
msg = TelegramMessage(
|
|
status_emoji="⚠️",
|
|
risk_level="MEDIUM",
|
|
resource_name="api-pod",
|
|
root_cause="High CPU",
|
|
suggested_action="Scale up",
|
|
estimated_downtime="0s",
|
|
approval_id="INC-20260329-0002",
|
|
ai_tokens=1500,
|
|
ai_cost=0.0015,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "💰 Tokens: 1,500 / $0.0015" in result
|
|
|
|
|
|
class TestSentryErrorMessage:
|
|
"""測試 Sentry 錯誤訊息"""
|
|
|
|
def test_sentry_error_format_basic(self):
|
|
"""測試基本 Sentry 錯誤格式"""
|
|
msg = SentryErrorMessage(
|
|
error_id="SENTRY-abc123",
|
|
error_type="TypeError",
|
|
error_message="Cannot read property 'x' of undefined",
|
|
service_name="awoooi-api",
|
|
file_location="src/api/v1/incidents.py:123",
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "🐛" in result
|
|
assert "SENTRY ERROR" in result
|
|
assert "TypeError" in result
|
|
assert "awoooi-api" in result
|
|
assert len(result) <= 900
|
|
|
|
def test_sentry_error_with_stack_trace(self):
|
|
"""測試含 Stack Trace 的 Sentry 錯誤"""
|
|
msg = SentryErrorMessage(
|
|
error_id="SENTRY-xyz789",
|
|
error_type="ValueError",
|
|
error_message="Invalid input",
|
|
service_name="awoooi-web",
|
|
file_location="src/components/App.tsx:45",
|
|
occurrence_count=15,
|
|
affected_users=3,
|
|
first_seen="10 分鐘前",
|
|
stack_trace=[
|
|
"incidents.py:123 in get_incident",
|
|
"service.py:45 in fetch_data",
|
|
"db.py:89 in query",
|
|
],
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "發生次數: <code>15</code>" in result
|
|
assert "影響用戶: <code>3</code>" in result
|
|
assert "Stack Trace" in result
|
|
|
|
|
|
class TestResourceWarnMessage:
|
|
"""測試資源告警訊息"""
|
|
|
|
def test_resource_warn_format_basic(self):
|
|
"""測試基本資源告警格式"""
|
|
msg = ResourceWarnMessage(
|
|
resource_id="RES-20260329-0001",
|
|
pod_name="awoooi-api-7d4b8c9f5-abc12",
|
|
namespace="awoooi-prod",
|
|
cpu_percent=92.5,
|
|
memory_percent=78.0,
|
|
disk_percent=45.0,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "⚠️" in result
|
|
assert "資源告警" in result
|
|
assert "CPU: 🔴" in result # 92.5% > 90%
|
|
assert "Memory: 🟡" in result # 78% >= 70%
|
|
assert "Disk: 🟢" in result # 45% < 70%
|
|
assert len(result) <= 900
|
|
|
|
def test_resource_warn_with_limits(self):
|
|
"""測試含限制資訊的資源告警"""
|
|
msg = ResourceWarnMessage(
|
|
resource_id="RES-20260329-0002",
|
|
pod_name="test-pod",
|
|
cpu_percent=85.0,
|
|
cpu_limit="500m",
|
|
memory_percent=60.0,
|
|
memory_limit="512Mi",
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "(limit: 500m)" in result
|
|
assert "(limit: 512Mi)" in result
|
|
|
|
|
|
class TestRepairReportMessage:
|
|
"""測試自動修復報告"""
|
|
|
|
def test_repair_report_format_basic(self):
|
|
"""測試基本修復報告格式"""
|
|
msg = RepairReportMessage(
|
|
report_date="2026-03-29",
|
|
total_repairs=12,
|
|
success_count=10,
|
|
failure_count=2,
|
|
saved_minutes=45,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "🔧" in result
|
|
assert "自動修復報告" in result
|
|
assert "總修復次數: <code>12</code>" in result
|
|
assert "成功: ✅ <code>10</code> (83%)" in result
|
|
assert len(result) <= 900
|
|
|
|
def test_repair_report_with_top_issues(self):
|
|
"""測試含 Top 問題的修復報告"""
|
|
msg = RepairReportMessage(
|
|
report_date="2026-03-29",
|
|
total_repairs=12,
|
|
success_count=10,
|
|
failure_count=2,
|
|
top_issues=[
|
|
("Pod CrashLoopBackOff", 5),
|
|
("OOM Killed", 4),
|
|
("Image Pull Failed", 3),
|
|
],
|
|
ai_cost_gemini=0.0234,
|
|
ai_tokens_total=1823,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "Top 3 問題" in result
|
|
assert "Pod CrashLoopBackOff" in result
|
|
assert "Gemini: $0.0234" in result
|
|
|
|
|
|
class TestDailySummaryMessage:
|
|
"""測試每日摘要"""
|
|
|
|
def test_daily_summary_format_basic(self):
|
|
"""測試基本每日摘要格式"""
|
|
msg = DailySummaryMessage(
|
|
summary_date="2026-03-29",
|
|
alert_total=45,
|
|
alert_critical=2,
|
|
alert_medium=18,
|
|
alert_low=25,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "📊" in result
|
|
assert "每日摘要" in result
|
|
assert "總數: <code>45</code>" in result
|
|
assert "Critical: <code>2</code>" in result
|
|
assert len(result) <= 900
|
|
|
|
def test_daily_summary_with_full_stats(self):
|
|
"""測試完整統計的每日摘要"""
|
|
msg = DailySummaryMessage(
|
|
summary_date="2026-03-29",
|
|
alert_total=45,
|
|
auto_repair_count=30,
|
|
manual_approval_count=10,
|
|
ignored_count=5,
|
|
api_availability=99.95,
|
|
ai_cost=0.15,
|
|
budget_remaining=9.85,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "自動修復: <code>30</code>" in result
|
|
assert "API: <code>99.95%</code>" in result
|
|
assert "預算剩餘: $9.85" in result
|
|
|
|
|
|
class TestDeploySuccessMessage:
|
|
"""測試部署成功訊息"""
|
|
|
|
def test_deploy_success_format_basic(self):
|
|
"""測試基本部署成功格式"""
|
|
msg = DeploySuccessMessage(
|
|
commit_sha="abc1234567",
|
|
triggered_by="ogt",
|
|
environment="Production",
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "✅" in result
|
|
assert "部署成功" in result
|
|
assert "abc12345" in result # 前 8 字元
|
|
assert "@ogt" in result
|
|
assert len(result) <= 900
|
|
|
|
def test_deploy_success_with_e2e(self):
|
|
"""測試含 E2E 結果的部署成功"""
|
|
msg = DeploySuccessMessage(
|
|
commit_sha="abc1234567",
|
|
triggered_by="ogt",
|
|
api_version="v1.2.3",
|
|
web_version="v1.2.3",
|
|
duration_seconds=225, # 3m 45s
|
|
e2e_passed=26,
|
|
e2e_total=26,
|
|
health_check_passed=True,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "v1.2.3" in result
|
|
assert "3m 45s" in result
|
|
assert "✅ 26/26 PASSED" in result
|
|
|
|
|
|
class TestRateLimitMessage:
|
|
"""測試 API 限額警告"""
|
|
|
|
def test_rate_limit_format_basic(self):
|
|
"""測試基本限額警告格式"""
|
|
msg = RateLimitMessage(
|
|
provider="gemini",
|
|
daily_usage=450,
|
|
daily_limit=500,
|
|
token_usage=85000,
|
|
token_limit=100000,
|
|
cost_usd=0.08,
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "⚠️" in result
|
|
assert "API 限額警告" in result
|
|
assert "GEMINI API" in result
|
|
assert "450/500" in result
|
|
assert "(90%)" in result
|
|
assert len(result) <= 900
|
|
|
|
def test_rate_limit_with_suggestions(self):
|
|
"""測試含建議的限額警告"""
|
|
msg = RateLimitMessage(
|
|
provider="openai",
|
|
daily_usage=90,
|
|
daily_limit=100,
|
|
suggestions=[
|
|
"考慮切換到 Ollama 優先",
|
|
"或增加每日限額",
|
|
],
|
|
reset_time="明日 00:00",
|
|
)
|
|
|
|
result = msg.format()
|
|
|
|
assert "建議" in result
|
|
assert "切換到 Ollama" in result
|
|
assert "明日 00:00" in result
|