n8cn / packages /cli /src /task-runners /task-managers /__tests__ /data-request-response-stripper.test.ts
| import type { DataRequestResponse, TaskDataRequestParams } from '@n8n/task-runner'; | |
| import { mock } from 'jest-mock-extended'; | |
| import type { IWorkflowExecuteAdditionalData } from 'n8n-workflow'; | |
| import { type INode, type INodeExecutionData } from 'n8n-workflow'; | |
| import { DataRequestResponseStripper } from '../data-request-response-stripper'; | |
| const triggerNode: INode = mock<INode>({ | |
| name: 'Trigger', | |
| }); | |
| const debugHelperNode: INode = mock<INode>({ | |
| name: 'DebugHelper', | |
| }); | |
| const codeNode: INode = mock<INode>({ | |
| name: 'Code', | |
| }); | |
| const workflow: DataRequestResponse['workflow'] = mock<DataRequestResponse['workflow']>(); | |
| const debugHelperNodeOutItems: INodeExecutionData[] = [ | |
| { | |
| json: { | |
| idx: 0, | |
| uid: 'abb74fd4-bef2-4fae-9d53-ea24e9eb3032', | |
| email: 'Dan.Schmidt31@yahoo.com', | |
| firstname: 'Toni', | |
| lastname: 'Schuster', | |
| password: 'Q!D6C2', | |
| }, | |
| pairedItem: { | |
| item: 0, | |
| }, | |
| }, | |
| { | |
| json: { | |
| idx: 1, | |
| uid: '4620e4-c1b4-dd8b-9a45-d0f9a29a3b7f', | |
| email: 'bob.johnson@domain.com', | |
| firstName: 'Bob', | |
| lastName: 'Johnson', | |
| password: '6e41b5ecf', | |
| }, | |
| pairedItem: { | |
| item: 1, | |
| }, | |
| }, | |
| { | |
| json: { | |
| idx: 2, | |
| email: '4358d3-418b-a8b3-49cb-076b1180f402', | |
| firstName: 'Eve', | |
| lastName: 'Johnson', | |
| password: 'e2414620e', | |
| }, | |
| pairedItem: { | |
| item: 2, | |
| }, | |
| }, | |
| ]; | |
| const codeNodeInputItems: INodeExecutionData[] = debugHelperNodeOutItems; | |
| const envProviderState: DataRequestResponse['envProviderState'] = mock< | |
| DataRequestResponse['envProviderState'] | |
| >({ | |
| env: {}, | |
| isEnvAccessBlocked: false, | |
| isProcessAvailable: true, | |
| }); | |
| const additionalData = mock<IWorkflowExecuteAdditionalData>({ | |
| formWaitingBaseUrl: 'http://localhost:5678/form-waiting', | |
| instanceBaseUrl: 'http://localhost:5678/', | |
| restApiUrl: 'http://localhost:5678/rest', | |
| variables: {}, | |
| webhookBaseUrl: 'http://localhost:5678/webhook', | |
| webhookTestBaseUrl: 'http://localhost:5678/webhook-test', | |
| webhookWaitingBaseUrl: 'http://localhost:5678/webhook-waiting', | |
| executionId: '45844', | |
| userId: '114984bc-44b3-4dd4-9b54-a4a8d34d51d5', | |
| currentNodeParameters: undefined, | |
| executionTimeoutTimestamp: undefined, | |
| restartExecutionId: undefined, | |
| }); | |
| /** | |
| * Drawn with https://asciiflow.com/#/ | |
| * Task data for an execution of the following WF: | |
| * where ►► denotes the currently being executing node. | |
| * ►► | |
| * ┌───────────┐ ┌─────────────┐ ┌────────┐ | |
| * │ Trigger ├──►│ DebugHelper ├───►│ Code │ | |
| * └───────────┘ └─────────────┘ └────────┘ | |
| */ | |
| const taskData: DataRequestResponse = { | |
| workflow, | |
| inputData: { | |
| main: [codeNodeInputItems], | |
| }, | |
| itemIndex: 0, | |
| activeNodeName: codeNode.name, | |
| contextNodeName: codeNode.name, | |
| defaultReturnRunIndex: -1, | |
| mode: 'manual', | |
| envProviderState, | |
| node: codeNode, | |
| runExecutionData: { | |
| startData: { | |
| destinationNode: codeNode.name, | |
| runNodeFilter: [triggerNode.name, debugHelperNode.name, codeNode.name], | |
| }, | |
| resultData: { | |
| runData: { | |
| [triggerNode.name]: [ | |
| { | |
| hints: [], | |
| startTime: 1730313407328, | |
| executionIndex: 0, | |
| executionTime: 1, | |
| source: [], | |
| executionStatus: 'success', | |
| data: { | |
| main: [[]], | |
| }, | |
| }, | |
| ], | |
| [debugHelperNode.name]: [ | |
| { | |
| hints: [], | |
| startTime: 1730313407330, | |
| executionIndex: 1, | |
| executionTime: 1, | |
| source: [ | |
| { | |
| previousNode: triggerNode.name, | |
| }, | |
| ], | |
| executionStatus: 'success', | |
| data: { | |
| main: [debugHelperNodeOutItems], | |
| }, | |
| }, | |
| ], | |
| }, | |
| pinData: {}, | |
| }, | |
| executionData: { | |
| contextData: {}, | |
| nodeExecutionStack: [], | |
| metadata: {}, | |
| waitingExecution: {}, | |
| waitingExecutionSource: {}, | |
| }, | |
| }, | |
| runIndex: 0, | |
| selfData: {}, | |
| siblingParameters: {}, | |
| connectionInputSource: { | |
| main: [ | |
| { | |
| previousNode: debugHelperNode.name, | |
| previousNodeOutput: 0, | |
| }, | |
| ], | |
| }, | |
| additionalData, | |
| } as const; | |
| describe('DataRequestResponseStripper', () => { | |
| const allDataParam: TaskDataRequestParams = { | |
| dataOfNodes: 'all', | |
| env: true, | |
| input: { | |
| include: true, | |
| }, | |
| prevNode: true, | |
| }; | |
| const newRequestParam = (opts: Partial<TaskDataRequestParams>) => ({ | |
| ...allDataParam, | |
| ...opts, | |
| }); | |
| describe('all data', () => { | |
| it('should build the runExecutionData as is when everything is requested', () => { | |
| const dataRequestResponseBuilder = new DataRequestResponseStripper(taskData, allDataParam); | |
| const { runExecutionData } = dataRequestResponseBuilder.strip(); | |
| expect(runExecutionData).toStrictEqual(taskData.runExecutionData); | |
| }); | |
| }); | |
| describe('envProviderState', () => { | |
| it("should filter out envProviderState when it's not requested", () => { | |
| const dataRequestResponseBuilder = new DataRequestResponseStripper( | |
| taskData, | |
| newRequestParam({ | |
| env: false, | |
| }), | |
| ); | |
| const result = dataRequestResponseBuilder.strip(); | |
| expect(result.envProviderState).toStrictEqual({ | |
| env: {}, | |
| isEnvAccessBlocked: false, | |
| isProcessAvailable: true, | |
| }); | |
| }); | |
| }); | |
| describe('input data', () => { | |
| const allExceptInputParam = newRequestParam({ | |
| input: { | |
| include: false, | |
| }, | |
| }); | |
| it('drops input data from result', () => { | |
| const result = new DataRequestResponseStripper(taskData, allExceptInputParam).strip(); | |
| expect(result.inputData).toStrictEqual({}); | |
| }); | |
| it('returns only chunked data', () => { | |
| const result = new DataRequestResponseStripper( | |
| taskData, | |
| newRequestParam({ | |
| input: { | |
| include: true, | |
| chunk: { | |
| startIndex: 1, | |
| count: 1, | |
| }, | |
| }, | |
| }), | |
| ).strip(); | |
| expect(result.inputData).toStrictEqual({ | |
| main: [debugHelperNodeOutItems.slice(1, 2)], | |
| }); | |
| }); | |
| }); | |
| describe('nodes', () => { | |
| it('should return empty run data when only Code node is requested', () => { | |
| const result = new DataRequestResponseStripper( | |
| taskData, | |
| newRequestParam({ dataOfNodes: ['Code'], prevNode: false }), | |
| ).strip(); | |
| expect(result.runExecutionData.resultData.runData).toStrictEqual({}); | |
| expect(result.runExecutionData.resultData.pinData).toStrictEqual({}); | |
| // executionData & startData contain only metadata --> returned as is | |
| expect(result.runExecutionData.startData).toStrictEqual(taskData.runExecutionData.startData); | |
| expect(result.runExecutionData.executionData).toStrictEqual( | |
| taskData.runExecutionData.executionData, | |
| ); | |
| }); | |
| it('should return empty run data when only Code node is requested', () => { | |
| const result = new DataRequestResponseStripper( | |
| taskData, | |
| newRequestParam({ dataOfNodes: [codeNode.name], prevNode: false }), | |
| ).strip(); | |
| expect(result.runExecutionData.resultData.runData).toStrictEqual({}); | |
| expect(result.runExecutionData.resultData.pinData).toStrictEqual({}); | |
| // executionData & startData contain only metadata --> returned as is | |
| expect(result.runExecutionData.startData).toStrictEqual(taskData.runExecutionData.startData); | |
| expect(result.runExecutionData.executionData).toStrictEqual( | |
| taskData.runExecutionData.executionData, | |
| ); | |
| }); | |
| it("should return only DebugHelper's data when only DebugHelper node is requested", () => { | |
| const result = new DataRequestResponseStripper( | |
| taskData, | |
| newRequestParam({ dataOfNodes: [debugHelperNode.name], prevNode: false }), | |
| ).strip(); | |
| expect(result.runExecutionData.resultData.runData).toStrictEqual({ | |
| [debugHelperNode.name]: taskData.runExecutionData.resultData.runData[debugHelperNode.name], | |
| }); | |
| expect(result.runExecutionData.resultData.pinData).toStrictEqual({}); | |
| // executionData & startData contain only metadata --> returned as is | |
| expect(result.runExecutionData.startData).toStrictEqual(taskData.runExecutionData.startData); | |
| expect(result.runExecutionData.executionData).toStrictEqual( | |
| taskData.runExecutionData.executionData, | |
| ); | |
| }); | |
| it("should return DebugHelper's data when only prevNode node is requested", () => { | |
| const result = new DataRequestResponseStripper( | |
| taskData, | |
| newRequestParam({ dataOfNodes: [], prevNode: true }), | |
| ).strip(); | |
| expect(result.runExecutionData.resultData.runData).toStrictEqual({ | |
| [debugHelperNode.name]: taskData.runExecutionData.resultData.runData[debugHelperNode.name], | |
| }); | |
| expect(result.runExecutionData.resultData.pinData).toStrictEqual({}); | |
| // executionData & startData contain only metadata --> returned as is | |
| expect(result.runExecutionData.startData).toStrictEqual(taskData.runExecutionData.startData); | |
| expect(result.runExecutionData.executionData).toStrictEqual( | |
| taskData.runExecutionData.executionData, | |
| ); | |
| }); | |
| }); | |
| describe('passthrough properties', () => { | |
| test.each<Array<keyof DataRequestResponse>>([ | |
| ['workflow'], | |
| ['connectionInputSource'], | |
| ['node'], | |
| ['runIndex'], | |
| ['itemIndex'], | |
| ['activeNodeName'], | |
| ['siblingParameters'], | |
| ['mode'], | |
| ['defaultReturnRunIndex'], | |
| ['selfData'], | |
| ['contextNodeName'], | |
| ['additionalData'], | |
| ])("it doesn't change %s", (propertyName) => { | |
| const dataRequestResponseBuilder = new DataRequestResponseStripper(taskData, allDataParam); | |
| const result = dataRequestResponseBuilder.strip(); | |
| expect(result[propertyName]).toBe(taskData[propertyName]); | |
| }); | |
| }); | |
| }); | |