mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-03-17 05:06:32 +01:00
202 lines
5.4 KiB
JavaScript
202 lines
5.4 KiB
JavaScript
|
|
const mongoose = require('mongoose');
|
||
|
|
const {
|
||
|
|
ResourceType,
|
||
|
|
PermissionBits,
|
||
|
|
PrincipalType,
|
||
|
|
PrincipalModel,
|
||
|
|
} = require('librechat-data-provider');
|
||
|
|
const { MongoMemoryServer } = require('mongodb-memory-server');
|
||
|
|
|
||
|
|
const mockInitializeAgent = jest.fn();
|
||
|
|
const mockValidateAgentModel = jest.fn();
|
||
|
|
|
||
|
|
jest.mock('@librechat/agents', () => ({
|
||
|
|
...jest.requireActual('@librechat/agents'),
|
||
|
|
createContentAggregator: jest.fn(() => ({
|
||
|
|
contentParts: [],
|
||
|
|
aggregateContent: jest.fn(),
|
||
|
|
})),
|
||
|
|
}));
|
||
|
|
|
||
|
|
jest.mock('@librechat/api', () => ({
|
||
|
|
...jest.requireActual('@librechat/api'),
|
||
|
|
initializeAgent: (...args) => mockInitializeAgent(...args),
|
||
|
|
validateAgentModel: (...args) => mockValidateAgentModel(...args),
|
||
|
|
GenerationJobManager: { setCollectedUsage: jest.fn() },
|
||
|
|
getCustomEndpointConfig: jest.fn(),
|
||
|
|
createSequentialChainEdges: jest.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
jest.mock('~/server/controllers/agents/callbacks', () => ({
|
||
|
|
createToolEndCallback: jest.fn(() => jest.fn()),
|
||
|
|
getDefaultHandlers: jest.fn(() => ({})),
|
||
|
|
}));
|
||
|
|
|
||
|
|
jest.mock('~/server/services/ToolService', () => ({
|
||
|
|
loadAgentTools: jest.fn(),
|
||
|
|
loadToolsForExecution: jest.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
jest.mock('~/server/controllers/ModelController', () => ({
|
||
|
|
getModelsConfig: jest.fn().mockResolvedValue({}),
|
||
|
|
}));
|
||
|
|
|
||
|
|
let agentClientArgs;
|
||
|
|
jest.mock('~/server/controllers/agents/client', () => {
|
||
|
|
return jest.fn().mockImplementation((args) => {
|
||
|
|
agentClientArgs = args;
|
||
|
|
return {};
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
jest.mock('./addedConvo', () => ({
|
||
|
|
processAddedConvo: jest.fn().mockResolvedValue({ userMCPAuthMap: undefined }),
|
||
|
|
}));
|
||
|
|
|
||
|
|
jest.mock('~/cache', () => ({
|
||
|
|
logViolation: jest.fn(),
|
||
|
|
}));
|
||
|
|
|
||
|
|
const { initializeClient } = require('./initialize');
|
||
|
|
const { createAgent } = require('~/models/Agent');
|
||
|
|
const { User, AclEntry } = require('~/db/models');
|
||
|
|
|
||
|
|
const PRIMARY_ID = 'agent_primary';
|
||
|
|
const TARGET_ID = 'agent_target';
|
||
|
|
const AUTHORIZED_ID = 'agent_authorized';
|
||
|
|
|
||
|
|
describe('initializeClient — processAgent ACL gate', () => {
|
||
|
|
let mongoServer;
|
||
|
|
let testUser;
|
||
|
|
|
||
|
|
beforeAll(async () => {
|
||
|
|
mongoServer = await MongoMemoryServer.create();
|
||
|
|
await mongoose.connect(mongoServer.getUri());
|
||
|
|
});
|
||
|
|
|
||
|
|
afterAll(async () => {
|
||
|
|
await mongoose.disconnect();
|
||
|
|
await mongoServer.stop();
|
||
|
|
});
|
||
|
|
|
||
|
|
beforeEach(async () => {
|
||
|
|
await mongoose.connection.dropDatabase();
|
||
|
|
jest.clearAllMocks();
|
||
|
|
agentClientArgs = undefined;
|
||
|
|
|
||
|
|
testUser = await User.create({
|
||
|
|
email: 'test@example.com',
|
||
|
|
name: 'Test User',
|
||
|
|
username: 'testuser',
|
||
|
|
role: 'USER',
|
||
|
|
});
|
||
|
|
|
||
|
|
mockValidateAgentModel.mockResolvedValue({ isValid: true });
|
||
|
|
});
|
||
|
|
|
||
|
|
const makeReq = () => ({
|
||
|
|
user: { id: testUser._id.toString(), role: 'USER' },
|
||
|
|
body: { conversationId: 'conv_1', files: [] },
|
||
|
|
config: { endpoints: {} },
|
||
|
|
_resumableStreamId: null,
|
||
|
|
});
|
||
|
|
|
||
|
|
const makeEndpointOption = () => ({
|
||
|
|
agent: Promise.resolve({
|
||
|
|
id: PRIMARY_ID,
|
||
|
|
name: 'Primary',
|
||
|
|
provider: 'openai',
|
||
|
|
model: 'gpt-4',
|
||
|
|
tools: [],
|
||
|
|
}),
|
||
|
|
model_parameters: { model: 'gpt-4' },
|
||
|
|
endpoint: 'agents',
|
||
|
|
});
|
||
|
|
|
||
|
|
const makePrimaryConfig = (edges) => ({
|
||
|
|
id: PRIMARY_ID,
|
||
|
|
endpoint: 'agents',
|
||
|
|
edges,
|
||
|
|
toolDefinitions: [],
|
||
|
|
toolRegistry: new Map(),
|
||
|
|
userMCPAuthMap: null,
|
||
|
|
tool_resources: {},
|
||
|
|
resendFiles: true,
|
||
|
|
maxContextTokens: 4096,
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should skip handoff agent and filter its edge when user lacks VIEW access', async () => {
|
||
|
|
await createAgent({
|
||
|
|
id: TARGET_ID,
|
||
|
|
name: 'Target Agent',
|
||
|
|
provider: 'openai',
|
||
|
|
model: 'gpt-4',
|
||
|
|
author: new mongoose.Types.ObjectId(),
|
||
|
|
tools: [],
|
||
|
|
});
|
||
|
|
|
||
|
|
const edges = [{ from: PRIMARY_ID, to: TARGET_ID, edgeType: 'handoff' }];
|
||
|
|
mockInitializeAgent.mockResolvedValue(makePrimaryConfig(edges));
|
||
|
|
|
||
|
|
await initializeClient({
|
||
|
|
req: makeReq(),
|
||
|
|
res: {},
|
||
|
|
signal: new AbortController().signal,
|
||
|
|
endpointOption: makeEndpointOption(),
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(mockInitializeAgent).toHaveBeenCalledTimes(1);
|
||
|
|
expect(agentClientArgs.agent.edges).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('should initialize handoff agent and keep its edge when user has VIEW access', async () => {
|
||
|
|
const authorizedAgent = await createAgent({
|
||
|
|
id: AUTHORIZED_ID,
|
||
|
|
name: 'Authorized Agent',
|
||
|
|
provider: 'openai',
|
||
|
|
model: 'gpt-4',
|
||
|
|
author: new mongoose.Types.ObjectId(),
|
||
|
|
tools: [],
|
||
|
|
});
|
||
|
|
|
||
|
|
await AclEntry.create({
|
||
|
|
principalType: PrincipalType.USER,
|
||
|
|
principalId: testUser._id,
|
||
|
|
principalModel: PrincipalModel.USER,
|
||
|
|
resourceType: ResourceType.AGENT,
|
||
|
|
resourceId: authorizedAgent._id,
|
||
|
|
permBits: PermissionBits.VIEW,
|
||
|
|
grantedBy: testUser._id,
|
||
|
|
});
|
||
|
|
|
||
|
|
const edges = [{ from: PRIMARY_ID, to: AUTHORIZED_ID, edgeType: 'handoff' }];
|
||
|
|
const handoffConfig = {
|
||
|
|
id: AUTHORIZED_ID,
|
||
|
|
edges: [],
|
||
|
|
toolDefinitions: [],
|
||
|
|
toolRegistry: new Map(),
|
||
|
|
userMCPAuthMap: null,
|
||
|
|
tool_resources: {},
|
||
|
|
};
|
||
|
|
|
||
|
|
let callCount = 0;
|
||
|
|
mockInitializeAgent.mockImplementation(() => {
|
||
|
|
callCount++;
|
||
|
|
return callCount === 1
|
||
|
|
? Promise.resolve(makePrimaryConfig(edges))
|
||
|
|
: Promise.resolve(handoffConfig);
|
||
|
|
});
|
||
|
|
|
||
|
|
await initializeClient({
|
||
|
|
req: makeReq(),
|
||
|
|
res: {},
|
||
|
|
signal: new AbortController().signal,
|
||
|
|
endpointOption: makeEndpointOption(),
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(mockInitializeAgent).toHaveBeenCalledTimes(2);
|
||
|
|
expect(agentClientArgs.agent.edges).toHaveLength(1);
|
||
|
|
expect(agentClientArgs.agent.edges[0].to).toBe(AUTHORIZED_ID);
|
||
|
|
});
|
||
|
|
});
|