Wire CP-78xx probe discovery, relay phone-probe commands, and a chat follow-up message so store desk phones get registration, switch, and provisioning detail alongside DECT and WAN diagnostics. Co-authored-by: Cursor <cursoragent@cursor.com>
593 lines
22 KiB
JavaScript
593 lines
22 KiB
JavaScript
// Golden-ish tests for services/renderers/{phone,av}StatusRenderer.js
|
|
//
|
|
// True byte-for-byte golden strings aren't practical because both
|
|
// renderers call `simpleTimeAgo` (which uses `Date.now()`). Instead we
|
|
// assert on structural properties AND on the exact assembly of key
|
|
// lines — enough to catch a regression like "we lost the Meraki
|
|
// `[Meraki↗](url)` link" or "the header format changed" but robust to
|
|
// time-of-day drift.
|
|
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import {
|
|
renderPhoneStatusMarkdown,
|
|
renderDectDiagnosticsMarkdown,
|
|
} from '../services/renderers/phoneStatusRenderer.js';
|
|
import { renderAvStatusMarkdown } from '../services/renderers/avStatusRenderer.js';
|
|
import { renderDectStatusMarkdown } from '../services/renderers/dectStatusRenderer.js';
|
|
import { buildHandsetContext } from '../services/dectStatus/buildHandsetContext.js';
|
|
|
|
// Timestamp exactly 3 hours in the past — makes `simpleTimeAgo`
|
|
// deterministic to "3 hours ago" for the duration of this test run.
|
|
const threeHrAgo = () => new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString();
|
|
|
|
test('phone renderer: header and empty-store message', () => {
|
|
const md = renderPhoneStatusMarkdown({}, { storeNum: '782' });
|
|
assert.match(md, /^\*\*Phone Status - Store 782\*\*/);
|
|
assert.match(md, /No phones or DECT basestations found/);
|
|
});
|
|
|
|
test('phone renderer: full desk-phone entry preserves Meraki link', () => {
|
|
const data = {
|
|
phones: {
|
|
data: [{
|
|
displayName: 'PHONE 782-1',
|
|
status: 'connected',
|
|
lastSeen: threeHrAgo(),
|
|
firmware: '12.0.4',
|
|
serial: 'ABC12345',
|
|
meraki: {
|
|
switchName: 'STORE-782-SW1',
|
|
status: 'Online',
|
|
port: '17',
|
|
vlan: '20',
|
|
ip: '10.1.1.5',
|
|
lastSeen: threeHrAgo(),
|
|
clientUrl: 'https://n123.meraki.com/example/manage/clients/abc/overview',
|
|
usage: { sent: 1024, recv: 4096 },
|
|
},
|
|
}],
|
|
},
|
|
telephonyProfile: { timeZone: 'America/New_York' },
|
|
locationMainNumber: '+14125550100',
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
// Header lines the poller cares about
|
|
assert.match(md, /\*\*Timezone:\*\* America\/New_York/);
|
|
assert.match(md, /\*\*PhoneNumber:\*\* \+14125550100/);
|
|
// Bold device name
|
|
assert.match(md, /\*\*PHONE 782-1\*\*/);
|
|
// Firmware / serial line
|
|
assert.match(md, /FW: 12\.0\.4 • Serial: ABC12345/);
|
|
// Meraki port info + link — the whole reason we ripped out the codeBlock
|
|
assert.match(md, /\*\*STORE-782-SW1\*\*/);
|
|
assert.match(md, /\[Meraki↗\]\(https:\/\/n123\.meraki\.com\/example\/manage\/clients\/abc\/overview\)/);
|
|
// Data usage
|
|
assert.match(md, /Data \(recent\): 1 KB sent \/ 4 KB recv/);
|
|
// No footer when opts.footer is false — Jira uses this mode
|
|
assert.doesNotMatch(md, /Last checked/);
|
|
});
|
|
|
|
test('phone renderer: footer appears when opts.footer=true (default) on non-empty data', () => {
|
|
// The empty-store branch legitimately returns early with no footer
|
|
// — that matches the chat handler's historical behavior. Feed a
|
|
// minimal populated fixture to exercise the footer path.
|
|
const data = {
|
|
phones: {
|
|
data: [{ displayName: 'X', status: 'connected', lastSeen: threeHrAgo() }],
|
|
},
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, { storeNum: '782' });
|
|
assert.match(md, /Last checked:/);
|
|
});
|
|
|
|
test('phone renderer: detailed mode reveals SIP details', () => {
|
|
const data = {
|
|
phones: {
|
|
data: [{
|
|
displayName: 'PHONE 782-2',
|
|
status: 'connected',
|
|
lastSeen: threeHrAgo(),
|
|
primarySipUrl: 'sip:782-2@aeo2go.webex.com',
|
|
sipUrls: ['sip:a@x', 'sip:b@x', 'sip:c@x'],
|
|
}],
|
|
},
|
|
};
|
|
const compact = renderPhoneStatusMarkdown(data, { storeNum: '782', detailed: false, footer: false });
|
|
const detailed = renderPhoneStatusMarkdown(data, { storeNum: '782', detailed: true, footer: false });
|
|
assert.doesNotMatch(compact, /SIP: sip:782-2/);
|
|
assert.match(detailed, /SIP: sip:782-2@aeo2go\.webex\.com/);
|
|
assert.match(detailed, /Alt SIPs: sip:a@x, sip:b@x…/);
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// IGMP snooping / DECT-safe multicast warning line
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
test('phone renderer: multicast warning line includes every deviation kind', () => {
|
|
const data = {
|
|
dectBasestations: [
|
|
{ mac: 'aa:bb:cc:dd:ee:ff', meraki: { status: 'Online' } },
|
|
],
|
|
dectHandsets: [],
|
|
multicast: {
|
|
needsFix: true,
|
|
defaultSnoopOn: true,
|
|
defaultFloodOff: true,
|
|
deviatingOverrides: [{}, {}],
|
|
},
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
assert.match(md, /\*\*Multicast:\*\* ⚠️/);
|
|
assert.match(md, /IGMP snoop=ON default/);
|
|
assert.match(md, /flood-unknown=OFF default/);
|
|
assert.match(md, /2 switch override\(s\) deviate/);
|
|
assert.match(md, /may disrupt DECT/);
|
|
});
|
|
|
|
test('phone renderer: multicast line names only the snoop deviation when that is the only problem', () => {
|
|
const data = {
|
|
dectBasestations: [
|
|
{ mac: 'aa:bb:cc:dd:ee:ff', meraki: { status: 'Online' } },
|
|
],
|
|
dectHandsets: [],
|
|
multicast: {
|
|
needsFix: true,
|
|
defaultSnoopOn: true,
|
|
defaultFloodOff: false,
|
|
deviatingOverrides: [],
|
|
},
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
assert.match(md, /\*\*Multicast:\*\* ⚠️ IGMP snoop=ON default — may disrupt DECT/);
|
|
assert.doesNotMatch(md, /flood-unknown/);
|
|
assert.doesNotMatch(md, /switch override/);
|
|
});
|
|
|
|
test('phone renderer: multicast line is ABSENT when needsFix is false', () => {
|
|
const data = {
|
|
dectBasestations: [
|
|
{ mac: 'aa:bb:cc:dd:ee:ff', meraki: { status: 'Online' } },
|
|
],
|
|
dectHandsets: [],
|
|
multicast: {
|
|
needsFix: false,
|
|
defaultSnoopOn: false,
|
|
defaultFloodOff: false,
|
|
deviatingOverrides: [],
|
|
},
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
assert.doesNotMatch(md, /\*\*Multicast:\*\*/);
|
|
});
|
|
|
|
test('phone renderer: multicast line is ABSENT when there are no DECT basestations', () => {
|
|
// Even if needsFix is true, the warning lives INSIDE the DECT section
|
|
// which itself is gated on dectBasestations.length > 0. Stores with
|
|
// no DECT get no multicast noise — it's simply not their problem.
|
|
const data = {
|
|
phones: {
|
|
data: [{ displayName: 'DESK', status: 'connected', lastSeen: threeHrAgo() }],
|
|
},
|
|
dectBasestations: [],
|
|
multicast: {
|
|
needsFix: true,
|
|
defaultSnoopOn: true,
|
|
defaultFloodOff: true,
|
|
deviatingOverrides: [{}],
|
|
},
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
assert.doesNotMatch(md, /\*\*Multicast:\*\*/);
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
test('av renderer: header always says (Mode: detailed)', () => {
|
|
const md = renderAvStatusMarkdown({}, { storeNum: '782', footer: false });
|
|
assert.match(md, /^\*\*Device Status - Store 782\*\* \(Mode: detailed\)/);
|
|
});
|
|
|
|
test('av renderer: MDM entry with Meraki wired client preserves link', () => {
|
|
const data = {
|
|
mdm: {
|
|
data: [{
|
|
friendlyName: 'STORE-782-KIOSK-1',
|
|
lastSeen: threeHrAgo(),
|
|
meraki: {
|
|
deviceName: 'STORE-782-SW2',
|
|
recentDeviceConnection: 'Wired',
|
|
status: 'Online',
|
|
clientStatus: 'Online',
|
|
port: '4',
|
|
vlan: '30',
|
|
ip: '10.1.2.20',
|
|
mac: 'aa:bb:cc:dd:ee:ff',
|
|
lastSeen: threeHrAgo(),
|
|
clientUrl: 'https://n123.meraki.com/example/manage/clients/xyz/overview',
|
|
},
|
|
}],
|
|
},
|
|
};
|
|
const md = renderAvStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
assert.match(md, /\*\*STORE-782-KIOSK-1\*\*/);
|
|
assert.match(md, /\*\*STORE-782-SW2 \(Wired - Online\)\*\*/);
|
|
assert.match(md, /\[Meraki↗\]\(https:\/\/n123\.meraki\.com\/example\/manage\/clients\/xyz\/overview\)/);
|
|
// Port line with double-arrow indent
|
|
assert.match(md, / → → Port: \*\*4\*\*/);
|
|
});
|
|
|
|
test('av renderer: absent MDM devices prints friendly message', () => {
|
|
const md = renderAvStatusMarkdown({ mdm: { data: [] } }, { storeNum: '782', footer: false });
|
|
assert.match(md, /No MDM devices found for this store/);
|
|
});
|
|
|
|
test('av renderer: Atlas AMP with vitals renders temps + fan + amps', () => {
|
|
const data = {
|
|
mdm: { data: [] },
|
|
atlas: {
|
|
data: [{
|
|
name: 'US000782AMP',
|
|
status: 'online',
|
|
last_seen_at: new Date(Date.now() - 5 * 60 * 1000).toISOString(),
|
|
model: { name: 'Atlas-4M' },
|
|
firmware: { version: '1.9.3' },
|
|
sn: 'SN-782-AMP',
|
|
state: {
|
|
voltageMonitor: '120.4',
|
|
faultStatus: 0,
|
|
tempCpu: '40', tempPsu: '35', tempIo: '38',
|
|
fanSpeed: 45.7,
|
|
ampStatus_1: 'Active',
|
|
ampStatus_2: 'Ready',
|
|
IpAddress: '10.1.9.9',
|
|
},
|
|
}],
|
|
},
|
|
};
|
|
const md = renderAvStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
assert.match(md, /\*\*Atlas Devices:\*\*/);
|
|
assert.match(md, /\*\*US000782AMP\*\* \(online\)/);
|
|
assert.match(md, /Atlas-4M • FW 1\.9\.3 • SN SN-782-AMP • IP 10\.1\.9\.9/);
|
|
assert.match(md, /CPU: 104°F • PSU: 95°F • Io: 100°F • Voltage: 120\.4V • Fan: 46%/);
|
|
assert.match(md, /Amps: Amp1: Active, Amp2: Ready/);
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// DECT follow-up "loading" hint (main /phonestatus output)
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
test('phone renderer: dectFollowUpBaseCount > 0 emits a loading hint inside the DECT section', () => {
|
|
const data = {
|
|
dectBasestations: [
|
|
{ mac: 'aa:bb:cc:dd:ee:01', meraki: { status: 'Online' } },
|
|
{ mac: 'aa:bb:cc:dd:ee:02', meraki: { status: 'Online' } },
|
|
],
|
|
dectHandsets: [],
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, {
|
|
storeNum: '782', footer: false, dectFollowUpBaseCount: 2,
|
|
});
|
|
assert.match(md, /Base-station diagnostics loading for 2 bases/);
|
|
});
|
|
|
|
test('phone renderer: dectFollowUpBaseCount === 1 uses singular "base"', () => {
|
|
const data = {
|
|
dectBasestations: [{ mac: 'aa:bb:cc:dd:ee:01', meraki: { status: 'Online' } }],
|
|
dectHandsets: [],
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, {
|
|
storeNum: '782', footer: false, dectFollowUpBaseCount: 1,
|
|
});
|
|
assert.match(md, /loading for 1 base —/);
|
|
});
|
|
|
|
test('phone renderer: dectFollowUpBaseCount === 0 emits no loading hint (default state)', () => {
|
|
const data = {
|
|
dectBasestations: [{ mac: 'aa:bb:cc:dd:ee:01', meraki: { status: 'Online' } }],
|
|
dectHandsets: [],
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, { storeNum: '782', footer: false });
|
|
assert.doesNotMatch(md, /diagnostics loading/);
|
|
});
|
|
|
|
test('phone renderer: mppFollowUpPhoneCount > 0 emits MPP loading hint', () => {
|
|
const data = {
|
|
phones: { data: [{ name: 'Store CP-7841', status: 'connected', lastSeen: new Date().toISOString() }] },
|
|
dectBasestations: [],
|
|
dectHandsets: [],
|
|
};
|
|
const md = renderPhoneStatusMarkdown(data, {
|
|
storeNum: '782', footer: false, mppFollowUpPhoneCount: 1,
|
|
});
|
|
assert.match(md, /MPP phone diagnostics loading for 1 desk phone/);
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// DECT follow-up message (renderDectDiagnosticsMarkdown)
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
const okResult = (overrides = {}) => ({
|
|
base: { mac: '6c:ab:05:f6:28:19', ip: '10.4.11.87', name: 'Basestation A' },
|
|
ok: true,
|
|
data: {
|
|
time: { operatingTime: '02:15:00 (H:M:S)' },
|
|
firmware: { version: '05-01-03-0101-09' },
|
|
multiCell: { role: 'primary' },
|
|
conflictInfo: 'No Conflict',
|
|
rebootLog: [],
|
|
rtp: { current: 0 },
|
|
},
|
|
verdict: { healthy: true, warnings: [], info: [] },
|
|
elapsedMs: 812,
|
|
...overrides,
|
|
});
|
|
|
|
test('dect diagnostics renderer: empty input returns empty string (caller should not send)', () => {
|
|
assert.equal(renderDectDiagnosticsMarkdown([], { storeNum: '782' }), '');
|
|
assert.equal(renderDectDiagnosticsMarkdown(null, { storeNum: '782' }), '');
|
|
});
|
|
|
|
test('dect diagnostics renderer: healthy base renders check + uptime + firmware', () => {
|
|
const md = renderDectDiagnosticsMarkdown([okResult()], { storeNum: '782', footer: false });
|
|
assert.match(md, /\*\*DECT Base Station Diagnostics — Store 782\*\*/);
|
|
assert.match(md, /✅ \*\*Basestation A\*\* \(10\.4\.11\.87\)/);
|
|
assert.match(md, /uptime 02:15:00/);
|
|
assert.match(md, /fw 05-01-03-0101-09/);
|
|
assert.match(md, /role: primary/);
|
|
});
|
|
|
|
test('dect diagnostics renderer: warnings from verdict are surfaced under the header', () => {
|
|
const md = renderDectDiagnosticsMarkdown([
|
|
okResult({
|
|
verdict: {
|
|
healthy: false,
|
|
warnings: ['Rx errors: 42 since last boot'],
|
|
info: [],
|
|
},
|
|
}),
|
|
], { storeNum: '782', footer: false });
|
|
assert.match(md, /⚠️ \*\*Basestation A\*\*/);
|
|
assert.match(md, /⚠️ Rx errors: 42 since last boot/);
|
|
});
|
|
|
|
test('dect diagnostics renderer: recent Power Loss reboot gets its own bolt line + suppresses duplicate warning', () => {
|
|
const md = renderDectDiagnosticsMarkdown([
|
|
okResult({
|
|
data: {
|
|
time: { operatingTime: '02:15:00' },
|
|
firmware: { version: '05-01-03-0101-09' },
|
|
multiCell: { role: 'primary' },
|
|
conflictInfo: 'No Conflict',
|
|
rebootLog: [
|
|
{ sequence: 164, at: '2026-07-02T12:54:12', reasonName: 'Power Loss', reasonCode: 80 },
|
|
],
|
|
rtp: { current: 0 },
|
|
},
|
|
verdict: {
|
|
healthy: false,
|
|
warnings: ['1 recent power-loss reboot(s); most recent at 2026-07-02T12:54:12'],
|
|
info: [],
|
|
},
|
|
}),
|
|
], { storeNum: '782', footer: false });
|
|
|
|
// The structured line survives …
|
|
assert.match(md, /⚡ Recent power loss: 2026-07-02T12:54:12 \(reboot #164\)/);
|
|
// … but the summary warning about power-loss is filtered out to
|
|
// avoid duplication under the same header.
|
|
assert.doesNotMatch(md, /⚠️ 1 recent power-loss/);
|
|
});
|
|
|
|
test('dect diagnostics renderer: active RTP session gets a call icon', () => {
|
|
const md = renderDectDiagnosticsMarkdown([
|
|
okResult({
|
|
data: {
|
|
time: { operatingTime: '02:15:00' },
|
|
firmware: { version: '05-01-03-0101-09' },
|
|
multiCell: { role: 'primary' },
|
|
conflictInfo: 'No Conflict',
|
|
rebootLog: [],
|
|
rtp: { current: 2 },
|
|
},
|
|
}),
|
|
], { storeNum: '782', footer: false });
|
|
assert.match(md, /📞 2 active RTP session/);
|
|
});
|
|
|
|
test('dect diagnostics renderer: base with error renders remediation hint', () => {
|
|
const md = renderDectDiagnosticsMarkdown([
|
|
{
|
|
base: { mac: '6c:ab:05:f6:28:19', ip: '10.4.11.87', name: 'Basestation A' },
|
|
ok: false,
|
|
data: null,
|
|
verdict: null,
|
|
elapsedMs: 15003,
|
|
error: {
|
|
code: 'RELAY_RPC_TIMEOUT',
|
|
message: 'timed out after 15000ms',
|
|
hint: 'Relay accepted the request but the base did not respond in time.',
|
|
},
|
|
},
|
|
], { storeNum: '782', footer: false });
|
|
assert.match(md, /⚠️ \*\*Basestation A\*\* \(10\.4\.11\.87\) — collect failed: timed out after 15000ms/);
|
|
assert.match(md, /Relay accepted the request but the base did not respond in time\./);
|
|
});
|
|
|
|
test('dect diagnostics renderer: footer references /dectstatus command by store', () => {
|
|
const md = renderDectDiagnosticsMarkdown([okResult()], { storeNum: '782' });
|
|
assert.match(md, /Use `\/dectstatus 782`/);
|
|
});
|
|
|
|
// ─────────────────────────────────────────────────────────────
|
|
// Full /dectstatus dump (renderDectStatusMarkdown)
|
|
// ─────────────────────────────────────────────────────────────
|
|
|
|
const fullOkResult = () => ({
|
|
base: { mac: '6c:ab:05:f6:28:19', ip: '10.4.11.87', name: 'Basestation A' },
|
|
ok: true,
|
|
data: {
|
|
device: {
|
|
model: 'DBS-210-3PC',
|
|
macAddress: '6c:ab:05:f6:28:19',
|
|
ipAddress: '10.4.11.87',
|
|
rfpiAddress: '13508C9C; RPN:00',
|
|
},
|
|
firmware: { version: 'IPDECT-V2/05-01-03-0101-09' },
|
|
time: { operatingTime: '02:15:00 (H:M:S)', currentLocalTime: '02-Jul-2026 14:00:36' },
|
|
multiCell: { role: 'primary' },
|
|
conflictInfo: 'No Conflict',
|
|
baseStatus: 'ok',
|
|
rebootLog: [
|
|
{ sequence: 164, at: '2026-07-02T12:54:12', reasonName: 'Power Loss', reasonCode: 80, firmwareAtBoot: '05-01-03-0101-09' },
|
|
{ sequence: 163, at: '2026-07-02T12:49:50', reasonName: 'Normal Reboot', reasonCode: 21, firmwareAtBoot: '05-01-03-0101-09' },
|
|
],
|
|
network: {
|
|
txPackets: 100,
|
|
txBlocked: 0,
|
|
txDropped: 0,
|
|
txErrors: 0,
|
|
txBroadcasts: 0,
|
|
rxPackets: 200,
|
|
rxBlocked: 0,
|
|
rxDropped: 18,
|
|
rxErrors: 0,
|
|
rxBroadcasts: 0,
|
|
},
|
|
rtp: { total: 2, current: 0, currentLocal: 0, currentRelay: 0 },
|
|
security: {
|
|
customCa: { installed: false },
|
|
dot1x: { protocol: 'N/A', transactionStatus: 'Unavailable' },
|
|
},
|
|
emergencyNumbers: ['911', '1911'],
|
|
},
|
|
verdict: {
|
|
healthy: false,
|
|
warnings: ['1 concerning reboot(s) in the last 7 days (latest: Power Loss at 2026-07-02T12:54:12)'],
|
|
info: ['Rx dropped packets: 18 since last boot'],
|
|
},
|
|
elapsedMs: 812,
|
|
});
|
|
|
|
test('dect status renderer: full dump includes reboot log, network stats, and verdict', () => {
|
|
const md = renderDectStatusMarkdown([fullOkResult()], {
|
|
storeNum: '782',
|
|
footer: false,
|
|
relay: { connected: true, agent: { hostname: 'dc-relay-1' } },
|
|
});
|
|
assert.match(md, /\*\*DECT Status — Store 782\*\*/);
|
|
assert.match(md, /Relay: online \(dc-relay-1\)/);
|
|
assert.match(md, /✅|⚠️ \*\*Basestation A\*\*/);
|
|
assert.match(md, /\*\*Reboot log\*\*/);
|
|
assert.match(md, /⚡ #164/);
|
|
assert.match(md, /Power Loss/);
|
|
assert.match(md, /\*\*TX:\*\* 100 pkts · 0 blocked · 0 dropped · 0 errors · 0 bcast/);
|
|
assert.match(md, /\*\*RX:\*\* 200 pkts · 0 blocked · 18 dropped · 0 errors · 0 bcast/);
|
|
assert.match(md, /\*\*RTP:\*\* 2 total/);
|
|
assert.match(md, /multi-cell Primary/);
|
|
assert.match(md, /Rx dropped packets: 18/);
|
|
assert.doesNotMatch(md, /\*\*Security\*\*/);
|
|
assert.doesNotMatch(md, /\*\*Emergency numbers\*\*/);
|
|
assert.doesNotMatch(md, /Update server/);
|
|
assert.doesNotMatch(md, /\*\*Health verdict\*\*/);
|
|
});
|
|
|
|
test('dect status renderer: shows registered handsets and RSSI when context provided', () => {
|
|
const webexId = 'base-1';
|
|
const handsetCtx = buildHandsetContext({
|
|
dectNetwork: { name: 'Store DECT', handsetsCount: 2 },
|
|
dectBasestations: [{ id: webexId, mac: '6c:ab:05:f6:28:19', linesRegistered: 2 }],
|
|
dectHandsets: [
|
|
{
|
|
index: 1,
|
|
name: 'Handset A',
|
|
extension: '50482',
|
|
baseStationId: webexId,
|
|
mac: '6c:ab:05:a1:b2:c3',
|
|
lastRegistrationTime: new Date(Date.now() - 3 * 3600 * 1000).toISOString(),
|
|
},
|
|
{
|
|
index: 2,
|
|
name: 'Handset B',
|
|
extension: '50483',
|
|
baseStationId: webexId,
|
|
mac: '6c:ab:05:d4:e5:f6',
|
|
lastRegistrationTime: new Date(Date.now() - 3 * 3600 * 1000).toISOString(),
|
|
},
|
|
],
|
|
});
|
|
|
|
const result = fullOkResult();
|
|
result.base.webexId = webexId;
|
|
result.data.rssi = [
|
|
{ rpn: '00', mac: '6c:ab:05:a1:b2:c3', rssiDbm: -58 },
|
|
{ rpn: '01', mac: '6c:ab:05:d4:e5:f6', rssiDbm: -71 },
|
|
];
|
|
result.data.devicePresence = [{ key: 'RPN_00', present: true, raw: 'Present' }];
|
|
result.data.sipIdentityStatus = [
|
|
{ key: 'Line_0', status: 'Registered' },
|
|
{ key: 'Line_1', status: 'Registered' },
|
|
];
|
|
|
|
const md = renderDectStatusMarkdown([result], {
|
|
storeNum: '782',
|
|
footer: false,
|
|
handsetCtx,
|
|
});
|
|
|
|
assert.match(md, /\*\*Handsets & RF\*\*/);
|
|
assert.match(md, /Handset A.*ext 50482/);
|
|
assert.match(md, /Webex lines registered on base: \*\*2\*\*/);
|
|
assert.match(md, /-58 dBm/);
|
|
assert.match(md, /\*\*SIP\*\* line 0: \*\*Registered\*\*/);
|
|
assert.match(md, /\*\*Presence\*\* RPN_00: present/);
|
|
});
|
|
|
|
test('dect status renderer: unassigned handsets at store level', () => {
|
|
const handsetCtx = buildHandsetContext({
|
|
dectBasestations: [{ id: 'base-1', mac: '6c:ab:05:f6:28:19', linesRegistered: 0 }],
|
|
dectHandsets: [
|
|
{ name: 'Floater', extension: '9999', baseStationId: null, lastRegistrationTime: null },
|
|
],
|
|
});
|
|
|
|
const md = renderDectStatusMarkdown([fullOkResult()], {
|
|
storeNum: '782',
|
|
footer: false,
|
|
handsetCtx,
|
|
});
|
|
assert.match(md, /\*\*Unassigned handsets\*\*/);
|
|
assert.match(md, /Floater/);
|
|
});
|
|
|
|
test('dect status renderer: empty discovery + offline relay still produces a usable message', () => {
|
|
const md = renderDectStatusMarkdown([], {
|
|
storeNum: '782',
|
|
footer: false,
|
|
relay: { connected: false },
|
|
discoveryWarnings: [{ mac: 'aa:bb:cc:dd:ee:ff', ip: '192.168.1.5', reason: 'not on 10.x' }],
|
|
});
|
|
assert.match(md, /Relay: \*\*offline\*\*/);
|
|
assert.match(md, /No reachable DECT basestations/);
|
|
assert.match(md, /not on 10\.x/);
|
|
});
|
|
|
|
test('dect status renderer: collect failure surfaces error + hint', () => {
|
|
const md = renderDectStatusMarkdown([
|
|
{
|
|
base: { mac: '6c:ab:05:f6:28:19', ip: '10.4.11.87', name: 'Basestation A' },
|
|
ok: false,
|
|
data: null,
|
|
verdict: null,
|
|
elapsedMs: 15000,
|
|
error: {
|
|
code: 'NOT_CONNECTED',
|
|
message: 'DECT relay agent is not connected',
|
|
hint: 'DECT relay agent is not connected. Check that dect-relay-agent is running in the data center.',
|
|
},
|
|
},
|
|
], { storeNum: '782', footer: false });
|
|
assert.match(md, /Collect failed: DECT relay agent is not connected/);
|
|
assert.match(md, /dect-relay-agent is running/);
|
|
});
|