mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-27 13:39:44 -08:00
170 lines
4 KiB
TypeScript
170 lines
4 KiB
TypeScript
|
import { N8nTool } from './N8nTool';
|
||
|
import { createMockExecuteFunction } from 'n8n-nodes-base/test/nodes/Helpers';
|
||
|
import { z } from 'zod';
|
||
|
import type { INode } from 'n8n-workflow';
|
||
|
import { DynamicStructuredTool, DynamicTool } from '@langchain/core/tools';
|
||
|
|
||
|
const mockNode: INode = {
|
||
|
id: '1',
|
||
|
name: 'Mock node',
|
||
|
typeVersion: 2,
|
||
|
type: 'n8n-nodes-base.mock',
|
||
|
position: [60, 760],
|
||
|
parameters: {
|
||
|
operation: 'test',
|
||
|
},
|
||
|
};
|
||
|
|
||
|
describe('Test N8nTool wrapper as DynamicStructuredTool', () => {
|
||
|
it('should wrap a tool', () => {
|
||
|
const func = jest.fn();
|
||
|
|
||
|
const ctx = createMockExecuteFunction({}, mockNode);
|
||
|
|
||
|
const tool = new N8nTool(ctx, {
|
||
|
name: 'Dummy Tool',
|
||
|
description: 'A dummy tool for testing',
|
||
|
func,
|
||
|
schema: z.object({
|
||
|
foo: z.string(),
|
||
|
}),
|
||
|
});
|
||
|
|
||
|
expect(tool).toBeInstanceOf(DynamicStructuredTool);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('Test N8nTool wrapper - DynamicTool fallback', () => {
|
||
|
it('should convert the tool to a dynamic tool', () => {
|
||
|
const func = jest.fn();
|
||
|
|
||
|
const ctx = createMockExecuteFunction({}, mockNode);
|
||
|
|
||
|
const tool = new N8nTool(ctx, {
|
||
|
name: 'Dummy Tool',
|
||
|
description: 'A dummy tool for testing',
|
||
|
func,
|
||
|
schema: z.object({
|
||
|
foo: z.string(),
|
||
|
}),
|
||
|
});
|
||
|
|
||
|
const dynamicTool = tool.asDynamicTool();
|
||
|
|
||
|
expect(dynamicTool).toBeInstanceOf(DynamicTool);
|
||
|
});
|
||
|
|
||
|
it('should format fallback description correctly', () => {
|
||
|
const func = jest.fn();
|
||
|
|
||
|
const ctx = createMockExecuteFunction({}, mockNode);
|
||
|
|
||
|
const tool = new N8nTool(ctx, {
|
||
|
name: 'Dummy Tool',
|
||
|
description: 'A dummy tool for testing',
|
||
|
func,
|
||
|
schema: z.object({
|
||
|
foo: z.string(),
|
||
|
bar: z.number().optional(),
|
||
|
qwe: z.boolean().describe('Boolean description'),
|
||
|
}),
|
||
|
});
|
||
|
|
||
|
const dynamicTool = tool.asDynamicTool();
|
||
|
|
||
|
expect(dynamicTool.description).toContain('foo: (description: , type: string, required: true)');
|
||
|
expect(dynamicTool.description).toContain(
|
||
|
'bar: (description: , type: number, required: false)',
|
||
|
);
|
||
|
|
||
|
expect(dynamicTool.description).toContain(
|
||
|
'qwe: (description: Boolean description, type: boolean, required: true)',
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('should handle empty parameter list correctly', () => {
|
||
|
const func = jest.fn();
|
||
|
|
||
|
const ctx = createMockExecuteFunction({}, mockNode);
|
||
|
|
||
|
const tool = new N8nTool(ctx, {
|
||
|
name: 'Dummy Tool',
|
||
|
description: 'A dummy tool for testing',
|
||
|
func,
|
||
|
schema: z.object({}),
|
||
|
});
|
||
|
|
||
|
const dynamicTool = tool.asDynamicTool();
|
||
|
|
||
|
expect(dynamicTool.description).toEqual('A dummy tool for testing');
|
||
|
});
|
||
|
|
||
|
it('should parse correct parameters', async () => {
|
||
|
const func = jest.fn();
|
||
|
|
||
|
const ctx = createMockExecuteFunction({}, mockNode);
|
||
|
|
||
|
const tool = new N8nTool(ctx, {
|
||
|
name: 'Dummy Tool',
|
||
|
description: 'A dummy tool for testing',
|
||
|
func,
|
||
|
schema: z.object({
|
||
|
foo: z.string().describe('Foo description'),
|
||
|
bar: z.number().optional(),
|
||
|
}),
|
||
|
});
|
||
|
|
||
|
const dynamicTool = tool.asDynamicTool();
|
||
|
|
||
|
const testParameters = { foo: 'some value' };
|
||
|
|
||
|
await dynamicTool.func(JSON.stringify(testParameters));
|
||
|
|
||
|
expect(func).toHaveBeenCalledWith(testParameters);
|
||
|
});
|
||
|
|
||
|
it('should recover when 1 parameter is passed directly', async () => {
|
||
|
const func = jest.fn();
|
||
|
|
||
|
const ctx = createMockExecuteFunction({}, mockNode);
|
||
|
|
||
|
const tool = new N8nTool(ctx, {
|
||
|
name: 'Dummy Tool',
|
||
|
description: 'A dummy tool for testing',
|
||
|
func,
|
||
|
schema: z.object({
|
||
|
foo: z.string().describe('Foo description'),
|
||
|
}),
|
||
|
});
|
||
|
|
||
|
const dynamicTool = tool.asDynamicTool();
|
||
|
|
||
|
const testParameter = 'some value';
|
||
|
|
||
|
await dynamicTool.func(testParameter);
|
||
|
|
||
|
expect(func).toHaveBeenCalledWith({ foo: testParameter });
|
||
|
});
|
||
|
|
||
|
it('should recover when JS object is passed instead of JSON', async () => {
|
||
|
const func = jest.fn();
|
||
|
|
||
|
const ctx = createMockExecuteFunction({}, mockNode);
|
||
|
|
||
|
const tool = new N8nTool(ctx, {
|
||
|
name: 'Dummy Tool',
|
||
|
description: 'A dummy tool for testing',
|
||
|
func,
|
||
|
schema: z.object({
|
||
|
foo: z.string().describe('Foo description'),
|
||
|
}),
|
||
|
});
|
||
|
|
||
|
const dynamicTool = tool.asDynamicTool();
|
||
|
|
||
|
await dynamicTool.func('{ foo: "some value" }');
|
||
|
|
||
|
expect(func).toHaveBeenCalledWith({ foo: 'some value' });
|
||
|
});
|
||
|
});
|