LibreChat/api/server/routes/__tests__/config.rum.spec.js

194 lines
6 KiB
JavaScript

jest.mock('~/cache/getLogStores');
const mockGetAppConfig = jest.fn();
jest.mock('~/server/services/Config/app', () => ({
getAppConfig: (...args) => mockGetAppConfig(...args),
}));
jest.mock('~/server/services/Config/ldap', () => ({
getLdapConfig: jest.fn(() => null),
}));
jest.mock('~/server/middleware/roles/capabilities', () => ({
hasCapability: jest.fn(),
}));
jest.mock('@librechat/data-schemas', () => ({
...jest.requireActual('@librechat/data-schemas'),
getTenantId: jest.fn(() => undefined),
}));
jest.mock('@librechat/api', () => ({
...jest.requireActual('@librechat/api'),
getCloudFrontConfig: jest.fn(() => null),
}));
const request = require('supertest');
const express = require('express');
const configRoute = require('../config');
function createApp(user) {
const app = express();
app.disable('x-powered-by');
if (user) {
app.use((req, _res, next) => {
req.user = user;
next();
});
}
app.use('/api/config', configRoute);
return app;
}
const baseAppConfig = {
registration: { socialLogins: ['google', 'github'] },
interfaceConfig: { modelSelect: true },
turnstileConfig: { siteKey: 'test-key' },
modelSpecs: { list: [{ name: 'test-spec' }] },
};
const mockUser = {
id: 'user123',
role: 'USER',
tenantId: undefined,
};
afterEach(() => {
jest.resetAllMocks();
delete process.env.RUM_ENABLED;
delete process.env.RUM_PROVIDER;
delete process.env.RUM_URL;
delete process.env.RUM_PROXY_TARGET_URL;
delete process.env.RUM_SERVICE_NAME;
delete process.env.RUM_AUTH_MODE;
delete process.env.RUM_PUBLIC_TOKEN;
delete process.env.RUM_TRACE_PROPAGATION_TARGETS;
delete process.env.RUM_CONSOLE_CAPTURE;
delete process.env.RUM_DISABLE_REPLAY;
delete process.env.RUM_ADVANCED_NETWORK_CAPTURE;
delete process.env.RUM_SAMPLE_RATE;
delete process.env.RUM_ENVIRONMENT;
});
describe('GET /api/config RUM config', () => {
it('includes public-token RUM config when enabled with valid env', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_URL = 'https://rum.example.com';
process.env.RUM_PUBLIC_TOKEN = 'public-token';
process.env.RUM_TRACE_PROPAGATION_TARGETS =
'https://app.example.com,https://api.openai.com,*,http://api.example.com';
process.env.RUM_SAMPLE_RATE = '0.25';
process.env.RUM_ENVIRONMENT = 'test';
const app = createApp(null);
const response = await request(app).get('/api/config');
expect(response.body.rum).toEqual({
provider: 'hyperdx',
enabled: true,
url: 'https://rum.example.com',
serviceName: 'librechat-web',
authMode: 'publicToken',
publicToken: 'public-token',
tracePropagationTargets: ['https://app.example.com', 'https://api.openai.com'],
consoleCapture: false,
disableReplay: true,
advancedNetworkCapture: false,
sampleRate: 0.25,
environment: 'test',
});
});
it('omits malformed RUM config', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_URL = 'not a url';
process.env.RUM_PUBLIC_TOKEN = 'public-token';
const app = createApp(null);
const response = await request(app).get('/api/config');
expect(response.body).not.toHaveProperty('rum');
});
it('includes proxy RUM config when enabled with valid env', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_AUTH_MODE = 'proxy';
process.env.RUM_PROXY_TARGET_URL = 'http://otel-collector:4318';
const app = createApp(mockUser);
const response = await request(app).get('/api/config');
expect(response.body.rum).toEqual({
provider: 'hyperdx',
enabled: true,
url: '/api/rum',
serviceName: 'librechat-web',
authMode: 'proxy',
consoleCapture: false,
disableReplay: true,
advancedNetworkCapture: false,
});
});
it('omits proxy RUM config without a target collector URL', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_AUTH_MODE = 'proxy';
const app = createApp(mockUser);
const response = await request(app).get('/api/config');
expect(response.body).not.toHaveProperty('rum');
});
it('omits RUM config when the URL contains credentials', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_URL = 'https://user:password@rum.example.com';
process.env.RUM_PUBLIC_TOKEN = 'public-token';
const app = createApp(null);
const response = await request(app).get('/api/config');
expect(response.body).not.toHaveProperty('rum');
});
it('allows IPv6 localhost HTTP RUM URLs in public-token mode', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_URL = 'http://[::1]:4318';
process.env.RUM_PUBLIC_TOKEN = 'public-token';
const app = createApp(null);
const response = await request(app).get('/api/config');
expect(response.body.rum?.url).toBe('http://[::1]:4318');
});
it('omits unsupported userJwt RUM config for authenticated users', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_URL = 'https://rum.example.com';
process.env.RUM_AUTH_MODE = 'userJwt';
const app = createApp(mockUser);
const response = await request(app).get('/api/config');
expect(response.body).not.toHaveProperty('rum');
});
it('omits unsupported userJwt RUM config for unauthenticated users', async () => {
mockGetAppConfig.mockResolvedValue(baseAppConfig);
process.env.RUM_ENABLED = 'true';
process.env.RUM_URL = 'https://rum.example.com';
process.env.RUM_AUTH_MODE = 'userJwt';
const app = createApp(null);
const response = await request(app).get('/api/config');
expect(response.body).not.toHaveProperty('rum');
});
});