Files
awoooi/scripts/test-approval-flow.js
OG T 7478dc0254 feat(phase6-9): Complete modular architecture and Agent Teams
Phase 6.4 - Modular Architecture:
- Add lewooogo-brain adapters for LLM providers
- Add lewooogo-data dual memory (Redis + PostgreSQL)
- Implement consensus engine for multi-agent decisions
- Add incident memory service for historical context

Phase 9 - Agent Teams (Claude Agent SDK):
- Add base agent class with Claude Sonnet 4 integration
- Implement action planner, blast radius, and security agents
- Add agent API endpoints and proposal workflow
- Integrate ADR-009 OpenClaw Agent Teams architecture

DevOps & CI/CD:
- Add GitHub Actions CI/CD workflows (ci.yaml, cd.yaml)
- Add pre-commit hooks and secrets baseline
- Add docker-compose for local development
- Update Kubernetes network policies

Frontend Improvements:
- Add auto-healing error boundary component
- Update i18n messages for agent features
- Enhance dual-state incident card with execution feedback

Documentation:
- Add 7 ADRs covering MCP, design system, architecture decisions
- Update ARCHITECTURE_MEMORY.md with modular design
- Add GLOBAL_RULES.md and SOUL.md for project identity

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-23 18:40:36 +08:00

226 lines
6.7 KiB
JavaScript
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.
#!/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)
})