mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-04 14:57:42 +00:00
🧭 test: Cover Agent Handoffs End to End (#14428)
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Helm Chart Tags / Ignore non-main push (push) Has been cancelled
Sync Helm Chart Tags / Sync chart tags (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
Some checks failed
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Has been cancelled
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Has been cancelled
GitNexus Index / index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Has been cancelled
Sync Helm Chart Tags / Ignore non-main push (push) Has been cancelled
Sync Helm Chart Tags / Sync chart tags (push) Has been cancelled
GitNexus Index / post-index (push) Has been cancelled
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Has been cancelled
* test: cover agent handoffs end to end * style: sort handoff imports * fix: normalize missing agent handoff edges * chore: update package dependencies and versions in package-lock.json and package.json * chore: bump agents SDK
This commit is contained in:
parent
8374b8416a
commit
a53936d273
26 changed files with 3160 additions and 527 deletions
|
|
@ -8,6 +8,7 @@ const {
|
|||
agentUpdateSchema,
|
||||
refreshListAvatars,
|
||||
collectEdgeAgentIds,
|
||||
replaceEdgeSourceId,
|
||||
mergeDeploymentSkillIds,
|
||||
mergeAgentOcrConversion,
|
||||
sanitizeModelParameters,
|
||||
|
|
@ -143,18 +144,30 @@ const classifyAgentReferences = async (agentIds, userId, userRole) => {
|
|||
};
|
||||
|
||||
/**
|
||||
* Validates VIEW access for every agent referenced in `edges`.
|
||||
* Missing ids are NOT errors here — at create time a self-referential
|
||||
* `from` often names the agent being built, which has no DB record
|
||||
* yet. Only unauthorized (existing but unviewable) ids are returned.
|
||||
* Validates that every agent referenced in `edges` exists and is viewable.
|
||||
* The create path may allow its newly generated self id because that agent
|
||||
* has not been inserted yet; all other missing references are invalid.
|
||||
* @param {GraphEdge[]} edges
|
||||
* @param {string} userId
|
||||
* @param {string} userRole
|
||||
* @param {Set<string>} [allowedMissingIds]
|
||||
* @returns {Promise<{ missing: string[], unauthorized: string[] }>}
|
||||
*/
|
||||
const validateEdgeAgentAccess = async (edges, userId, userRole) => {
|
||||
const { unauthorized } = await classifyAgentReferences(
|
||||
const validateEdgeAgentReferences = async (
|
||||
edges,
|
||||
userId,
|
||||
userRole,
|
||||
allowedMissingIds = new Set(),
|
||||
) => {
|
||||
const { missing, unauthorized } = await classifyAgentReferences(
|
||||
collectEdgeAgentIds(edges),
|
||||
userId,
|
||||
userRole,
|
||||
);
|
||||
return unauthorized;
|
||||
return {
|
||||
missing: missing.filter((id) => !allowedMissingIds.has(id)),
|
||||
unauthorized,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -366,6 +379,8 @@ const createAgentHandler = async (req, res) => {
|
|||
}
|
||||
|
||||
const { id: userId, role: userRole } = req.user;
|
||||
agentData.id = `agent_${nanoid()}`;
|
||||
agentData.edges = replaceEdgeSourceId(agentData.edges, '', agentData.id);
|
||||
|
||||
if (agentData.tool_resources) {
|
||||
await pruneToolResourceFileIdsForAgent({
|
||||
|
|
@ -376,7 +391,18 @@ const createAgentHandler = async (req, res) => {
|
|||
}
|
||||
|
||||
if (agentData.edges?.length) {
|
||||
const unauthorized = await validateEdgeAgentAccess(agentData.edges, userId, userRole);
|
||||
const { missing, unauthorized } = await validateEdgeAgentReferences(
|
||||
agentData.edges,
|
||||
userId,
|
||||
userRole,
|
||||
new Set([agentData.id]),
|
||||
);
|
||||
if (missing.length > 0) {
|
||||
return res.status(400).json({
|
||||
error: 'One or more agents referenced in edges do not exist',
|
||||
agent_ids: missing,
|
||||
});
|
||||
}
|
||||
if (unauthorized.length > 0) {
|
||||
return res.status(403).json({
|
||||
error: 'You do not have access to one or more agents referenced in edges',
|
||||
|
|
@ -423,7 +449,6 @@ const createAgentHandler = async (req, res) => {
|
|||
}
|
||||
}
|
||||
|
||||
agentData.id = `agent_${nanoid()}`;
|
||||
agentData.author = userId;
|
||||
agentData.tools = [];
|
||||
|
||||
|
|
@ -629,9 +654,23 @@ const updateAgentHandler = async (req, res) => {
|
|||
updateData.avatar = avatarField;
|
||||
}
|
||||
|
||||
if (updateData.edges !== undefined) {
|
||||
updateData.edges = replaceEdgeSourceId(updateData.edges, '', id);
|
||||
}
|
||||
|
||||
if (updateData.edges?.length) {
|
||||
const { id: userId, role: userRole } = req.user;
|
||||
const unauthorized = await validateEdgeAgentAccess(updateData.edges, userId, userRole);
|
||||
const { missing, unauthorized } = await validateEdgeAgentReferences(
|
||||
updateData.edges,
|
||||
userId,
|
||||
userRole,
|
||||
);
|
||||
if (missing.length > 0) {
|
||||
return res.status(400).json({
|
||||
error: 'One or more agents referenced in edges do not exist',
|
||||
agent_ids: missing,
|
||||
});
|
||||
}
|
||||
if (unauthorized.length > 0) {
|
||||
return res.status(403).json({
|
||||
error: 'You do not have access to one or more agents referenced in edges',
|
||||
|
|
@ -802,7 +841,7 @@ const updateAgentHandler = async (req, res) => {
|
|||
*/
|
||||
const duplicateAgentHandler = async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const { id: userId } = req.user;
|
||||
const { id: userId, role: userRole } = req.user;
|
||||
const sensitiveFields = ['api_key', 'oauth_client_id', 'oauth_client_secret'];
|
||||
|
||||
try {
|
||||
|
|
@ -852,6 +891,29 @@ const duplicateAgentHandler = async (req, res) => {
|
|||
id: newAgentId,
|
||||
author: userId,
|
||||
});
|
||||
newAgentData.edges = replaceEdgeSourceId(newAgentData.edges, id, newAgentId);
|
||||
newAgentData.edges = replaceEdgeSourceId(newAgentData.edges, '', newAgentId);
|
||||
|
||||
if (newAgentData.edges?.length) {
|
||||
const { missing, unauthorized } = await validateEdgeAgentReferences(
|
||||
newAgentData.edges,
|
||||
userId,
|
||||
userRole,
|
||||
new Set([newAgentId]),
|
||||
);
|
||||
if (missing.length > 0) {
|
||||
return res.status(400).json({
|
||||
error: 'One or more agents referenced in edges do not exist',
|
||||
agent_ids: missing,
|
||||
});
|
||||
}
|
||||
if (unauthorized.length > 0) {
|
||||
return res.status(403).json({
|
||||
error: 'You do not have access to one or more agents referenced in edges',
|
||||
agent_ids: unauthorized,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const newActionsList = [];
|
||||
const originalActions = (await db.getActions({ agent_id: id }, true)) ?? [];
|
||||
|
|
@ -1274,10 +1336,42 @@ const revertAgentVersionHandler = async (req, res) => {
|
|||
return res.status(404).json({ error: 'Agent not found' });
|
||||
}
|
||||
|
||||
const revertVersion = existingAgent.versions?.[version_index];
|
||||
const storedRevertEdges = Array.isArray(revertVersion?.edges) ? revertVersion.edges : [];
|
||||
const revertEdges = replaceEdgeSourceId(storedRevertEdges, '', id);
|
||||
const hasLegacyEdgeSource = storedRevertEdges.some((edge) =>
|
||||
Array.isArray(edge.from) ? edge.from.includes('') : edge.from === '',
|
||||
);
|
||||
if (revertEdges.length > 0) {
|
||||
const { missing, unauthorized } = await validateEdgeAgentReferences(
|
||||
revertEdges,
|
||||
req.user.id,
|
||||
req.user.role,
|
||||
);
|
||||
if (missing.length > 0) {
|
||||
return res.status(400).json({
|
||||
error: 'One or more agents referenced in edges do not exist',
|
||||
agent_ids: missing,
|
||||
});
|
||||
}
|
||||
if (unauthorized.length > 0) {
|
||||
return res.status(403).json({
|
||||
error: 'You do not have access to one or more agents referenced in edges',
|
||||
agent_ids: unauthorized,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Permissions are enforced via route middleware (ACL EDIT)
|
||||
|
||||
let updatedAgent = await db.revertAgentVersion({ id }, version_index);
|
||||
const revertUpdates = {};
|
||||
if (
|
||||
revertVersion &&
|
||||
(hasLegacyEdgeSource || (!Array.isArray(revertVersion.edges) && updatedAgent.edges?.length))
|
||||
) {
|
||||
revertUpdates.edges = revertEdges;
|
||||
}
|
||||
|
||||
if (updatedAgent.tools?.length) {
|
||||
const [availableTools, configServers] = await Promise.all([
|
||||
|
|
|
|||
|
|
@ -2475,7 +2475,7 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
|
|||
name: 'Attacker Agent',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
edges: [{ from: 'self_placeholder', to: targetAgent.id, edgeType: 'handoff' }],
|
||||
edges: [{ from: '', to: targetAgent.id, edgeType: 'handoff' }],
|
||||
};
|
||||
|
||||
await createAgentHandler(mockReq, mockRes);
|
||||
|
|
@ -2493,25 +2493,33 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
|
|||
name: 'Legit Agent',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
edges: [{ from: 'self_placeholder', to: targetAgent.id, edgeType: 'handoff' }],
|
||||
edges: [{ from: '', to: targetAgent.id, edgeType: 'handoff' }],
|
||||
};
|
||||
|
||||
await createAgentHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
const response = mockRes.json.mock.calls[0][0];
|
||||
expect(response.edges).toEqual([
|
||||
{ from: response.id, to: targetAgent.id, edgeType: 'handoff' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('createAgentHandler should allow edges referencing non-existent agents (self-reference at create time)', async () => {
|
||||
test('createAgentHandler should reject a non-existent handoff target', async () => {
|
||||
mockReq.body = {
|
||||
name: 'Self-Ref Agent',
|
||||
name: 'Dangling Edge Agent',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
edges: [{ from: 'agent_does_not_exist_yet', to: 'agent_also_new', edgeType: 'handoff' }],
|
||||
edges: [{ from: '', to: 'agent_missing_target', edgeType: 'handoff' }],
|
||||
};
|
||||
|
||||
await createAgentHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'One or more agents referenced in edges do not exist',
|
||||
agent_ids: ['agent_missing_target'],
|
||||
});
|
||||
});
|
||||
|
||||
test('updateAgentHandler should return 403 when user lacks VIEW on an edge-referenced agent', async () => {
|
||||
|
|
@ -2540,6 +2548,42 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
|
|||
expect(response.agent_ids).not.toContain(ownedAgent.id);
|
||||
});
|
||||
|
||||
test('updateAgentHandler should repair a legacy empty handoff source', async () => {
|
||||
const ownedAgent = await Agent.create({
|
||||
id: `agent_${nanoid()}`,
|
||||
author: mockReq.user.id,
|
||||
name: 'Legacy Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [{ from: '', to: targetAgent.id, edgeType: 'handoff' }],
|
||||
});
|
||||
getResourcePermissionsMap.mockResolvedValueOnce(
|
||||
new Map([
|
||||
[ownedAgent._id.toString(), PermissionBits.VIEW],
|
||||
[targetAgent._id.toString(), PermissionBits.VIEW],
|
||||
]),
|
||||
);
|
||||
|
||||
mockReq.params = { id: ownedAgent.id };
|
||||
mockReq.body = {
|
||||
edges: [{ from: '', to: targetAgent.id, edgeType: 'handoff' }],
|
||||
};
|
||||
|
||||
await updateAgentHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).not.toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
edges: [{ from: ownedAgent.id, to: targetAgent.id, edgeType: 'handoff' }],
|
||||
}),
|
||||
);
|
||||
const persisted = await Agent.findOne({ id: ownedAgent.id }).lean();
|
||||
expect(persisted.edges).toEqual([
|
||||
{ from: ownedAgent.id, to: targetAgent.id, edgeType: 'handoff' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('updateAgentHandler should succeed when edges field is absent from payload', async () => {
|
||||
const ownedAgent = await Agent.create({
|
||||
id: `agent_${nanoid()}`,
|
||||
|
|
@ -2559,5 +2603,238 @@ describe('Agent Controllers - Mass Assignment Protection', () => {
|
|||
const response = mockRes.json.mock.calls[0][0];
|
||||
expect(response.name).toBe('Renamed Agent');
|
||||
});
|
||||
|
||||
test('duplicateAgentHandler should move current and legacy handoff sources to the clone', async () => {
|
||||
const sourceAgentId = `agent_${nanoid()}`;
|
||||
const secondTarget = await Agent.create({
|
||||
id: `agent_${nanoid()}`,
|
||||
author: new mongoose.Types.ObjectId().toString(),
|
||||
name: 'Second Target Agent',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
});
|
||||
const sourceAgent = await Agent.create({
|
||||
id: sourceAgentId,
|
||||
author: mockReq.user.id,
|
||||
name: 'Legacy Clone Source',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [
|
||||
{ from: sourceAgentId, to: targetAgent.id, edgeType: 'handoff' },
|
||||
{ from: '', to: secondTarget.id, edgeType: 'handoff' },
|
||||
],
|
||||
});
|
||||
getResourcePermissionsMap.mockResolvedValueOnce(
|
||||
new Map([
|
||||
[targetAgent._id.toString(), PermissionBits.VIEW],
|
||||
[secondTarget._id.toString(), PermissionBits.VIEW],
|
||||
]),
|
||||
);
|
||||
jest.spyOn(require('~/models'), 'getActions').mockResolvedValueOnce([]);
|
||||
|
||||
mockReq.params = { id: sourceAgent.id };
|
||||
|
||||
await duplicateAgentHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(201);
|
||||
const { agent } = mockRes.json.mock.calls[0][0];
|
||||
expect(agent.edges).toEqual([
|
||||
{ from: agent.id, to: targetAgent.id, edgeType: 'handoff' },
|
||||
{ from: agent.id, to: secondTarget.id, edgeType: 'handoff' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('duplicateAgentHandler should return 400 for a missing handoff target', async () => {
|
||||
const missingTargetId = `agent_${nanoid()}`;
|
||||
const sourceAgent = await Agent.create({
|
||||
id: `agent_${nanoid()}`,
|
||||
author: mockReq.user.id,
|
||||
name: 'Stale Clone Source',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [{ from: '', to: missingTargetId, edgeType: 'handoff' }],
|
||||
});
|
||||
|
||||
mockReq.params = { id: sourceAgent.id };
|
||||
|
||||
await duplicateAgentHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'One or more agents referenced in edges do not exist',
|
||||
agent_ids: [missingTargetId],
|
||||
});
|
||||
expect(await Agent.countDocuments()).toBe(2);
|
||||
});
|
||||
|
||||
test('duplicateAgentHandler should return 403 without VIEW access to a handoff target', async () => {
|
||||
const sourceAgentId = `agent_${nanoid()}`;
|
||||
const sourceAgent = await Agent.create({
|
||||
id: sourceAgentId,
|
||||
author: mockReq.user.id,
|
||||
name: 'Restricted Clone Source',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [{ from: sourceAgentId, to: targetAgent.id, edgeType: 'handoff' }],
|
||||
});
|
||||
getResourcePermissionsMap.mockResolvedValueOnce(new Map());
|
||||
|
||||
mockReq.params = { id: sourceAgent.id };
|
||||
|
||||
await duplicateAgentHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(403);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'You do not have access to one or more agents referenced in edges',
|
||||
agent_ids: [targetAgent.id],
|
||||
});
|
||||
expect(await Agent.countDocuments()).toBe(2);
|
||||
});
|
||||
|
||||
test('revertAgentVersionHandler should clear handoffs when the historical version has none', async () => {
|
||||
const agentId = `agent_${nanoid()}`;
|
||||
await Agent.create({
|
||||
id: agentId,
|
||||
author: mockReq.user.id,
|
||||
name: 'Current Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [{ from: agentId, to: targetAgent.id, edgeType: 'handoff' }],
|
||||
versions: [
|
||||
{
|
||||
name: 'Historical Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
mockReq.params = { id: agentId };
|
||||
mockReq.body = { version_index: 0 };
|
||||
|
||||
await revertAgentVersionHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).not.toHaveBeenCalledWith(400);
|
||||
const persisted = await Agent.findOne({ id: agentId }).lean();
|
||||
expect(persisted.name).toBe('Historical Router');
|
||||
expect(persisted.edges).toEqual([]);
|
||||
});
|
||||
|
||||
test('revertAgentVersionHandler should restore accessible historical handoffs', async () => {
|
||||
const agentId = `agent_${nanoid()}`;
|
||||
const sourceAgent = await Agent.create({
|
||||
id: agentId,
|
||||
author: mockReq.user.id,
|
||||
name: 'Current Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [],
|
||||
versions: [
|
||||
{
|
||||
name: 'Historical Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [{ from: '', to: targetAgent.id, edgeType: 'handoff' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
getResourcePermissionsMap.mockResolvedValueOnce(
|
||||
new Map([
|
||||
[sourceAgent._id.toString(), PermissionBits.VIEW],
|
||||
[targetAgent._id.toString(), PermissionBits.VIEW],
|
||||
]),
|
||||
);
|
||||
|
||||
mockReq.params = { id: agentId };
|
||||
mockReq.body = { version_index: 0 };
|
||||
|
||||
await revertAgentVersionHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).not.toHaveBeenCalledWith(400);
|
||||
expect(mockRes.status).not.toHaveBeenCalledWith(403);
|
||||
const persisted = await Agent.findOne({ id: agentId }).lean();
|
||||
expect(persisted.name).toBe('Historical Router');
|
||||
expect(persisted.edges).toEqual([{ from: agentId, to: targetAgent.id, edgeType: 'handoff' }]);
|
||||
});
|
||||
|
||||
test('revertAgentVersionHandler should return 400 before restoring a missing handoff target', async () => {
|
||||
const agentId = `agent_${nanoid()}`;
|
||||
const missingTargetId = `agent_${nanoid()}`;
|
||||
await Agent.create({
|
||||
id: agentId,
|
||||
author: mockReq.user.id,
|
||||
name: 'Current Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
versions: [
|
||||
{
|
||||
name: 'Stale Historical Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [{ from: agentId, to: missingTargetId, edgeType: 'handoff' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
mockReq.params = { id: agentId };
|
||||
mockReq.body = { version_index: 0 };
|
||||
|
||||
await revertAgentVersionHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(400);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'One or more agents referenced in edges do not exist',
|
||||
agent_ids: [missingTargetId],
|
||||
});
|
||||
const persisted = await Agent.findOne({ id: agentId }).lean();
|
||||
expect(persisted.name).toBe('Current Router');
|
||||
});
|
||||
|
||||
test('revertAgentVersionHandler should return 403 before restoring a restricted handoff target', async () => {
|
||||
const agentId = `agent_${nanoid()}`;
|
||||
const sourceAgent = await Agent.create({
|
||||
id: agentId,
|
||||
author: mockReq.user.id,
|
||||
name: 'Current Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
versions: [
|
||||
{
|
||||
name: 'Restricted Historical Router',
|
||||
provider: 'openai',
|
||||
model: 'gpt-4',
|
||||
tools: [],
|
||||
edges: [{ from: agentId, to: targetAgent.id, edgeType: 'handoff' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
getResourcePermissionsMap.mockResolvedValueOnce(
|
||||
new Map([[sourceAgent._id.toString(), PermissionBits.VIEW]]),
|
||||
);
|
||||
|
||||
mockReq.params = { id: agentId };
|
||||
mockReq.body = { version_index: 0 };
|
||||
|
||||
await revertAgentVersionHandler(mockReq, mockRes);
|
||||
|
||||
expect(mockRes.status).toHaveBeenCalledWith(403);
|
||||
expect(mockRes.json).toHaveBeenCalledWith({
|
||||
error: 'You do not have access to one or more agents referenced in edges',
|
||||
agent_ids: [targetAgent.id],
|
||||
});
|
||||
const persisted = await Agent.findOne({ id: agentId }).lean();
|
||||
expect(persisted.name).toBe('Current Router');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue