// tests/voiceDiag.wan.test.js // // Unit coverage for the 8 WAN checks under services/voiceDiag/checks/wan/. // Same pattern as tests/voiceDiag.checks.test.js — build a stub // ctx with a `sdwanSite` + `sdwanData` (no HTTP, no Prisma) and // assert each check's verdict. // // Every check gets: // - happy path (data present, in the compliant range) // - threshold breach (warn and/or error where applicable) // - missing data (skipped with a clear reason) // - kill-switch skip (WAN_STANDARD_ENABLED=false) import test from 'node:test'; import assert from 'node:assert/strict'; import { wanSiteCheck } from '../services/voiceDiag/checks/wan/wanSite.js'; import { wanHealthscoreCheck } from '../services/voiceDiag/checks/wan/wanHealthscore.js'; import { wanLinkStateCheck } from '../services/voiceDiag/checks/wan/wanLinkState.js'; import { wanLatencyCheck } from '../services/voiceDiag/checks/wan/wanLatency.js'; import { wanJitterCheck } from '../services/voiceDiag/checks/wan/wanJitter.js'; import { wanLossCheck } from '../services/voiceDiag/checks/wan/wanLoss.js'; import { wanMosCheck } from '../services/voiceDiag/checks/wan/wanMos.js'; import { wanAlarmsCheck } from '../services/voiceDiag/checks/wan/wanAlarms.js'; import { CHECKS } from '../services/voiceDiag/checks/index.js'; // ─── Helpers ──────────────────────────────────────────────────────── function mkWanCtx({ site = { id: 'site-1', name: 'CG00782' }, links = [], healthscore = null, alarms = null, elements = [] } = {}) { return { storeNum: '782', personId: null, personLabel: 'store 782', telephonyProfile: {}, phoneStatus: null, sdwanSite: site, sdwanData: site ? { storeNum: '782', site, elements, links, healthscore, alarms: alarms || { last1h: { critical: 0, major: 0, minor: 0 }, samples: [] }, errors: [], } : null, }; } function link(overrides = {}) { return { interfaceId: 'if-mpls', interfaceName: 'wan1', elementId: 'el-1', elementName: 'ION-A', transportType: 'MPLS', up: true, latencyMs: 40, jitterMs: 5, lossPct: 0.1, mos: 4.4, ...overrides, }; } async function withKillSwitchOn(fn) { const prev = process.env.WAN_STANDARD_ENABLED; process.env.WAN_STANDARD_ENABLED = 'false'; try { await fn(); } finally { if (prev === undefined) delete process.env.WAN_STANDARD_ENABLED; else process.env.WAN_STANDARD_ENABLED = prev; } } // ─── wanSite ──────────────────────────────────────────────────────── test('wanSite: happy path → ok with site + element + link counts in message', async () => { const ctx = mkWanCtx({ elements: [{ id: 'el-1', connected: true }, { id: 'el-2', connected: false }], links: [link(), link({ interfaceId: 'if-bb' })], }); const r = await wanSiteCheck.run(ctx); assert.equal(r.status, 'ok'); assert.match(r.message, /CG00782/); assert.equal(r.details.elementCount, 2); assert.equal(r.details.connectedElementCount, 1); assert.equal(r.details.linkCount, 2); }); test('wanSite: kill-switch skips', async () => { await withKillSwitchOn(async () => { const r = await wanSiteCheck.run(mkWanCtx({ elements: [{ id: 'x', connected: true }] })); assert.equal(r.status, 'skipped'); assert.match(r.message, /WAN_STANDARD_ENABLED/); }); }); // ─── wanHealthscore ───────────────────────────────────────────────── test('wanHealthscore: 92/100 → ok', async () => { const r = await wanHealthscoreCheck.run(mkWanCtx({ healthscore: { value: 92, breakdown: {} } })); assert.equal(r.status, 'ok'); }); test('wanHealthscore: 70/100 → warn (< default warn 80)', async () => { const r = await wanHealthscoreCheck.run(mkWanCtx({ healthscore: { value: 70, breakdown: {} } })); assert.equal(r.status, 'warn'); assert.equal(r.details.value, 70); }); test('wanHealthscore: 40/100 → error (< default error 60)', async () => { const r = await wanHealthscoreCheck.run(mkWanCtx({ healthscore: { value: 40, breakdown: {} } })); assert.equal(r.status, 'error'); }); test('wanHealthscore: env override adjusts thresholds', async () => { const prev = process.env.WAN_STANDARD_HEALTHSCORE_WARN; process.env.WAN_STANDARD_HEALTHSCORE_WARN = '95'; try { const r = await wanHealthscoreCheck.run(mkWanCtx({ healthscore: { value: 92 } })); assert.equal(r.status, 'warn', '92 should warn once threshold is raised to 95'); } finally { if (prev === undefined) delete process.env.WAN_STANDARD_HEALTHSCORE_WARN; else process.env.WAN_STANDARD_HEALTHSCORE_WARN = prev; } }); test('wanHealthscore: missing → skipped', async () => { const r = await wanHealthscoreCheck.run(mkWanCtx({ healthscore: null })); assert.equal(r.status, 'skipped'); }); test('wanHealthscore: kill-switch skips', async () => { await withKillSwitchOn(async () => { const r = await wanHealthscoreCheck.run(mkWanCtx({ healthscore: { value: 40 } })); assert.equal(r.status, 'skipped'); assert.match(r.message, /WAN_STANDARD_ENABLED/); }); }); // ─── wanLinkState ─────────────────────────────────────────────────── test('wanLinkState: all up → ok', async () => { const r = await wanLinkStateCheck.run(mkWanCtx({ links: [link({ interfaceId: 'a', up: true }), link({ interfaceId: 'b', up: true })], })); assert.equal(r.status, 'ok'); assert.equal(r.details.up, 2); }); test('wanLinkState: one down → error, offender named', async () => { const r = await wanLinkStateCheck.run(mkWanCtx({ links: [ link({ interfaceId: 'a', up: true }), link({ interfaceId: 'b', up: false, interfaceName: 'bb1', transportType: 'BROADBAND' }), ], })); assert.equal(r.status, 'error'); assert.equal(r.details.down, 1); assert.match(r.message, /bb1/); }); test('wanLinkState: all unknown, none up → warn', async () => { const r = await wanLinkStateCheck.run(mkWanCtx({ links: [link({ up: null }), link({ interfaceId: 'x', up: null })], })); assert.equal(r.status, 'warn'); assert.equal(r.details.unknown, 2); }); test('wanLinkState: no links → skipped', async () => { const r = await wanLinkStateCheck.run(mkWanCtx({ links: [] })); assert.equal(r.status, 'skipped'); }); // ─── wanLatency ───────────────────────────────────────────────────── test('wanLatency: all under 150ms → ok', async () => { const r = await wanLatencyCheck.run(mkWanCtx({ links: [link({ latencyMs: 40 }), link({ latencyMs: 80 })] })); assert.equal(r.status, 'ok'); }); test('wanLatency: one path 220ms → warn (> 150, < 400)', async () => { const r = await wanLatencyCheck.run(mkWanCtx({ links: [link({ latencyMs: 40 }), link({ interfaceId: 'x', interfaceName: 'x1', latencyMs: 220 })], })); assert.equal(r.status, 'warn'); assert.match(r.message, /x1/); assert.match(r.message, /220ms/); }); test('wanLatency: one path 500ms → error (> 400)', async () => { const r = await wanLatencyCheck.run(mkWanCtx({ links: [link({ latencyMs: 40 }), link({ interfaceId: 'x', interfaceName: 'x1', latencyMs: 500 })], })); assert.equal(r.status, 'error'); }); test('wanLatency: env override adjusts thresholds', async () => { const prev = process.env.WAN_STANDARD_LATENCY_WARN_MS; process.env.WAN_STANDARD_LATENCY_WARN_MS = '50'; try { const r = await wanLatencyCheck.run(mkWanCtx({ links: [link({ latencyMs: 80 })] })); assert.equal(r.status, 'warn', '80ms should warn when threshold is 50'); } finally { if (prev === undefined) delete process.env.WAN_STANDARD_LATENCY_WARN_MS; else process.env.WAN_STANDARD_LATENCY_WARN_MS = prev; } }); test('wanLatency: no data → skipped', async () => { const r = await wanLatencyCheck.run(mkWanCtx({ links: [link({ latencyMs: null })] })); assert.equal(r.status, 'skipped'); }); test('wanLatency: kill-switch skips', async () => { await withKillSwitchOn(async () => { const r = await wanLatencyCheck.run(mkWanCtx({ links: [link({ latencyMs: 500 })] })); assert.equal(r.status, 'skipped'); }); }); // ─── wanJitter ────────────────────────────────────────────────────── test('wanJitter: 5ms → ok', async () => { const r = await wanJitterCheck.run(mkWanCtx({ links: [link({ jitterMs: 5 })] })); assert.equal(r.status, 'ok'); }); test('wanJitter: 40ms → warn (> 30, < 50)', async () => { const r = await wanJitterCheck.run(mkWanCtx({ links: [link({ jitterMs: 40 })] })); assert.equal(r.status, 'warn'); }); test('wanJitter: 100ms → error (> 50)', async () => { const r = await wanJitterCheck.run(mkWanCtx({ links: [link({ jitterMs: 100 })] })); assert.equal(r.status, 'error'); }); // ─── wanLoss ──────────────────────────────────────────────────────── test('wanLoss: 0.1% → ok', async () => { const r = await wanLossCheck.run(mkWanCtx({ links: [link({ lossPct: 0.1 })] })); assert.equal(r.status, 'ok'); }); test('wanLoss: 2% → warn (> 1, < 3)', async () => { const r = await wanLossCheck.run(mkWanCtx({ links: [link({ lossPct: 2 })] })); assert.equal(r.status, 'warn'); }); test('wanLoss: 5% → error (> 3)', async () => { const r = await wanLossCheck.run(mkWanCtx({ links: [link({ lossPct: 5 })] })); assert.equal(r.status, 'error'); }); // ─── wanMos ───────────────────────────────────────────────────────── // MOS is inverted — low is bad. test('wanMos: 4.4 → ok (>= 4.0)', async () => { const r = await wanMosCheck.run(mkWanCtx({ links: [link({ mos: 4.4 })] })); assert.equal(r.status, 'ok'); }); test('wanMos: 3.8 → warn (< 4.0, >= 3.5)', async () => { const r = await wanMosCheck.run(mkWanCtx({ links: [link({ mos: 3.8 })] })); assert.equal(r.status, 'warn'); }); test('wanMos: 3.0 → error (< 3.5)', async () => { const r = await wanMosCheck.run(mkWanCtx({ links: [link({ mos: 3.0 })] })); assert.equal(r.status, 'error'); }); // ─── wanAlarms ────────────────────────────────────────────────────── test('wanAlarms: none → ok', async () => { const r = await wanAlarmsCheck.run(mkWanCtx()); assert.equal(r.status, 'ok'); }); test('wanAlarms: minor only → ok (informational)', async () => { const r = await wanAlarmsCheck.run(mkWanCtx({ alarms: { last1h: { critical: 0, major: 0, minor: 3 }, samples: [] }, })); assert.equal(r.status, 'ok'); assert.equal(r.details.minor, 3); }); test('wanAlarms: major → warn (message cites humanized code, never raw info blob)', async () => { const r = await wanAlarmsCheck.run(mkWanCtx({ alarms: { last1h: { critical: 0, major: 1, minor: 0 }, samples: [{ code: 'NETWORK_ANYNETLINK_DOWN', severity: 'major', message: '{"vpn_reasons":[{"code":"NETWORK_VPNLINK_DOWN","element_id":"…"}]}', ts: new Date().toISOString(), }], }, })); assert.equal(r.status, 'warn'); // Humanized code appears — no raw JSON blob. assert.match(r.message, /SD-WAN overlay tunnel down/); assert.equal(r.message.includes('vpn_reasons'), false, 'raw Prisma info JSON must never leak into the chat message'); assert.equal(r.message.includes('{"'), false, 'no stringified object leakage'); // Category count captured in details. assert.equal(r.details.byCategory.overlay, 1); }); test('wanAlarms: critical → error, cites the highest-severity rollup', async () => { const r = await wanAlarmsCheck.run(mkWanCtx({ alarms: { last1h: { critical: 2, major: 0, minor: 0 }, samples: [ { code: 'DEVICE_UNREACHABLE', severity: 'critical', ts: '2026-07-09T14:00:00Z' }, { code: 'DEVICE_UNREACHABLE', severity: 'critical', ts: '2026-07-09T14:05:00Z' }, ], }, })); assert.equal(r.status, 'error'); assert.match(r.message, /2 critical alarms/); assert.match(r.message, /ION unreachable/); assert.match(r.message, /×2/); }); test('wanAlarms: rollup groups near-identical alarms in details.recentSamples', async () => { const samples = Array.from({ length: 20 }).map((_, i) => ({ code: 'NETWORK_ANYNETLINK_DOWN', severity: 'major', ts: new Date(Date.now() - i * 60_000).toISOString(), })); const r = await wanAlarmsCheck.run(mkWanCtx({ alarms: { last1h: { critical: 0, major: 20, minor: 0 }, samples }, })); assert.equal(r.status, 'warn'); assert.equal(r.details.recentSamples.length, 1, '20 identical alarms should collapse to a single rolled-up row'); assert.equal(r.details.recentSamples[0].count, 20); assert.equal(r.details.recentSamples[0].category, 'overlay'); assert.equal(r.details.recentSamples[0].humanized, 'SD-WAN overlay tunnel down'); }); test('wanAlarms: overlay-only + all physical links up → adds clarifying hint', async () => { const r = await wanAlarmsCheck.run(mkWanCtx({ links: [link(), link({ interfaceId: 'if-bb', up: true })], alarms: { last1h: { critical: 0, major: 5, minor: 0 }, samples: Array.from({ length: 5 }).map(() => ({ code: 'NETWORK_VPNLINK_DOWN', severity: 'major', })), }, })); assert.equal(r.status, 'warn'); assert.match(r.message, /physical WAN paths are all up/); }); test('wanAlarms: physical alarms + a physical link down → different clarifier', async () => { const r = await wanAlarmsCheck.run(mkWanCtx({ links: [link({ up: false })], alarms: { last1h: { critical: 1, major: 0, minor: 0 }, samples: [{ code: 'NETWORK_INTERNET_DOWN', severity: 'critical' }], }, })); assert.equal(r.status, 'error'); assert.match(r.message, /Physical WAN interfaces are affected/); }); test('wanAlarms: no alarms feed → skipped', async () => { const ctx = mkWanCtx(); ctx.sdwanData.alarms = null; const r = await wanAlarmsCheck.run(ctx); assert.equal(r.status, 'skipped'); }); test('wanAlarms: message reflects the effective alarm window (not hardcoded 1h)', async () => { const ctx = mkWanCtx({ alarms: { last1h: { critical: 0, major: 1, minor: 0 }, samples: [{ code: 'NETWORK_ANYNETLINK_DOWN', severity: 'major' }], }, }); // Simulate a 24h alarm window (matches the shipping default) ctx.sdwanData.window = { minutes: 1440, alarmMinutes: 1440 }; const r = await wanAlarmsCheck.run(ctx); assert.match(r.message, /in the last 1d/); }); // ─── standards regression + registry ordering ────────────────────── test('every WAN check exposes a standards object', () => { const wanChecks = CHECKS.filter((c) => c.id.startsWith('wan')); assert.equal(wanChecks.length, 8, 'expected 8 registered WAN checks'); const missing = wanChecks.filter((c) => !c.standards || typeof c.standards !== 'object'); assert.deepEqual(missing.map((c) => c.id), []); }); test('WAN checks registered in the expected order after port bucket', () => { const ids = CHECKS.map((c) => c.id); const wanIds = ids.filter((id) => id.startsWith('wan')); assert.deepEqual(wanIds, [ 'wanSite', 'wanHealthscore', 'wanLinkState', 'wanLatency', 'wanJitter', 'wanLoss', 'wanMos', 'wanAlarms', ]); // WAN bucket sits after the port bucket and before phoneOnline. const portEnabledIdx = ids.indexOf('portEnabled'); const wanSiteIdx = ids.indexOf('wanSite'); const phoneOnlineIdx = ids.indexOf('phoneOnline'); assert.ok(portEnabledIdx < wanSiteIdx); assert.ok(wanSiteIdx < phoneOnlineIdx); }); test('no WAN check declares a remediation (diagnostic-only)', () => { const wanChecks = CHECKS.filter((c) => c.id.startsWith('wan')); for (const c of wanChecks) { assert.equal(c.remediations, undefined, `${c.id} should not expose remediations`); } });