// 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('Yessip.webex.com'); return; } if (req.url === '/') { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end('MPP phone'); 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(); } });