first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
+159
@@ -0,0 +1,159 @@
|
||||
import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import { AgentExecutor } from '@langchain/classic/agents';
|
||||
import type { Tool } from '@langchain/classic/tools';
|
||||
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
||||
|
||||
import * as helpers from '../../../../../utils/helpers';
|
||||
import { toolsAgentExecute } from '../../agents/ToolsAgent/V1/execute';
|
||||
|
||||
const mockHelpers = mock<IExecuteFunctions['helpers']>();
|
||||
const mockContext = mock<IExecuteFunctions>({ helpers: mockHelpers });
|
||||
|
||||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
describe('toolsAgentExecute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockContext.logger = {
|
||||
debug: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
error: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
it('should process items', async () => {
|
||||
const mockNode = mock<INode>();
|
||||
mockContext.getNode.mockReturnValue(mockNode);
|
||||
mockContext.getInputData.mockReturnValue([
|
||||
{ json: { text: 'test input 1' } },
|
||||
{ json: { text: 'test input 2' } },
|
||||
]);
|
||||
|
||||
const mockModel = mock<BaseChatModel>();
|
||||
mockModel.bindTools = jest.fn();
|
||||
mockModel.lc_namespace = ['chat_models'];
|
||||
mockContext.getInputConnectionData.mockResolvedValue(mockModel);
|
||||
|
||||
const mockTools = [mock<Tool>()];
|
||||
jest.spyOn(helpers, 'getConnectedTools').mockResolvedValue(mockTools);
|
||||
|
||||
// Mock getNodeParameter to return default values
|
||||
mockContext.getNodeParameter.mockImplementation((param, _i, defaultValue) => {
|
||||
if (param === 'text') return 'test input';
|
||||
if (param === 'options')
|
||||
return {
|
||||
systemMessage: 'You are a helpful assistant',
|
||||
maxIterations: 10,
|
||||
returnIntermediateSteps: false,
|
||||
passthroughBinaryImages: true,
|
||||
};
|
||||
return defaultValue;
|
||||
});
|
||||
|
||||
const mockExecutor = {
|
||||
invoke: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ output: JSON.stringify({ text: 'success 1' }) })
|
||||
.mockResolvedValueOnce({ output: JSON.stringify({ text: 'success 2' }) }),
|
||||
};
|
||||
|
||||
jest.spyOn(AgentExecutor, 'fromAgentAndTools').mockReturnValue(mockExecutor as any);
|
||||
|
||||
const result = await toolsAgentExecute.call(mockContext);
|
||||
|
||||
expect(mockExecutor.invoke).toHaveBeenCalledTimes(2);
|
||||
expect(result[0]).toHaveLength(2);
|
||||
expect(result[0][0].json).toEqual({ output: { text: 'success 1' } });
|
||||
expect(result[0][1].json).toEqual({ output: { text: 'success 2' } });
|
||||
});
|
||||
|
||||
it('should handle errors when continueOnFail is true', async () => {
|
||||
const mockNode = mock<INode>();
|
||||
mockContext.getNode.mockReturnValue(mockNode);
|
||||
mockContext.getInputData.mockReturnValue([
|
||||
{ json: { text: 'test input 1' } },
|
||||
{ json: { text: 'test input 2' } },
|
||||
]);
|
||||
|
||||
const mockModel = mock<BaseChatModel>();
|
||||
mockModel.bindTools = jest.fn();
|
||||
mockModel.lc_namespace = ['chat_models'];
|
||||
mockContext.getInputConnectionData.mockResolvedValue(mockModel);
|
||||
|
||||
const mockTools = [mock<Tool>()];
|
||||
jest.spyOn(helpers, 'getConnectedTools').mockResolvedValue(mockTools);
|
||||
|
||||
mockContext.getNodeParameter.mockImplementation((param, _i, defaultValue) => {
|
||||
if (param === 'text') return 'test input';
|
||||
if (param === 'options')
|
||||
return {
|
||||
systemMessage: 'You are a helpful assistant',
|
||||
maxIterations: 10,
|
||||
returnIntermediateSteps: false,
|
||||
passthroughBinaryImages: true,
|
||||
};
|
||||
return defaultValue;
|
||||
});
|
||||
|
||||
mockContext.continueOnFail.mockReturnValue(true);
|
||||
|
||||
const mockExecutor = {
|
||||
invoke: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ output: '{ "text": "success" }' })
|
||||
.mockRejectedValueOnce(new Error('Test error')),
|
||||
};
|
||||
|
||||
jest.spyOn(AgentExecutor, 'fromAgentAndTools').mockReturnValue(mockExecutor as any);
|
||||
|
||||
const result = await toolsAgentExecute.call(mockContext);
|
||||
|
||||
expect(result[0]).toHaveLength(2);
|
||||
expect(result[0][0].json).toEqual({ output: { text: 'success' } });
|
||||
expect(result[0][1].json).toEqual({ error: 'Test error' });
|
||||
});
|
||||
|
||||
it('should throw error in when continueOnFail is false', async () => {
|
||||
const mockNode = mock<INode>();
|
||||
mockContext.getNode.mockReturnValue(mockNode);
|
||||
mockContext.getInputData.mockReturnValue([
|
||||
{ json: { text: 'test input 1' } },
|
||||
{ json: { text: 'test input 2' } },
|
||||
]);
|
||||
|
||||
const mockModel = mock<BaseChatModel>();
|
||||
mockModel.bindTools = jest.fn();
|
||||
mockModel.lc_namespace = ['chat_models'];
|
||||
mockContext.getInputConnectionData.mockResolvedValue(mockModel);
|
||||
|
||||
const mockTools = [mock<Tool>()];
|
||||
jest.spyOn(helpers, 'getConnectedTools').mockResolvedValue(mockTools);
|
||||
|
||||
mockContext.getNodeParameter.mockImplementation((param, _i, defaultValue) => {
|
||||
if (param === 'text') return 'test input';
|
||||
if (param === 'options')
|
||||
return {
|
||||
systemMessage: 'You are a helpful assistant',
|
||||
maxIterations: 10,
|
||||
returnIntermediateSteps: false,
|
||||
passthroughBinaryImages: true,
|
||||
};
|
||||
return defaultValue;
|
||||
});
|
||||
|
||||
mockContext.continueOnFail.mockReturnValue(false);
|
||||
|
||||
const mockExecutor = {
|
||||
invoke: jest
|
||||
.fn()
|
||||
.mockResolvedValueOnce({ output: JSON.stringify({ text: 'success' }) })
|
||||
.mockRejectedValueOnce(new Error('Test error')),
|
||||
};
|
||||
|
||||
jest.spyOn(AgentExecutor, 'fromAgentAndTools').mockReturnValue(mockExecutor as any);
|
||||
|
||||
await expect(toolsAgentExecute.call(mockContext)).rejects.toThrow('Test error');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user