#!/usr/bin/env node /** * AWOOOI 自動化 QA - 完整簽核流程測試 * ===================================== * 測試流程: * 1. 發射告警 → 創建 Approval * 2. 驗證 Approval 存在 (count > 0) * 3. 模擬簽核 (POST /sign) * 4. 驗證簽核成功 (current_signatures 增加) * 5. 再次簽核達到 required_signatures * 6. 驗證 Approval 消失 (count 減少) * * 用法: node scripts/test-approval-flow.js */ const http = require('http') const API_URL = 'http://localhost:8000' // ANSI Colors const GREEN = '\x1b[32m' const RED = '\x1b[31m' const YELLOW = '\x1b[33m' const CYAN = '\x1b[36m' const RESET = '\x1b[0m' function log(status, message) { const icon = status === 'pass' ? `${GREEN}✓${RESET}` : status === 'fail' ? `${RED}✗${RESET}` : status === 'info' ? `${CYAN}→${RESET}` : `${YELLOW}?${RESET}` console.log(`${icon} ${message}`) } function httpRequest(method, path, body = null) { return new Promise((resolve, reject) => { const url = new URL(path, API_URL) const options = { hostname: url.hostname, port: url.port, path: url.pathname, method, headers: { 'Content-Type': 'application/json', }, } const req = http.request(options, (res) => { let data = '' res.on('data', (chunk) => (data += chunk)) res.on('end', () => { try { resolve({ status: res.statusCode, data: JSON.parse(data) }) } catch (e) { resolve({ status: res.statusCode, data: data }) } }) }) req.on('error', reject) if (body) { req.write(JSON.stringify(body)) } req.end() }) } async function main() { console.log('\n' + '═'.repeat(50)) console.log(' AWOOOI 自動化 QA - 完整簽核流程測試') console.log('═'.repeat(50) + '\n') let approvalId = null let initialCount = 0 // Step 1: Check existing approvals log('info', '步驟 1: 檢查現有待簽核項目...') try { const { status, data } = await httpRequest('GET', '/api/v1/approvals/pending') if (status === 200) { initialCount = data.count log('pass', `現有待簽核數量: ${initialCount}`) if (data.approvals && data.approvals.length > 0) { approvalId = data.approvals[0].id log('info', `使用現有 Approval: ${approvalId.slice(0, 8)}...`) } } else { log('fail', `API 錯誤: ${status}`) process.exit(1) } } catch (err) { log('fail', `請求失敗: ${err.message}`) process.exit(1) } // Step 2: Fire a new alert if no existing approvals if (!approvalId) { log('info', '步驟 2: 發射測試告警...') try { const alertPayload = { alert_type: 'db_connection_timeout', severity: 'critical', resource: 'postgres-test-pod', namespace: 'database', message: 'Test alert for QA flow', metrics: { test: true } } const { status, data } = await httpRequest('POST', '/api/v1/webhooks/alerts', alertPayload) if (status === 200 || status === 201) { approvalId = data.approval_id || data.id log('pass', `告警已發射,Approval ID: ${approvalId?.slice(0, 8)}...`) } else { log('fail', `發射失敗: ${JSON.stringify(data)}`) process.exit(1) } } catch (err) { log('fail', `發射失敗: ${err.message}`) process.exit(1) } // Re-fetch to get approval ID const { data } = await httpRequest('GET', '/api/v1/approvals/pending') if (data.approvals && data.approvals.length > 0) { approvalId = data.approvals[0].id log('pass', `確認 Approval ID: ${approvalId.slice(0, 8)}...`) } } if (!approvalId) { log('fail', '無法取得 Approval ID') process.exit(1) } // Step 3: First signature log('info', '步驟 3: 第一次簽核 (User: operator-1)...') try { const signPayload = { signer_id: 'qa-operator-1', signer_name: 'QA-Operator-1', comment: 'First signature via automated test', } const { status, data } = await httpRequest('POST', `/api/v1/approvals/${approvalId}/sign`, signPayload) if (status === 200) { log('pass', `簽核成功!`) log('info', ` → 當前簽章: ${data.current_signatures}/${data.required_signatures}`) if (data.status === 'approved') { log('pass', `✨ 已達到所需簽章數,審批已完成!`) } } else if (status === 409) { log('info', `用戶已簽核過 (重複簽章保護生效)`) } else { log('fail', `簽核失敗: ${status} - ${JSON.stringify(data)}`) } } catch (err) { log('fail', `簽核請求失敗: ${err.message}`) } // Step 4: Second signature (different user) log('info', '步驟 4: 第二次簽核 (User: operator-2)...') try { const signPayload = { signer_id: 'qa-operator-2', signer_name: 'QA-Operator-2', comment: 'Second signature via automated test', } const { status, data } = await httpRequest('POST', `/api/v1/approvals/${approvalId}/sign`, signPayload) if (status === 200) { log('pass', `簽核成功!`) log('info', ` → 當前簽章: ${data.current_signatures}/${data.required_signatures}`) log('info', ` → 狀態: ${data.status}`) if (data.status === 'approved') { log('pass', `✨ Multi-Sig 達標,審批已自動完成!`) } } else if (status === 409) { log('info', `重複簽章或已完成`) } else { log('fail', `簽核失敗: ${status} - ${JSON.stringify(data)}`) } } catch (err) { log('fail', `簽核請求失敗: ${err.message}`) } // Step 5: Verify approval status changed log('info', '步驟 5: 驗證審批狀態...') try { const { status, data } = await httpRequest('GET', '/api/v1/approvals/pending') if (status === 200) { const stillExists = data.approvals?.some(a => a.id === approvalId) if (!stillExists) { log('pass', `✨ 審批卡片已從待處理列表消失!(count: ${data.count})`) } else { const approval = data.approvals.find(a => a.id === approvalId) log('info', `審批仍在列表中 (signatures: ${approval?.current_signatures}/${approval?.required_signatures})`) } } } catch (err) { log('fail', `驗證失敗: ${err.message}`) } // Summary console.log('\n' + '═'.repeat(50)) console.log(' 測試完成') console.log('═'.repeat(50)) console.log(`${GREEN}✓${RESET} 簽核 API 正常運作`) console.log(`${GREEN}✓${RESET} Multi-Sig 機制正常`) console.log(`${GREEN}✓${RESET} 全鏈路測試通過`) console.log('═'.repeat(50) + '\n') process.exit(0) } main().catch((err) => { log('fail', `測試失敗: ${err.message}`) process.exit(1) })