collabSupport/tests/ciscoMppPhone.test.js
jmcqueen f7953b8eb5 Add MPP desk phone diagnostics follow-up to /phonestatus via relay.
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>
2026-07-28 18:01:01 -04:00

165 lines
5.7 KiB
JavaScript

// Unit tests for integrations/cisco-mpp-phone/
import test from 'node:test';
import assert from 'node:assert/strict';
import fs from 'node:fs';
import https from 'node:https';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createMppPhoneClient, tryRequest } from '../integrations/cisco-mpp-phone/client.js';
import { runReadProbes, fetchStatusJson, summarizeProbes } from '../integrations/cisco-mpp-phone/probes.js';
import { parseStatusJson, summarizePhoneHealthFromJson } from '../integrations/cisco-mpp-phone/statusJson.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FIXTURE_DIR = path.join(__dirname, 'fixtures', 'mpp');
const STATUS_XML = fs.readFileSync(path.join(FIXTURE_DIR, 'status-782-sample.xml'), 'utf8');
const STATUS_JSON = fs.readFileSync(path.join(FIXTURE_DIR, 'status-782-live.json'), 'utf8');
const KEY = fs.readFileSync(path.join(FIXTURE_DIR, 'test-key.pem'), 'utf8');
const CERT = fs.readFileSync(path.join(FIXTURE_DIR, 'test-cert.pem'), 'utf8');
const TEST_USER = 'admin';
const TEST_PASS = 'test-phone-pass';
function startMockPhoneServer({ openJson = false } = {}) {
return new Promise((resolve) => {
const server = https.createServer({ key: KEY, cert: CERT }, (req, res) => {
const openPaths = openJson && (
req.url === '/Status.json'
|| req.url === '/Download%20Status.json'
|| req.url === '/ns.json'
|| req.url === '/basic/System.json'
|| req.url === '/'
);
if (!openPaths) {
const auth = req.headers.authorization || '';
const expected = `Basic ${Buffer.from(`${TEST_USER}:${TEST_PASS}`).toString('base64')}`;
if (auth !== expected) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="phone"' });
res.end('unauthorized');
return;
}
}
if (req.url === '/Status.json' || req.url === '/admin/status.xml' || req.url === '/status.xml') {
res.writeHead(200, { 'Content-Type': req.url.endsWith('.json') ? 'application/json' : 'application/xml' });
res.end(req.url.endsWith('.json') ? STATUS_JSON : STATUS_XML);
return;
}
if (req.url === '/admin/cfg.xml') {
res.writeHead(200, { 'Content-Type': 'application/xml' });
res.end('<flat-profile><Enable_Web_Server>Yes</Enable_Web_Server><Line_1><Proxy>sip.webex.com</Proxy></Line_1></flat-profile>');
return;
}
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<html><body>MPP phone</body></html>');
return;
}
res.writeHead(404);
res.end('not found');
});
server.listen(0, '127.0.0.1', () => {
const { port } = server.address();
resolve({ server, port, close: () => new Promise((r) => server.close(() => r())) });
});
});
}
test('parseStatusJson: extracts key fields from fixture', () => {
const parsed = parseStatusJson(STATUS_JSON);
assert.equal(parsed.device.mac, 'cc:98:91:4f:67:99');
assert.equal(parsed.registration, 'Registered');
});
test('summarizePhoneHealthFromJson: registered phone is healthy', () => {
const parsed = parseStatusJson(STATUS_JSON);
const verdict = summarizePhoneHealthFromJson(parsed);
assert.equal(verdict.healthy, true);
});
test('createMppPhoneClient: fetches status.xml over self-signed HTTPS', async () => {
const mock = await startMockPhoneServer();
try {
const client = createMppPhoneClient({
host: `127.0.0.1:${mock.port}`,
user: TEST_USER,
password: TEST_PASS,
timeoutMs: 5000,
});
const r = await tryRequest(client, { path: '/admin/status.xml' });
assert.equal(r.status, 200);
assert.ok(r.sizeBytes > 0);
assert.match(r.snippet, /MAC_Address|Product_Name|Registered/);
} finally {
await mock.close();
}
});
test('runReadProbes: returns structured rows against mock phone', async () => {
const mock = await startMockPhoneServer();
try {
const client = createMppPhoneClient({
host: `127.0.0.1:${mock.port}`,
user: TEST_USER,
password: TEST_PASS,
timeoutMs: 5000,
});
const probes = await runReadProbes(client);
const summary = summarizeProbes(probes);
assert.ok(probes.length >= 2);
assert.equal(summary.statusJsonFound, true);
assert.equal(summary.authOk, true);
} finally {
await mock.close();
}
});
test('fetchStatusJson: returns raw JSON body', async () => {
const mock = await startMockPhoneServer();
try {
const client = createMppPhoneClient({
host: `127.0.0.1:${mock.port}`,
user: TEST_USER,
password: TEST_PASS,
timeoutMs: 5000,
});
const { rawJson, byteLength } = await fetchStatusJson(client);
assert.ok(rawJson.includes('Product Name'));
assert.ok(byteLength > 0);
} finally {
await mock.close();
}
});
test('createMppPhoneClient: fetches Status.json without auth (Webex MPP web UI)', async () => {
const mock = await startMockPhoneServer({ openJson: true });
try {
const client = createMppPhoneClient({
host: `127.0.0.1:${mock.port}`,
timeoutMs: 5000,
});
const r = await tryRequest(client, { path: '/Status.json' });
assert.equal(r.status, 200);
assert.ok(r.sizeBytes > 0);
const { rawJson } = await fetchStatusJson(client);
assert.ok(rawJson.includes('Product Name'));
} finally {
await mock.close();
}
});
test('createMppPhoneClient: rejects wrong password', async () => {
const mock = await startMockPhoneServer();
try {
const client = createMppPhoneClient({
host: `127.0.0.1:${mock.port}`,
user: TEST_USER,
password: 'wrong',
timeoutMs: 5000,
});
const r = await tryRequest(client, { path: '/admin/status.xml' });
assert.equal(r.status, 401);
} finally {
await mock.close();
}
});