diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.test.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.test.ts index 43e0acc1ee0..05dc372c4de 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.test.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.test.ts @@ -40,7 +40,7 @@ describe('ToolSerpApi', () => { const node = new ToolSerpApi(); const inputData: INodeExecutionData[] = [ { - json: { query: 'artificial intelligence news' }, + json: { input: 'artificial intelligence news' }, }, ]; @@ -69,17 +69,17 @@ describe('ToolSerpApi', () => { }, ], ]); - expect(SerpAPI.prototype.invoke).toHaveBeenCalledWith(inputData[0]); + expect(SerpAPI.prototype.invoke).toHaveBeenCalledWith(inputData[0].json); }); it('should handle multiple input items', async () => { const node = new ToolSerpApi(); const inputData: INodeExecutionData[] = [ { - json: { query: 'machine learning' }, + json: { input: 'machine learning' }, }, { - json: { query: 'deep learning' }, + json: { input: 'deep learning' }, }, ]; @@ -125,7 +125,7 @@ describe('ToolSerpApi', () => { const node = new ToolSerpApi(); const inputData: INodeExecutionData[] = [ { - json: { query: 'test query' }, + json: { input: 'test query' }, }, ]; @@ -144,5 +144,25 @@ describe('ToolSerpApi', () => { expect(mockExecute.getCredentials).toHaveBeenCalledWith('serpApi'); expect(mockExecute.getNodeParameter).toHaveBeenCalledWith('options', 0); }); + + it('should fail gracefully if input is missing', async () => { + const node = new ToolSerpApi(); + const inputData: INodeExecutionData[] = [ + { + json: {}, + }, + ]; + + const mockExecute = mock({ + getInputData: jest.fn(() => inputData), + getNode: jest.fn(() => mock({ name: 'test serpapi' })), + getCredentials: jest.fn().mockResolvedValue({ apiKey: 'test-api-key' }), + getNodeParameter: jest.fn().mockReturnValue({}), + }); + + await expect(node.execute.call(mockExecute)).rejects.toThrow( + 'Missing search query input at itemIndex 0', + ); + }); }); }); diff --git a/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.ts b/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.ts index 6b21ca41389..152fe9eedc2 100644 --- a/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.ts +++ b/packages/@n8n/nodes-langchain/nodes/tools/ToolSerpApi/ToolSerpApi.node.ts @@ -1,4 +1,6 @@ import { SerpAPI } from '@langchain/community/tools/serpapi'; +import { logWrapper } from '@utils/logWrapper'; +import { getConnectionHintNoticeField } from '@utils/sharedFields'; import { type IExecuteFunctions, NodeConnectionTypes, @@ -7,10 +9,9 @@ import { type ISupplyDataFunctions, type SupplyData, type INodeExecutionData, + NodeOperationError, } from 'n8n-workflow'; -import { logWrapper } from '@utils/logWrapper'; -import { getConnectionHintNoticeField } from '@utils/sharedFields'; async function getTool(ctx: ISupplyDataFunctions | IExecuteFunctions, itemIndex: number) { const credentials = await ctx.getCredentials('serpApi'); @@ -133,8 +134,17 @@ export class ToolSerpApi implements INodeType { const returnData: INodeExecutionData[] = []; for (let itemIndex = 0; itemIndex < inputData.length; itemIndex++) { const tool = await getTool(this, itemIndex); - const query = inputData[itemIndex]; - const result = await tool.invoke(query); + const item = inputData[itemIndex].json; + + if (typeof item.input !== 'string' || !item.input) { + throw new NodeOperationError( + this.getNode(), + `Missing search query input at itemIndex ${itemIndex}`, + ); + } + + const result = (await tool.invoke(item)) as string; + returnData.push({ json: { response: result,