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>
165 lines
5.7 KiB
JavaScript
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();
|
|
}
|
|
});
|