611f1a3318
"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).
81 lines
2.9 KiB
JavaScript
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/);
|
|
});
|
|
});
|