#!/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)