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