/** * QmtMcpManager 状态自适应探测 回归测试(R-023 / 迭代 19,2026-09-09) * * 运行:node scripts/test-mcp-status.mjs * 说明:SDK 握手(initialize/tools.list)依赖真实网络端点,无法纯内存 mock; * 本测试守护的是【调度机制】——probe 结果驱动 setTimeout 自适应链: * connected → intervalMs 稳态;非 connected → retryMs 快速重试; * 无激活连接不调度;dispose 停止调度。 * 用 stub probe(记录调用 + 翻转状态 + 触达真实 _scheduleNext)验证自动恢复数据流; * 无激活连接场景用真实 probe(早退路径不触 SDK)。 */ let passed = 0; let failed = 0; function ok(cond, name) { if (cond) { passed++; console.log(' ✓ ' + name); } else { failed++; console.error(' ✗ ' + name); } } const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const { QmtMcpManager } = await import('../src/mcp/QmtMcpManager.js'); const RETRY_MS = 50; const INTERVAL_MS = 350; const BRIDGE_URL = 'http://bridge.test:8610'; /** settings scope:baseUrl 传 '' = 无激活连接 */ function mockSettings(baseUrl) { return { get: () => ({ qmtConnections: { list: baseUrl ? [{ id: 'c1', name: 'bridge', baseUrl, order: 1 }] : [], activeId: baseUrl ? 'c1' : '', defaultId: '', }, }), }; } const quietLogger = { info() {}, warn() {}, debug() {} }; console.log('[1] 无激活连接:置 disabled 且不调度(真实 probe 早退,不触 SDK)'); { const mgr = new QmtMcpManager({ ctx: {}, runtime: { settings: mockSettings(''), dataSource: {} }, logger: quietLogger, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); let probeCalls = 0; const orig = mgr.probe.bind(mgr); mgr.probe = async (u) => { probeCalls++; return orig(u); }; await mgr.start(); ok(mgr.getStatus().state === 'disabled', '无激活连接 → 状态 disabled'); await sleep(RETRY_MS * 4); ok(probeCalls === 1, '无激活连接无定时探测(仅 start 一次)'); await mgr.dispose(); } console.log('[2] 异常 → 快速重试自动恢复(核心回归:MCP 服务恢复后灯 ≤ 重试间隔回绿)'); { const mgr = new QmtMcpManager({ ctx: {}, runtime: { settings: mockSettings(BRIDGE_URL), dataSource: {} }, logger: quietLogger, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); let bridgeUp = false; let probeCalls = 0; mgr.probe = async () => { probeCalls++; mgr.status = bridgeUp ? { state: 'connected', serverName: 'QMT_Bridge_MCP', url: BRIDGE_URL + '/mcp', mounted: true, toolCount: 9, error: null, latencyMs: 3, checkedAt: Date.now() } : { state: 'error', serverName: 'QMT_Bridge_MCP', url: BRIDGE_URL + '/mcp', mounted: true, toolCount: 0, error: 'ECONNREFUSED', latencyMs: 3, checkedAt: Date.now() }; mgr._scheduleNext(); // 等价真实 probe 尾部 return mgr.status; }; await mgr.probe(); // 启动首次探测(Bridge 未启动) ok(mgr.getStatus().state === 'error', 'Bridge 未启动 → 状态 error'); await sleep(RETRY_MS * 3); ok(probeCalls >= 3, '异常态按 retryMs 快速重试(' + probeCalls + ' 次 ≥ 3)'); bridgeUp = true; // 模拟 MCP 服务启动 await sleep(RETRY_MS * 3); ok(mgr.getStatus().state === 'connected', '恢复后被自动探测到 → connected(灯将回绿,未等 5 分钟稳态间隔)'); await mgr.dispose(); } console.log('[3] 已连接稳态:回落到长间隔,不空转'); { const mgr = new QmtMcpManager({ ctx: {}, runtime: { settings: mockSettings(BRIDGE_URL), dataSource: {} }, logger: quietLogger, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); let probeCalls = 0; mgr.probe = async () => { probeCalls++; mgr.status = { state: 'connected', serverName: 'QMT_Bridge_MCP', url: BRIDGE_URL + '/mcp', mounted: true, toolCount: 9, error: null, latencyMs: 3, checkedAt: Date.now() }; mgr._scheduleNext(); return mgr.status; }; await mgr.probe(); ok(probeCalls === 1 && mgr.getStatus().state === 'connected', '首探测 connected'); await sleep(RETRY_MS * 3); // 小于稳态间隔、大于重试节奏 ok(probeCalls === 1, '已连接态在稳态间隔内不按重试节奏空转'); await sleep(INTERVAL_MS + RETRY_MS * 2); // 跨过稳态间隔 ok(probeCalls === 2, '跨过稳态间隔后又探测 1 次'); await mgr.dispose(); } console.log('[4] dispose() 停止自动调度'); { const mgr = new QmtMcpManager({ ctx: {}, runtime: { settings: mockSettings(BRIDGE_URL), dataSource: {} }, logger: quietLogger, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); let bridgeUp = false; let probeCalls = 0; mgr.probe = async () => { probeCalls++; mgr.status = bridgeUp ? { state: 'connected', serverName: 'QMT_Bridge_MCP', url: BRIDGE_URL + '/mcp', mounted: true, toolCount: 9, error: null, latencyMs: 3, checkedAt: Date.now() } : { state: 'error', serverName: 'QMT_Bridge_MCP', url: BRIDGE_URL + '/mcp', mounted: true, toolCount: 0, error: 'ECONNREFUSED', latencyMs: 3, checkedAt: Date.now() }; mgr._scheduleNext(); return mgr.status; }; await mgr.probe(); await sleep(RETRY_MS * 2); const before = probeCalls; await mgr.dispose(); bridgeUp = true; await sleep(RETRY_MS * 4); ok(probeCalls === before, 'dispose 后无自动探测'); } console.log('[5] _nextDelay 节奏选择'); { const mgr = new QmtMcpManager({ ctx: {}, runtime: { settings: mockSettings(BRIDGE_URL), dataSource: {} }, logger: quietLogger, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); mgr.status = { state: 'connected' }; ok(mgr._nextDelay() === INTERVAL_MS, 'connected → intervalMs'); mgr.status = { state: 'error' }; ok(mgr._nextDelay() === RETRY_MS, 'error → retryMs'); mgr.status = mgr._disabledStatus(); ok(mgr._nextDelay() === RETRY_MS, 'disabled → retryMs(无连接时 probe 早退不调度)'); } console.log('----'); console.log(passed + ' passed, ' + failed + ' failed'); if (failed > 0) process.exit(1);