/** * QmtHealthMonitor 自适应探测 回归测试(R-022 / 迭代 19,2026-09-09) * * 运行:node scripts/test-health-monitor.mjs * 隔离:纯内存 mock(mock fetch + mock settings scope),用短间隔参数测真实 setTimeout 调度。 * 背景:原实现固定 5 分钟探测,QMT Bridge 恢复后指示灯要等满 5 分钟(老师反馈「自动检查没生效」); * 本测试守护「异常/未知 → 快速重试,健康 → 稳态间隔」的自适应节奏。 */ 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 { QmtHealthMonitor } = await import('../src/data-source/QmtHealthMonitor.js'); /** 可编程 settings scope(resolveActiveBaseUrl 依赖 scope.get() → qmtConnections) */ function mockSettings(baseUrl) { return { get: () => ({ qmtConnections: { list: [{ id: 'c1', name: 'bridge', baseUrl, order: 1 }], activeId: 'c1', defaultId: '', }, }), }; } const BRIDGE_URL = 'http://bridge.test:8610'; /** 可编程 fetch mock:bridgeUp=false 时抛连接错误 */ function mockFetch() { const calls = []; let up = true; globalThis.fetch = async (url) => { calls.push({ url: String(url), t: Date.now() }); if (!up) throw new Error('fetch failed: ECONNREFUSED bridge down'); return { ok: true, status: 200 }; }; return { calls, set up(v) { up = v; }, get up() { return up; }, }; } const RETRY_MS = 40; const INTERVAL_MS = 300; console.log('[1] 启动即探测 + 缓存读取(健康)'); { const fetch = mockFetch(); const monitor = new QmtHealthMonitor({ runtime: { settings: mockSettings(BRIDGE_URL), dataSource: { baseUrl: BRIDGE_URL } }, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); await monitor.probe(); // 等价 start() 的立即探测 ok(fetch.calls.length === 1, '启动探测发起 1 次'); ok(fetch.calls[0].url === BRIDGE_URL + '/health', '探测地址 = 激活 baseUrl + /health'); const st = monitor.getStatus(); ok(st.healthy === true && st.fromCache === true, '缓存 healthy=true 且 fromCache=true'); await monitor.stop(); } console.log('[2] 异常 → 快速重试自动恢复(核心回归:Bridge 启动后灯 ≤ 重试间隔回绿)'); { const fetch = mockFetch(); fetch.up = false; const monitor = new QmtHealthMonitor({ runtime: { settings: mockSettings(BRIDGE_URL), dataSource: { baseUrl: BRIDGE_URL } }, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); monitor.start(); await sleep(RETRY_MS * 3); // 快速重试窗口内应已多次探测 ok(fetch.calls.length >= 2, '异常态多次快速重试(' + fetch.calls.length + ' 次 ≥ 2)'); ok(monitor.getStatus().healthy === false, '异常缓存 healthy=false'); fetch.up = true; // 模拟 QMT Bridge 启动 const before = fetch.calls.length; await sleep(RETRY_MS * 4); // 等下一个快速重试命中 const st = monitor.getStatus(); ok(fetch.calls.length > before, '恢复后被自动重探测到'); ok(st.healthy === true, '恢复后缓存 healthy=true(灯将回绿,未等 5 分钟稳态间隔)'); await monitor.stop(); } console.log('[3] 健康稳态:回落到长间隔,不空转'); { const fetch = mockFetch(); const monitor = new QmtHealthMonitor({ runtime: { settings: mockSettings(BRIDGE_URL), dataSource: { baseUrl: BRIDGE_URL } }, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); monitor.start(); await sleep(RETRY_MS * 4); // 小于稳态间隔:健康态不应按重试节奏空转 ok(fetch.calls.length === 1, '健康态在稳态间隔内只探测 1 次(无快速空转)'); await sleep(INTERVAL_MS + RETRY_MS * 2); // 跨过稳态间隔 ok(fetch.calls.length === 2, '跨过稳态间隔后又探测 1 次(共 2 次)'); await monitor.stop(); } console.log('[4] stop() 后停止自动调度'); { const fetch = mockFetch(); fetch.up = false; const monitor = new QmtHealthMonitor({ runtime: { settings: mockSettings(BRIDGE_URL), dataSource: { baseUrl: BRIDGE_URL } }, intervalMs: INTERVAL_MS, retryMs: RETRY_MS, }); monitor.start(); await sleep(RETRY_MS * 2); const before = fetch.calls.length; monitor.stop(); fetch.up = true; await sleep(RETRY_MS * 4); ok(fetch.calls.length === before, 'stop 后无自动探测'); } console.log('----'); console.log(passed + ' passed, ' + failed + ' failed'); if (failed > 0) process.exit(1);