Files
sso-manager-node/nodejs/tests/jump_client.test.js
T
wmantly 611f1a3318 feat(directory): real mesh-gateway count on the Multi-Site modal; docs links
"Theta Gateways: N active gateways" was counting this app's own
unrelated WireGuard roaming-client/exit-node Resources
(metadata.subType === 'wireguard') -- a completely different subsystem
from the gateway-to-gateway mesh the modal is actually about, and it
never queried jump-host's mesh registry at all (so it couldn't show
the local self-entry either, since there was nothing mesh-related
being counted in the first place).

Added utils/jump_client.js (same pattern as utils/proxy_client.js:
reuses jump-host's existing self-service jmp_ API token system rather
than inventing a new credential) to query jump-host's real
GET /api/mesh/gateways. Reports a null count (not misleading 0) when
the integration isn't configured/reachable, surfaced distinctly in the
UI. Also added help links to the published multi-site/mesh docs on the
modal.

Includes docs links + count only -- this session also discovered that
utils/proxy_client.js's PROXY_INTERNAL_URL, and now JUMP_INTERNAL_URL,
were never actually wired into theta-suite's docker-compose.yml, so
both service-to-service integrations were unreachable in every real
deployment despite existing in code (fixed in theta-suite separately).
2026-08-10 22:17:57 -04:00

81 lines
2.9 KiB
JavaScript

'use strict';
let mockBaoStore = new Map();
jest.mock('@simpleworkjs/bao-conf', () => ({
get: jest.fn(async (path) => mockBaoStore.get(path) || null),
set: jest.fn(async (path, value) => { mockBaoStore.set(path, value); })
}));
describe('jump_client', () => {
let jumpClient;
let originalFetch;
let mockFetchImpl;
let calls;
beforeEach(() => {
jest.resetModules();
mockBaoStore = new Map();
calls = [];
mockFetchImpl = async () => ({ ok: true, status: 200, json: async () => ({ status: 'ok', gateways: [] }) });
originalFetch = global.fetch;
global.fetch = (...args) => { calls.push(args); return mockFetchImpl(...args); };
jumpClient = require('../utils/jump_client');
jumpClient._reset();
delete process.env.JUMP_INTERNAL_URL;
});
afterEach(() => {
global.fetch = originalFetch;
});
test('reports null count (not zero) when JUMP_INTERNAL_URL is not configured', async () => {
const result = await jumpClient.getGatewayCount();
expect(result.count).toBeNull();
expect(result.note).toMatch(/JUMP_INTERNAL_URL/);
expect(calls.length).toBe(0);
});
test('reports null count when no token is stored in OpenBao', async () => {
process.env.JUMP_INTERNAL_URL = 'http://jump-host.internal';
const result = await jumpClient.getGatewayCount();
expect(result.count).toBeNull();
expect(result.note).toMatch(/no jump-host API token/);
expect(calls.length).toBe(0);
});
test('returns the real gateway count on success', async () => {
process.env.JUMP_INTERNAL_URL = 'http://jump-host.internal';
mockBaoStore.set('integrations/theta-jump', { token: 'jmp_test_token' });
mockFetchImpl = async () => ({
ok: true, status: 200,
json: async () => ({ status: 'ok', gateways: [{ siteSlug: '(self)' }, { siteSlug: 'site-b' }] })
});
const result = await jumpClient.getGatewayCount();
expect(result.count).toBe(2);
expect(result.note).toBe('ok');
expect(calls[0][0]).toBe('http://jump-host.internal/api/mesh/gateways');
expect(calls[0][1].headers.Authorization).toBe('Bearer jmp_test_token');
});
test('reports null count on a non-2xx response', async () => {
process.env.JUMP_INTERNAL_URL = 'http://jump-host.internal';
mockBaoStore.set('integrations/theta-jump', { token: 'jmp_test_token' });
mockFetchImpl = async () => ({ ok: false, status: 403 });
const result = await jumpClient.getGatewayCount();
expect(result.count).toBeNull();
expect(result.note).toMatch(/HTTP 403/);
});
test('reports a network failure without throwing', async () => {
process.env.JUMP_INTERNAL_URL = 'http://jump-host.internal';
mockBaoStore.set('integrations/theta-jump', { token: 'jmp_test_token' });
mockFetchImpl = async () => { throw new Error('connection refused'); };
const result = await jumpClient.getGatewayCount();
expect(result.count).toBeNull();
expect(result.note).toMatch(/failed: connection refused/);
});
});