""" verify_telegram_ui.py — Telegram UI 修復驗證腳本 ================================================ 注入 800 字極端字串,驗證: 1. _smart_truncate 在句子邊界截斷,不破壞括號 2. _parse_debate_summary 正確拆分各欄位(不重複) 3. TYPE-3 requires_human_approval=True → 含批准/拒絕按鈕 2026-04-17 ogt + Claude Sonnet 4.6 (ADR-075 UI 修復驗證) """ import sys sys.path.insert(0, "apps/api") # ─── 測試 1:smart_truncate ────────────────────────────────────────────────── from src.services.telegram_gateway import _smart_truncate # 800 字多層括號測試字串 LONG_REASONING = ( "診斷:根據告警訊號分析,發現 MoWoooWorkDown 事件導致服務下線," "可能是由於 deployment 配置錯誤或是 pod 問題引起的(信心 90%,系統正常);" "方案:kubectl rollout restart deployment/awoooi-api -n awoooi-prod" "(blast_radius=25,rollback_cost=5,降級風險極低);" "安全審查:approve(blast_radius 符合安全閾值 ≤50,靜態規則通過,系統正常);" "質疑:無(通過審查,所有指標在正常範圍內,無需人工干預,建議自動執行)" "額外備注:此次分析基於最近 15 分鐘的 Prometheus 指標窗口," "包含 CPU 使用率、記憶體壓力、網路 I/O 三個維度的複合評估(樣本數 N=1440)。" "補充說明:若下次相同告警在 30 分鐘內再次出現,建議升級至 P1 並通知值班主管。" ) print("=" * 60) print("TEST 1: _smart_truncate") print("=" * 60) print(f"原始長度: {len(LONG_REASONING)} 字") print() for limit in [100, 200, 300, 500]: result = _smart_truncate(LONG_REASONING, limit) # 驗證括號平衡 open_p = result.count("(") close_p = result.count(")") bracket_ok = open_p == close_p print(f"limit={limit}: len={len(result)} 括號平衡={bracket_ok} ((={open_p}, )={close_p})") print(f" 結尾: ...{result[-30:]}") print() # ─── 測試 2:_parse_debate_summary ────────────────────────────────────────── # 在 decision_manager 中定義(複製相同邏輯做驗證) def _parse_debate_summary(reasoning: str) -> dict: result = {"diagnosis": "", "plan": "", "review": "", "critic": ""} for part in reasoning.split(";"): part = part.strip() if part.startswith("診斷:"): result["diagnosis"] = part[3:] elif part.startswith("方案:"): result["plan"] = part[3:] elif part.startswith("安全審查:"): result["review"] = part[5:] elif part.startswith("質疑:"): result["critic"] = part[3:] return result print("=" * 60) print("TEST 2: _parse_debate_summary(各欄位不可重複)") print("=" * 60) parsed = _parse_debate_summary(LONG_REASONING) for key, val in parsed.items(): print(f" {key}: {val[:80]}{'...' if len(val) > 80 else ''}") print() print("✅ 各欄位均不同(修復重複渲染):") vals = [v for v in parsed.values() if v] all_different = len(vals) == len(set(vals)) print(f" all_different = {all_different}") # 模擬 TYPE-8M 卡片渲染 print() print("── TYPE-8M 卡片預覽 ──") _diag = _smart_truncate(parsed["diagnosis"] or "(無診斷)", 120) _impact = _smart_truncate(parsed["plan"] or "", 150) _cause = _smart_truncate(parsed["critic"] or parsed["review"] or "", 100) print(f"🎯 診斷結果:{_diag}") if _impact: print(f"🧠 系統影響") print(f" {_impact}") if _cause: print(f"└─ 可能根因:{_cause}") # ─── 測試 3:requires_human_approval 按鈕邏輯 ─────────────────────────────── print() print("=" * 60) print("TEST 3: requires_human_approval → 動態按鈕含批准/拒絕") print("=" * 60) # 模擬 callback_dispatcher 回傳 k8s 動態按鈕 MOCK_K8S_BUTTONS = [ ("🔄 重啟", "restart:INC-001"), ("⬆️ 擴容", "scale_up:INC-001"), ("⬇️ 縮容", "scale_down:INC-001"), ("🔙 回滾", "rollback:INC-001"), ] def simulate_keyboard(dynamic_buttons: list, requires_human_approval: bool) -> list: is_type3 = True approve_nonce = "approve-nonce-xxx" reject_nonce = "reject-nonce-xxx" silence_nonce = "silence-nonce-xxx" if is_type3 and dynamic_buttons: btns = [{"text": t, "callback_data": cb} for t, cb in dynamic_buttons] rows = [btns[i:i+3] for i in range(0, len(btns), 3)] if requires_human_approval: rows.append([ {"text": "✅ 批准", "callback_data": approve_nonce}, {"text": "❌ 拒絕", "callback_data": reject_nonce}, ]) rows.append([ {"text": "📋 詳情", "callback_data": "detail:INC-001"}, {"text": "🔕 忽略", "callback_data": silence_nonce}, ]) return rows return [[ {"text": "✅ 批准", "callback_data": approve_nonce}, {"text": "❌ 拒絕", "callback_data": reject_nonce}, {"text": "🔕 靜默", "callback_data": silence_nonce}, ]] print() print("場景 A: requires_human_approval=False(無動態按鈕卡)") kb_a = simulate_keyboard([], False) for row in kb_a: print(" " + " | ".join(b["text"] for b in row)) print() print("場景 B: requires_human_approval=False + k8s 動態按鈕(舊 bug:死卡)") kb_b = simulate_keyboard(MOCK_K8S_BUTTONS, False) for row in kb_b: print(" " + " | ".join(b["text"] for b in row)) has_approve_b = any(b["text"] == "✅ 批准" for row in kb_b for b in row) print(f" 含批准按鈕: {has_approve_b} ← 舊 bug = False(死卡)") print() print("場景 C: requires_human_approval=True + k8s 動態按鈕(新修復)") kb_c = simulate_keyboard(MOCK_K8S_BUTTONS, True) for row in kb_c: print(" " + " | ".join(b["text"] for b in row)) has_approve_c = any(b["text"] == "✅ 批准" for row in kb_c for b in row) print(f" 含批准按鈕: {has_approve_c} ← 修復後 = True ✅") print() print("=" * 60) print("SUMMARY") print("=" * 60) t1 = not any("(" in _smart_truncate(LONG_REASONING, l) and ")" not in _smart_truncate(LONG_REASONING, l) for l in [100, 200, 300, 500]) t2 = all_different t3 = has_approve_c and not has_approve_b print(f"TEST 1 smart_truncate 括號不破壞: {'✅' if t1 else '❌'}") print(f"TEST 2 parse_debate 各欄位不重複: {'✅' if t2 else '❌'}") print(f"TEST 3 requires_human→批准按鈕: {'✅' if t3 else '❌'}") if all([t1, t2, t3]): print("\n🎉 全部通過!UI 修復驗證完成。") else: print("\n❌ 有測試未通過,請檢查。") sys.exit(1)