e115934061
Publish `@librechat/data-schemas` to NPM / pack (push) Failing after 8s
Publish `@librechat/client` to NPM / pack (push) Failing after 0s
GitNexus Index / index (push) Failing after 1s
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been skipped
Sync Helm Chart Tags / Sync chart tags (push) Failing after 2s
Publish `librechat-data-provider` to NPM / pack (push) Failing after 1s
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Failing after 1s
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Failing after 0s
Sync Helm Chart Tags / Ignore non-main push (push) Has been skipped
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Failing after 1s
Publish `@librechat/client` to NPM / publish-npm (push) Has been cancelled
Publish `librechat-data-provider` to NPM / publish-npm (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Publish `@librechat/data-schemas` to NPM / publish-npm (push) Has been cancelled
182 lines
6.0 KiB
JavaScript
182 lines
6.0 KiB
JavaScript
const mockRegistry = {
|
|
ensureConfigServers: jest.fn(),
|
|
getAllServerConfigs: jest.fn(),
|
|
};
|
|
|
|
jest.mock('~/config', () => ({
|
|
getMCPServersRegistry: jest.fn(() => mockRegistry),
|
|
getMCPManager: jest.fn(),
|
|
getFlowStateManager: jest.fn(),
|
|
getOAuthReconnectionManager: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('@librechat/data-schemas', () => ({
|
|
getTenantId: jest.fn(() => 'tenant-1'),
|
|
logger: { debug: jest.fn(), info: jest.fn(), warn: jest.fn(), error: jest.fn() },
|
|
}));
|
|
|
|
jest.mock('~/server/services/Config', () => ({
|
|
getAppConfig: jest.fn(),
|
|
setCachedTools: jest.fn(),
|
|
getCachedTools: jest.fn(),
|
|
getMCPServerTools: jest.fn(),
|
|
loadCustomConfig: jest.fn(),
|
|
}));
|
|
|
|
jest.mock('@librechat/api', () => ({
|
|
sendEvent: jest.fn(),
|
|
MCPOAuthHandler: jest.fn(),
|
|
isMCPDomainAllowed: jest.fn(),
|
|
normalizeServerName: jest.fn((name) => name),
|
|
normalizeJsonSchema: jest.fn((schema) => schema),
|
|
GenerationJobManager: jest.fn(),
|
|
resolveJsonSchemaRefs: jest.fn((schema) => schema),
|
|
buildOAuthToolCallName: jest.fn((name) => name),
|
|
}));
|
|
|
|
jest.mock('~/cache', () => ({ getLogStores: jest.fn() }));
|
|
jest.mock('~/models', () => ({
|
|
findToken: jest.fn(),
|
|
createToken: jest.fn(),
|
|
updateToken: jest.fn(),
|
|
}));
|
|
jest.mock('~/server/services/GraphTokenService', () => ({
|
|
getGraphApiToken: jest.fn(),
|
|
}));
|
|
jest.mock('~/server/services/OboTokenService', () => ({
|
|
exchangeOboToken: jest.fn(),
|
|
}));
|
|
jest.mock('~/server/services/OboPolicyService', () => ({
|
|
createOboTrustChecker: jest.fn(() => async () => true),
|
|
}));
|
|
jest.mock('~/server/services/Tools/mcp', () => ({
|
|
reinitMCPServer: jest.fn(),
|
|
}));
|
|
|
|
const { getAppConfig } = require('~/server/services/Config');
|
|
const { resolveConfigServers, resolveMcpConfigNames, resolveAllMcpConfigs } = require('../MCP');
|
|
|
|
describe('resolveConfigServers', () => {
|
|
beforeEach(() => jest.clearAllMocks());
|
|
|
|
it('resolves config servers for the current request context', async () => {
|
|
getAppConfig.mockResolvedValue({ mcpConfig: { srv: { url: 'http://a' } } });
|
|
mockRegistry.ensureConfigServers.mockResolvedValue({ srv: { name: 'srv' } });
|
|
|
|
const result = await resolveConfigServers({ user: { id: 'u1', role: 'admin' } });
|
|
|
|
expect(result).toEqual({ srv: { name: 'srv' } });
|
|
expect(getAppConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({ role: 'admin', userId: 'u1' }),
|
|
);
|
|
expect(mockRegistry.ensureConfigServers).toHaveBeenCalledWith({ srv: { url: 'http://a' } });
|
|
});
|
|
|
|
it('returns {} when ensureConfigServers throws', async () => {
|
|
getAppConfig.mockResolvedValue({ mcpConfig: { srv: {} } });
|
|
mockRegistry.ensureConfigServers.mockRejectedValue(new Error('inspect failed'));
|
|
|
|
const result = await resolveConfigServers({ user: { id: 'u1' } });
|
|
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
it('returns {} when getAppConfig throws', async () => {
|
|
getAppConfig.mockRejectedValue(new Error('db timeout'));
|
|
|
|
const result = await resolveConfigServers({ user: { id: 'u1' } });
|
|
|
|
expect(result).toEqual({});
|
|
});
|
|
|
|
it('passes empty mcpConfig when appConfig has none', async () => {
|
|
getAppConfig.mockResolvedValue({});
|
|
mockRegistry.ensureConfigServers.mockResolvedValue({});
|
|
|
|
await resolveConfigServers({ user: { id: 'u1' } });
|
|
|
|
expect(mockRegistry.ensureConfigServers).toHaveBeenCalledWith({});
|
|
});
|
|
});
|
|
|
|
describe('resolveMcpConfigNames', () => {
|
|
beforeEach(() => jest.clearAllMocks());
|
|
|
|
it('resolves current request config server names', async () => {
|
|
getAppConfig.mockResolvedValue({ mcpConfig: { cfg_srv: {}, yaml_srv: {} } });
|
|
|
|
const result = await resolveMcpConfigNames({ user: { id: 'u1', role: 'admin' } });
|
|
|
|
expect(result).toEqual(['cfg_srv', 'yaml_srv']);
|
|
expect(getAppConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({ role: 'admin', userId: 'u1' }),
|
|
);
|
|
});
|
|
|
|
it('returns [] when mcpConfig is absent', async () => {
|
|
getAppConfig.mockResolvedValue({});
|
|
|
|
const result = await resolveMcpConfigNames({ user: { id: 'u1' } });
|
|
|
|
expect(result).toEqual([]);
|
|
});
|
|
|
|
it('propagates getAppConfig failures for write-path callers', async () => {
|
|
getAppConfig.mockRejectedValue(new Error('db timeout'));
|
|
|
|
await expect(resolveMcpConfigNames({ user: { id: 'u1' } })).rejects.toThrow('db timeout');
|
|
});
|
|
});
|
|
|
|
describe('resolveAllMcpConfigs', () => {
|
|
beforeEach(() => jest.clearAllMocks());
|
|
|
|
it('merges config servers with base servers', async () => {
|
|
getAppConfig.mockResolvedValue({ mcpConfig: { cfg_srv: {} } });
|
|
mockRegistry.ensureConfigServers.mockResolvedValue({ cfg_srv: { name: 'cfg_srv' } });
|
|
mockRegistry.getAllServerConfigs.mockResolvedValue({
|
|
cfg_srv: { name: 'cfg_srv' },
|
|
yaml_srv: { name: 'yaml_srv' },
|
|
});
|
|
|
|
const result = await resolveAllMcpConfigs('u1', { id: 'u1', role: 'user' });
|
|
|
|
expect(result).toEqual({
|
|
cfg_srv: { name: 'cfg_srv' },
|
|
yaml_srv: { name: 'yaml_srv' },
|
|
});
|
|
expect(mockRegistry.getAllServerConfigs).toHaveBeenCalledWith(
|
|
'u1',
|
|
{
|
|
cfg_srv: { name: 'cfg_srv' },
|
|
},
|
|
'user',
|
|
);
|
|
});
|
|
|
|
it('continues with empty configServers when ensureConfigServers fails', async () => {
|
|
getAppConfig.mockResolvedValue({ mcpConfig: { srv: {} } });
|
|
mockRegistry.ensureConfigServers.mockRejectedValue(new Error('inspect failed'));
|
|
mockRegistry.getAllServerConfigs.mockResolvedValue({ yaml_srv: { name: 'yaml_srv' } });
|
|
|
|
const result = await resolveAllMcpConfigs('u1', { id: 'u1' });
|
|
|
|
expect(result).toEqual({ yaml_srv: { name: 'yaml_srv' } });
|
|
expect(mockRegistry.getAllServerConfigs).toHaveBeenCalledWith('u1', {});
|
|
});
|
|
|
|
it('propagates getAllServerConfigs failures', async () => {
|
|
getAppConfig.mockResolvedValue({ mcpConfig: {} });
|
|
mockRegistry.ensureConfigServers.mockResolvedValue({});
|
|
mockRegistry.getAllServerConfigs.mockRejectedValue(new Error('redis down'));
|
|
|
|
await expect(resolveAllMcpConfigs('u1', { id: 'u1' })).rejects.toThrow('redis down');
|
|
});
|
|
|
|
it('propagates getAppConfig failures', async () => {
|
|
getAppConfig.mockRejectedValue(new Error('mongo down'));
|
|
|
|
await expect(resolveAllMcpConfigs('u1', { id: 'u1' })).rejects.toThrow('mongo down');
|
|
});
|
|
});
|