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>
158 lines
4.5 KiB
JavaScript
158 lines
4.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* AWOOOI 自動化 QA 腳本 - verify-sse.js
|
|
* =====================================
|
|
* 自動驗證 SSE 連線,禁止人工 QA
|
|
*
|
|
* 驗證項目:
|
|
* 1. 後端 SSE 端點 200 OK
|
|
* 2. SSE 數據流 (connected, heartbeat)
|
|
* 3. 前端頁面可訪問
|
|
*
|
|
* 用法: node scripts/verify-sse.js
|
|
*/
|
|
|
|
const http = require('http')
|
|
|
|
const API_URL = 'http://localhost:8000'
|
|
const FRONTEND_URL = 'http://localhost:3000'
|
|
|
|
// ANSI Colors
|
|
const GREEN = '\x1b[32m'
|
|
const RED = '\x1b[31m'
|
|
const YELLOW = '\x1b[33m'
|
|
const RESET = '\x1b[0m'
|
|
|
|
function log(status, message) {
|
|
const icon = status === 'pass' ? `${GREEN}✓${RESET}` : status === 'fail' ? `${RED}✗${RESET}` : `${YELLOW}→${RESET}`
|
|
console.log(`${icon} ${message}`)
|
|
}
|
|
|
|
// Test 1: Backend SSE Endpoint
|
|
async function testBackendSSE() {
|
|
return new Promise((resolve) => {
|
|
log('info', '測試後端 SSE 端點...')
|
|
|
|
const req = http.get(`${API_URL}/api/v1/dashboard/stream`, (res) => {
|
|
if (res.statusCode !== 200) {
|
|
log('fail', `後端 SSE: HTTP ${res.statusCode}`)
|
|
resolve(false)
|
|
return
|
|
}
|
|
|
|
let data = ''
|
|
let gotConnected = false
|
|
let gotHeartbeat = false
|
|
|
|
res.on('data', (chunk) => {
|
|
data += chunk.toString()
|
|
|
|
if (data.includes('event: connected')) gotConnected = true
|
|
if (data.includes('event: heartbeat') || data.includes('event: host_update')) gotHeartbeat = true
|
|
|
|
// Stop after receiving both events
|
|
if (gotConnected && gotHeartbeat) {
|
|
req.destroy()
|
|
log('pass', '後端 SSE: connected + data events 收到')
|
|
resolve(true)
|
|
}
|
|
})
|
|
|
|
// Timeout after 10 seconds
|
|
setTimeout(() => {
|
|
req.destroy()
|
|
if (gotConnected) {
|
|
log('pass', '後端 SSE: connected 收到')
|
|
resolve(true)
|
|
} else {
|
|
log('fail', '後端 SSE: 超時未收到事件')
|
|
resolve(false)
|
|
}
|
|
}, 10000)
|
|
})
|
|
|
|
req.on('error', (err) => {
|
|
log('fail', `後端 SSE: ${err.message}`)
|
|
resolve(false)
|
|
})
|
|
})
|
|
}
|
|
|
|
// Test 2: Frontend Accessible
|
|
async function testFrontend() {
|
|
return new Promise((resolve) => {
|
|
log('info', '測試前端頁面...')
|
|
|
|
http.get(`${FRONTEND_URL}/zh-TW`, (res) => {
|
|
if (res.statusCode === 200 || res.statusCode === 307) {
|
|
log('pass', `前端頁面: HTTP ${res.statusCode}`)
|
|
resolve(true)
|
|
} else {
|
|
log('fail', `前端頁面: HTTP ${res.statusCode}`)
|
|
resolve(false)
|
|
}
|
|
}).on('error', (err) => {
|
|
log('fail', `前端頁面: ${err.message}`)
|
|
resolve(false)
|
|
})
|
|
})
|
|
}
|
|
|
|
// Test 3: API Health Check
|
|
async function testAPIHealth() {
|
|
return new Promise((resolve) => {
|
|
log('info', '測試 API 健康狀態...')
|
|
|
|
http.get(`${API_URL}/api/v1/health`, (res) => {
|
|
let data = ''
|
|
res.on('data', chunk => data += chunk)
|
|
res.on('end', () => {
|
|
try {
|
|
const json = JSON.parse(data)
|
|
if (json.status === 'healthy') {
|
|
log('pass', `API 健康: ${json.status}`)
|
|
resolve(true)
|
|
} else {
|
|
log('fail', `API 健康: ${json.status}`)
|
|
resolve(false)
|
|
}
|
|
} catch (e) {
|
|
log('fail', `API 健康: 無法解析 JSON`)
|
|
resolve(false)
|
|
}
|
|
})
|
|
}).on('error', (err) => {
|
|
log('fail', `API 健康: ${err.message}`)
|
|
resolve(false)
|
|
})
|
|
})
|
|
}
|
|
|
|
// Main
|
|
async function main() {
|
|
console.log('\n========================================')
|
|
console.log(' AWOOOI 自動化 QA - SSE 驗證腳本')
|
|
console.log('========================================\n')
|
|
|
|
const results = {
|
|
apiHealth: await testAPIHealth(),
|
|
backendSSE: await testBackendSSE(),
|
|
frontend: await testFrontend(),
|
|
}
|
|
|
|
console.log('\n========================================')
|
|
console.log(' 驗證結果')
|
|
console.log('========================================')
|
|
console.log(`API 健康: ${results.apiHealth ? GREEN + 'PASS' + RESET : RED + 'FAIL' + RESET}`)
|
|
console.log(`後端 SSE: ${results.backendSSE ? GREEN + 'PASS' + RESET : RED + 'FAIL' + RESET}`)
|
|
console.log(`前端頁面: ${results.frontend ? GREEN + 'PASS' + RESET : RED + 'FAIL' + RESET}`)
|
|
|
|
const allPassed = Object.values(results).every(Boolean)
|
|
console.log(`\n總結: ${allPassed ? GREEN + '全部通過' + RESET : RED + '有失敗項目' + RESET}`)
|
|
console.log('========================================\n')
|
|
|
|
process.exit(allPassed ? 0 : 1)
|
|
}
|
|
|
|
main().catch(console.error)
|