n8n/packages/@n8n/nodes-langchain/utils/N8nTool.test.ts

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' });
});
});