collabSupport/tests/paloalto.client.test.js
jmcqueen b802383441 Add Prisma SD-WAN voice-quality enrichment for /phonestatus + /voicediag
Introduces a full Palo Alto Prisma SD-WAN integration (dual-mode SASE
OAuth 2.0 / legacy CloudGenix auth, pagination, 429 backoff, session
priming) that surfaces per-path latency/jitter/loss/MOS, site
healthscore, link state, and alarm data for a store. Wired into the
/phonestatus WAN follow-up and eight new /voicediag WAN checks graded
against ITU-T G.114 / RFC 3550 defaults (env-overridable via
WAN_STANDARD_*).

Also adds a shape-aware detail renderer for /voicediag (per-link
tables with verdict icons instead of a stringified JSON dump) and a
--window flag (15m / 1h / 6h / 24h / 1d, env default via
WAN_STANDARD_WINDOW_MINUTES) so operators can widen the look-back
without redeploying. scripts/prismaProbe.js is bundled as a CLI for
schema iteration against a live tenant.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-09 09:45:29 -04:00

326 lines
11 KiB
JavaScript

// tests/paloalto.client.test.js
//
// Coverage for integrations/paloalto/client.js — the dual-mode
// auth wrapper. Uses a fake HTTP server to exercise:
// - SASE OAuth token acquisition (form-encoded client_credentials
// against a fake auth URL)
// - Legacy CloudGenix login (JSON POST against /v2.0/api/login)
// - Mutex behaviour under concurrent callers
// - 401 → forced refresh + one-shot retry
// - PRISMA_AUTH_MODE=unknown throws a clear error
//
// Uses the same fake-server pattern as paloalto.sites.test.js.
import test from 'node:test';
import assert from 'node:assert/strict';
import http from 'node:http';
import { paloAltoAxios, getPrismaToken, _resetPrismaAuthCache } from '../integrations/paloalto/client.js';
const FAKE_TOKEN_1 = 'token-round-1';
const FAKE_TOKEN_2 = 'token-round-2';
async function makeFakeAuthServer(handlers = {}) {
const requests = [];
const server = http.createServer((req, res) => {
let body = '';
req.on('data', (c) => (body += c));
req.on('end', () => {
requests.push({ method: req.method, url: req.url, body, headers: req.headers });
// Default SASE session-prime responder so tests that only care
// about the token / retry path don't need to wire this in.
// Individual tests can override by providing an explicit
// handler for `GET /sdwan/v2.1/api/profile`.
const explicitHandler = handlers[`${req.method} ${req.url}`];
if (
req.url === '/sdwan/v2.1/api/profile' &&
req.method === 'GET' &&
!explicitHandler
) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 'stub-profile' }));
return;
}
// Default OAuth token responder — same rationale as the prime
// default above. Overridable by explicit handler.
if (
req.url === '/oauth2/access_token' &&
req.method === 'POST' &&
!explicitHandler
) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ access_token: 'default-token', expires_in: 900 }));
return;
}
const handler = explicitHandler;
if (handler) {
const result = handler({ req, body });
res.writeHead(result.status || 200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(result.body || {}));
return;
}
res.writeHead(404);
res.end();
});
});
await new Promise((r) => server.listen(0, '127.0.0.1', r));
const { port } = server.address();
return {
port,
baseUrl: `http://127.0.0.1:${port}`,
requests,
close: () => new Promise((r) => server.close(r)),
};
}
function clearAllEnv() {
delete process.env.PRISMA_AUTH_MODE;
delete process.env.PRISMA_SASE_BASE_URL;
delete process.env.PRISMA_LEGACY_BASE_URL;
delete process.env.PRISMA_AUTH_URL;
delete process.env.PRISMA_CLIENT_ID;
delete process.env.PRISMA_CLIENT_SECRET;
delete process.env.PRISMA_TSG_ID;
delete process.env.PRISMA_EMAIL;
delete process.env.PRISMA_PASSWORD;
}
test('client: PRISMA_AUTH_MODE=unknown throws on first token request', async () => {
_resetPrismaAuthCache();
clearAllEnv();
process.env.PRISMA_AUTH_MODE = 'nonsense';
try {
await assert.rejects(() => getPrismaToken(true), /PRISMA_AUTH_MODE/);
} finally {
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: SASE mode fetches token via client_credentials + scopes to tsg_id', async () => {
_resetPrismaAuthCache();
clearAllEnv();
const fake = await makeFakeAuthServer({
'POST /oauth2/access_token': ({ body }) => {
assert.match(body, /grant_type=client_credentials/);
assert.match(body, /scope=tsg_id%3A/);
return { status: 200, body: { access_token: FAKE_TOKEN_1, expires_in: 900 } };
},
});
process.env.PRISMA_AUTH_MODE = 'sase';
process.env.PRISMA_AUTH_URL = `${fake.baseUrl}/oauth2/access_token`;
process.env.PRISMA_CLIENT_ID = 'id';
process.env.PRISMA_CLIENT_SECRET = 'secret';
process.env.PRISMA_TSG_ID = 'tsg';
try {
const t = await getPrismaToken(true);
assert.equal(t, FAKE_TOKEN_1);
} finally {
await fake.close();
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: SASE mode surfaces missing envs with a clear error', async () => {
_resetPrismaAuthCache();
clearAllEnv();
process.env.PRISMA_AUTH_MODE = 'sase';
try {
await assert.rejects(() => getPrismaToken(true), /PRISMA_CLIENT_ID/);
} finally {
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: legacy mode POSTs email+password to /v2.0/api/login', async () => {
_resetPrismaAuthCache();
clearAllEnv();
const fake = await makeFakeAuthServer({
'POST /v2.0/api/login': ({ body }) => {
const parsed = JSON.parse(body);
assert.equal(parsed.email, 'a@b.com');
assert.equal(parsed.password, 'secret');
return { status: 200, body: { x_auth_token: 'legacy-token-xyz' } };
},
});
process.env.PRISMA_AUTH_MODE = 'legacy';
process.env.PRISMA_LEGACY_BASE_URL = fake.baseUrl;
process.env.PRISMA_EMAIL = 'a@b.com';
process.env.PRISMA_PASSWORD = 'secret';
try {
const t = await getPrismaToken(true);
assert.equal(t, 'legacy-token-xyz');
} finally {
await fake.close();
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: legacy mode surfaces missing envs with a clear error', async () => {
_resetPrismaAuthCache();
clearAllEnv();
process.env.PRISMA_AUTH_MODE = 'legacy';
try {
await assert.rejects(() => getPrismaToken(true), /PRISMA_EMAIL/);
} finally {
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: concurrent callers coalesce onto a single token refresh', async () => {
_resetPrismaAuthCache();
clearAllEnv();
let hits = 0;
const fake = await makeFakeAuthServer({
'POST /oauth2/access_token': () => {
hits += 1;
return { status: 200, body: { access_token: `token-${hits}`, expires_in: 900 } };
},
});
process.env.PRISMA_AUTH_MODE = 'sase';
process.env.PRISMA_AUTH_URL = `${fake.baseUrl}/oauth2/access_token`;
process.env.PRISMA_CLIENT_ID = 'id';
process.env.PRISMA_CLIENT_SECRET = 'secret';
process.env.PRISMA_TSG_ID = 'tsg';
try {
const tokens = await Promise.all([
getPrismaToken(true),
getPrismaToken(false),
getPrismaToken(false),
getPrismaToken(false),
]);
assert.equal(hits, 1, 'mutex should coalesce concurrent refresh requests');
assert.equal(tokens[0], 'token-1');
// Subsequent callers should get the same cached token as the
// first (mutex holds them until it's cached).
assert.equal(tokens[1], tokens[0]);
assert.equal(tokens[2], tokens[0]);
assert.equal(tokens[3], tokens[0]);
} finally {
await fake.close();
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: SASE mode primes session with GET /sdwan/v2.1/api/profile before the first SD-WAN call', async () => {
_resetPrismaAuthCache();
clearAllEnv();
let profileHits = 0;
let apiHits = 0;
const fake = await makeFakeAuthServer({
'GET /sdwan/v2.1/api/profile': ({ req }) => {
profileHits += 1;
assert.match(req.headers.authorization || '', /^Bearer /);
return { status: 200, body: { id: 'stub-profile', tenant_id: 't' } };
},
'GET /sdwan/v4.13/api/sites': () => {
apiHits += 1;
return { status: 200, body: { items: [] } };
},
});
process.env.PRISMA_AUTH_MODE = 'sase';
process.env.PRISMA_SASE_BASE_URL = fake.baseUrl;
process.env.PRISMA_AUTH_URL = `${fake.baseUrl}/oauth2/access_token`;
process.env.PRISMA_CLIENT_ID = 'id';
process.env.PRISMA_CLIENT_SECRET = 'secret';
process.env.PRISMA_TSG_ID = 'tsg';
try {
await paloAltoAxios.get('/sdwan/v4.13/api/sites');
await paloAltoAxios.get('/sdwan/v4.13/api/sites');
await paloAltoAxios.get('/sdwan/v4.13/api/sites');
assert.equal(profileHits, 1, 'priming call should fire exactly once per token');
assert.equal(apiHits, 3, 'subsequent SD-WAN calls should all succeed');
// Ordering assertion: /profile happened before the first SD-WAN call.
const seqUrls = fake.requests.map((r) => r.url).filter((u) => u.startsWith('/sdwan/'));
assert.equal(seqUrls[0], '/sdwan/v2.1/api/profile');
} finally {
await fake.close();
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: SASE priming re-runs after a forced token refresh', async () => {
_resetPrismaAuthCache();
clearAllEnv();
let profileHits = 0;
const fake = await makeFakeAuthServer({
'GET /sdwan/v2.1/api/profile': () => {
profileHits += 1;
return { status: 200, body: { id: 'stub-profile' } };
},
'GET /sdwan/v4.13/api/sites': () => ({ status: 200, body: { items: [] } }),
});
process.env.PRISMA_AUTH_MODE = 'sase';
process.env.PRISMA_SASE_BASE_URL = fake.baseUrl;
process.env.PRISMA_AUTH_URL = `${fake.baseUrl}/oauth2/access_token`;
process.env.PRISMA_CLIENT_ID = 'id';
process.env.PRISMA_CLIENT_SECRET = 'secret';
process.env.PRISMA_TSG_ID = 'tsg';
try {
await paloAltoAxios.get('/sdwan/v4.13/api/sites');
assert.equal(profileHits, 1);
// Force a token refresh — priming should re-run on next call.
await getPrismaToken(true);
await paloAltoAxios.get('/sdwan/v4.13/api/sites');
assert.equal(profileHits, 2, 'new token → re-prime');
} finally {
await fake.close();
clearAllEnv();
_resetPrismaAuthCache();
}
});
test('client: axios instance retries once after 401 with forced refresh', async () => {
_resetPrismaAuthCache();
clearAllEnv();
let tokenCallCount = 0;
let apiCallCount = 0;
const fake = await makeFakeAuthServer({
'POST /oauth2/access_token': () => {
tokenCallCount += 1;
return {
status: 200,
body: {
access_token: tokenCallCount === 1 ? FAKE_TOKEN_1 : FAKE_TOKEN_2,
expires_in: 900,
},
};
},
'GET /some/api/endpoint': ({ req }) => {
apiCallCount += 1;
// First call → 401 to trigger refresh. Second call must
// present the new token to succeed.
if (apiCallCount === 1) {
return { status: 401, body: { error: 'expired' } };
}
const auth = req.headers.authorization || '';
if (auth === `Bearer ${FAKE_TOKEN_2}`) {
return { status: 200, body: { ok: true } };
}
return { status: 401, body: { error: 'still bad' } };
},
});
process.env.PRISMA_AUTH_MODE = 'sase';
process.env.PRISMA_SASE_BASE_URL = fake.baseUrl;
process.env.PRISMA_AUTH_URL = `${fake.baseUrl}/oauth2/access_token`;
process.env.PRISMA_CLIENT_ID = 'id';
process.env.PRISMA_CLIENT_SECRET = 'secret';
process.env.PRISMA_TSG_ID = 'tsg';
try {
const res = await paloAltoAxios.get('/some/api/endpoint');
assert.equal(res.status, 200);
assert.equal(res.data.ok, true);
assert.equal(tokenCallCount, 2, 'token refresh happened after 401');
assert.equal(apiCallCount, 2, 'API call retried once');
} finally {
await fake.close();
clearAllEnv();
_resetPrismaAuthCache();
}
});