test-connectivity.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #!/usr/bin/env node
  2. /**
  3. * VOC + TikHub 接口连通性测试
  4. *
  5. * 测试维度:
  6. * - 可达性(TCP/TLS/HTTP 响应)
  7. * - 延迟(ms)
  8. * - 认证链路(无/有 token 对比)
  9. * - 响应格式(JSON 解析)
  10. *
  11. * 用法:
  12. * node scripts/tools/test-connectivity.js
  13. * node scripts/tools/test-connectivity.js --token=r:xxx # 覆盖 voc-credentials.json
  14. * node scripts/tools/test-connectivity.js --tikhub=tk:xxx # 额外测试 TikHub 直连
  15. */
  16. const fs = require('fs');
  17. const os = require('os');
  18. const path = require('path');
  19. // ─── 解析参数 ───
  20. const args = process.argv.slice(2);
  21. const argToken = (args.find(a => a.startsWith('--token=')) || '').slice(8);
  22. const tikhubToken = (args.find(a => a.startsWith('--tikhub=')) || '').slice(9);
  23. // ─── 从 voc-credentials.json 读 token 兜底 ───
  24. let sessionToken = argToken;
  25. if (!sessionToken) {
  26. const cfg = path.join(os.homedir(), '.openclaw', 'voc-credentials.json');
  27. if (fs.existsSync(cfg)) {
  28. try {
  29. const data = JSON.parse(fs.readFileSync(cfg, 'utf-8'));
  30. sessionToken = data.vocToken || '';
  31. } catch (_) {}
  32. }
  33. }
  34. const APP_ID = 'ncloudmaster';
  35. const APIG_ID = 'Vo3ROWEvDy';
  36. const VOC_BASE = 'https://server.fmode.cn';
  37. const TIKHUB_BASE = 'https://api.tikhub.io';
  38. // ─── 单次测试执行器 ───
  39. async function probe(label, url, opts = {}) {
  40. const t0 = Date.now();
  41. try {
  42. const ctrl = new AbortController();
  43. const timer = setTimeout(() => ctrl.abort(), opts.timeoutMs || 8000);
  44. const resp = await fetch(url, { ...opts, signal: ctrl.signal });
  45. clearTimeout(timer);
  46. const ms = Date.now() - t0;
  47. const txt = await resp.text();
  48. let json = null;
  49. try { json = JSON.parse(txt); } catch (_) {}
  50. return {
  51. label, url, ok: resp.ok, status: resp.status, ms,
  52. body: json || txt.slice(0, 200),
  53. error: null
  54. };
  55. } catch (e) {
  56. return {
  57. label, url, ok: false, status: 0, ms: Date.now() - t0,
  58. body: null, error: e.message
  59. };
  60. }
  61. }
  62. function printResult(r) {
  63. const statusColor = r.ok ? '\x1b[32m' : (r.status > 0 ? '\x1b[33m' : '\x1b[31m');
  64. const statusTxt = r.ok ? `✅ ${r.status}` : (r.status > 0 ? `⚠️ ${r.status}` : `❌ 无响应`);
  65. console.log(`${statusColor}${statusTxt}\x1b[0m ${r.ms}ms ${r.label}`);
  66. console.log(` ${r.url}`);
  67. if (r.error) console.log(` 错误: ${r.error}`);
  68. else if (typeof r.body === 'object' && r.body !== null) {
  69. const s = JSON.stringify(r.body);
  70. console.log(` 响应: ${s.slice(0, 180)}${s.length > 180 ? '...' : ''}`);
  71. } else if (r.body) {
  72. console.log(` 响应: ${String(r.body).slice(0, 180)}`);
  73. }
  74. console.log('');
  75. }
  76. // ─── 主流程 ───
  77. (async () => {
  78. console.log('═══════════════════════════════════════════════════════════════');
  79. console.log(' VOC + TikHub 接口连通性测试');
  80. console.log('═══════════════════════════════════════════════════════════════');
  81. console.log(` VOC: ${VOC_BASE}`);
  82. console.log(` TikHub: ${TIKHUB_BASE}`);
  83. console.log(` APIG_ID: ${APIG_ID}`);
  84. console.log(` Session token: ${sessionToken ? sessionToken.slice(0, 6) + '...' + sessionToken.slice(-4) : '(无)'}`);
  85. console.log(` TikHub token: ${tikhubToken ? tikhubToken.slice(0, 6) + '...' + tikhubToken.slice(-4) : '(未提供,跳过直连测试)'}`);
  86. console.log('');
  87. const results = [];
  88. // ─── Section 1: VOC 基础可达性 ───
  89. console.log('── [1/3] VOC 基础可达性 ──');
  90. results.push(await probe(
  91. 'Parse health',
  92. `${VOC_BASE}/parse/health`,
  93. { headers: { 'X-Parse-Application-Id': APP_ID } }
  94. ));
  95. results.push(await probe(
  96. 'Parse REST: 读取 APIG 记录',
  97. `${VOC_BASE}/parse/classes/APIG/${APIG_ID}`,
  98. { headers: { 'X-Parse-Application-Id': APP_ID } }
  99. ));
  100. results.push(await probe(
  101. 'APIG getApig (无 token)',
  102. `${VOC_BASE}/api/apig/getApig`,
  103. {
  104. method: 'POST',
  105. headers: { 'Content-Type': 'application/json' },
  106. body: JSON.stringify({ authid: '' })
  107. }
  108. ));
  109. results.slice(-3).forEach(printResult);
  110. // ─── Section 2: VOC 鉴权链路(带 session token) ───
  111. console.log('── [2/3] VOC 鉴权链路 ──');
  112. if (sessionToken) {
  113. results.push(await probe(
  114. 'Parse /users/me (用当前 token)',
  115. `${VOC_BASE}/parse/users/me?include=company`,
  116. { headers: { 'X-Parse-Application-Id': APP_ID, 'X-Parse-Session-Token': sessionToken } }
  117. ));
  118. printResult(results[results.length - 1]);
  119. } else {
  120. console.log(' (跳过,无 session token)\n');
  121. }
  122. // ─── Section 3: TikHub 代理链路(通过 VOC) ───
  123. console.log('── [3/3] TikHub 代理链路(通过 VOC) ──');
  124. // Instagram 代理(无 token)
  125. results.push(await probe(
  126. 'VOC→TikHub: Instagram search (无 token)',
  127. `${VOC_BASE}/api/voc-social/instagram/v1/fetch_search?query=nike&select=users`,
  128. { method: 'GET' }
  129. ));
  130. printResult(results[results.length - 1]);
  131. // TikTok 代理(无 token)
  132. results.push(await probe(
  133. 'VOC→TikHub: TikTok video search (无 token)',
  134. `${VOC_BASE}/api/voc-social/tiktok/app/v3/fetch_video_search_result?keyword=nike&count=1&cursor=0`,
  135. { method: 'GET' }
  136. ));
  137. printResult(results[results.length - 1]);
  138. // TikHub 直连测试(如果提供了 TikHub token)
  139. if (tikhubToken) {
  140. console.log('── [额外] TikHub 直连测试 ──');
  141. results.push(await probe(
  142. 'TikHub 直连: Instagram search',
  143. `${TIKHUB_BASE}/api/v1/instagram/web_app/fetch_search_users_v1?keyword=nike&count=1`,
  144. { headers: { Authorization: `Bearer ${tikhubToken}` } }
  145. ));
  146. printResult(results[results.length - 1]);
  147. }
  148. // ─── 汇总报告 ───
  149. console.log('═══════════════════════════════════════════════════════════════');
  150. console.log(' 汇总');
  151. console.log('═══════════════════════════════════════════════════════════════');
  152. const pass = results.filter(r => r.ok).length;
  153. const softFail = results.filter(r => !r.ok && r.status > 0).length;
  154. const hardFail = results.filter(r => r.status === 0).length;
  155. console.log(` ✅ 可达 + 2xx: ${pass}`);
  156. console.log(` ⚠️ 可达但非 2xx: ${softFail} (通常是鉴权/参数问题,不是连通性问题)`);
  157. console.log(` ❌ 完全不可达: ${hardFail}`);
  158. console.log('');
  159. const avgMs = Math.round(results.filter(r => r.ms > 0).reduce((s, r) => s + r.ms, 0) / results.length);
  160. console.log(` 平均延迟: ${avgMs}ms`);
  161. console.log('');
  162. if (hardFail > 0) {
  163. console.log(' ❌ 存在完全不可达的端点 — 检查网络/DNS/防火墙');
  164. process.exitCode = 1;
  165. } else {
  166. console.log(' ✅ 所有端点 TCP/TLS/HTTP 层面都可达');
  167. }
  168. })();