#!/usr/bin/env node /** * VOC + TikHub 接口连通性测试 * * 测试维度: * - 可达性(TCP/TLS/HTTP 响应) * - 延迟(ms) * - 认证链路(无/有 token 对比) * - 响应格式(JSON 解析) * * 用法: * node scripts/tools/test-connectivity.js * node scripts/tools/test-connectivity.js --token=r:xxx # 覆盖 voc-credentials.json * node scripts/tools/test-connectivity.js --tikhub=tk:xxx # 额外测试 TikHub 直连 */ const fs = require('fs'); const os = require('os'); const path = require('path'); // ─── 解析参数 ─── const args = process.argv.slice(2); const argToken = (args.find(a => a.startsWith('--token=')) || '').slice(8); const tikhubToken = (args.find(a => a.startsWith('--tikhub=')) || '').slice(9); // ─── 从 voc-credentials.json 读 token 兜底 ─── let sessionToken = argToken; if (!sessionToken) { const cfg = path.join(os.homedir(), '.openclaw', 'voc-credentials.json'); if (fs.existsSync(cfg)) { try { const data = JSON.parse(fs.readFileSync(cfg, 'utf-8')); sessionToken = data.vocToken || ''; } catch (_) {} } } const APP_ID = 'ncloudmaster'; const APIG_ID = 'Vo3ROWEvDy'; const VOC_BASE = 'https://server.fmode.cn'; const TIKHUB_BASE = 'https://api.tikhub.io'; // ─── 单次测试执行器 ─── async function probe(label, url, opts = {}) { const t0 = Date.now(); try { const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs || 8000); const resp = await fetch(url, { ...opts, signal: ctrl.signal }); clearTimeout(timer); const ms = Date.now() - t0; const txt = await resp.text(); let json = null; try { json = JSON.parse(txt); } catch (_) {} return { label, url, ok: resp.ok, status: resp.status, ms, body: json || txt.slice(0, 200), error: null }; } catch (e) { return { label, url, ok: false, status: 0, ms: Date.now() - t0, body: null, error: e.message }; } } function printResult(r) { const statusColor = r.ok ? '\x1b[32m' : (r.status > 0 ? '\x1b[33m' : '\x1b[31m'); const statusTxt = r.ok ? `✅ ${r.status}` : (r.status > 0 ? `⚠️ ${r.status}` : `❌ 无响应`); console.log(`${statusColor}${statusTxt}\x1b[0m ${r.ms}ms ${r.label}`); console.log(` ${r.url}`); if (r.error) console.log(` 错误: ${r.error}`); else if (typeof r.body === 'object' && r.body !== null) { const s = JSON.stringify(r.body); console.log(` 响应: ${s.slice(0, 180)}${s.length > 180 ? '...' : ''}`); } else if (r.body) { console.log(` 响应: ${String(r.body).slice(0, 180)}`); } console.log(''); } // ─── 主流程 ─── (async () => { console.log('═══════════════════════════════════════════════════════════════'); console.log(' VOC + TikHub 接口连通性测试'); console.log('═══════════════════════════════════════════════════════════════'); console.log(` VOC: ${VOC_BASE}`); console.log(` TikHub: ${TIKHUB_BASE}`); console.log(` APIG_ID: ${APIG_ID}`); console.log(` Session token: ${sessionToken ? sessionToken.slice(0, 6) + '...' + sessionToken.slice(-4) : '(无)'}`); console.log(` TikHub token: ${tikhubToken ? tikhubToken.slice(0, 6) + '...' + tikhubToken.slice(-4) : '(未提供,跳过直连测试)'}`); console.log(''); const results = []; // ─── Section 1: VOC 基础可达性 ─── console.log('── [1/3] VOC 基础可达性 ──'); results.push(await probe( 'Parse health', `${VOC_BASE}/parse/health`, { headers: { 'X-Parse-Application-Id': APP_ID } } )); results.push(await probe( 'Parse REST: 读取 APIG 记录', `${VOC_BASE}/parse/classes/APIG/${APIG_ID}`, { headers: { 'X-Parse-Application-Id': APP_ID } } )); results.push(await probe( 'APIG getApig (无 token)', `${VOC_BASE}/api/apig/getApig`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ authid: '' }) } )); results.slice(-3).forEach(printResult); // ─── Section 2: VOC 鉴权链路(带 session token) ─── console.log('── [2/3] VOC 鉴权链路 ──'); if (sessionToken) { results.push(await probe( 'Parse /users/me (用当前 token)', `${VOC_BASE}/parse/users/me?include=company`, { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-Session-Token': sessionToken } } )); printResult(results[results.length - 1]); } else { console.log(' (跳过,无 session token)\n'); } // ─── Section 3: TikHub 代理链路(通过 VOC) ─── console.log('── [3/3] TikHub 代理链路(通过 VOC) ──'); // Instagram 代理(无 token) results.push(await probe( 'VOC→TikHub: Instagram search (无 token)', `${VOC_BASE}/api/voc-social/instagram/v1/fetch_search?query=nike&select=users`, { method: 'GET' } )); printResult(results[results.length - 1]); // TikTok 代理(无 token) results.push(await probe( 'VOC→TikHub: TikTok video search (无 token)', `${VOC_BASE}/api/voc-social/tiktok/app/v3/fetch_video_search_result?keyword=nike&count=1&cursor=0`, { method: 'GET' } )); printResult(results[results.length - 1]); // TikHub 直连测试(如果提供了 TikHub token) if (tikhubToken) { console.log('── [额外] TikHub 直连测试 ──'); results.push(await probe( 'TikHub 直连: Instagram search', `${TIKHUB_BASE}/api/v1/instagram/web_app/fetch_search_users_v1?keyword=nike&count=1`, { headers: { Authorization: `Bearer ${tikhubToken}` } } )); printResult(results[results.length - 1]); } // ─── 汇总报告 ─── console.log('═══════════════════════════════════════════════════════════════'); console.log(' 汇总'); console.log('═══════════════════════════════════════════════════════════════'); const pass = results.filter(r => r.ok).length; const softFail = results.filter(r => !r.ok && r.status > 0).length; const hardFail = results.filter(r => r.status === 0).length; console.log(` ✅ 可达 + 2xx: ${pass}`); console.log(` ⚠️ 可达但非 2xx: ${softFail} (通常是鉴权/参数问题,不是连通性问题)`); console.log(` ❌ 完全不可达: ${hardFail}`); console.log(''); const avgMs = Math.round(results.filter(r => r.ms > 0).reduce((s, r) => s + r.ms, 0) / results.length); console.log(` 平均延迟: ${avgMs}ms`); console.log(''); if (hardFail > 0) { console.log(' ❌ 存在完全不可达的端点 — 检查网络/DNS/防火墙'); process.exitCode = 1; } else { console.log(' ✅ 所有端点 TCP/TLS/HTTP 层面都可达'); } })();