Files
n8n-enterprise-unlocked/packages/nodes-base/utils/workflow-backtracking.test.ts

533 lines
14 KiB
TypeScript

import type {
INodeExecutionData,
IPairedItemData,
IRunExecutionData,
ISourceData,
ITaskData,
} from 'n8n-workflow';
import { previousTaskData, findPairedItemTroughWorkflowData } from './workflow-backtracking';
describe('backtracking.ts', () => {
describe('previousTaskData', () => {
it('should return undefined when source is empty', () => {
const runData = {};
const currentRunData: ITaskData = {
source: [],
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
};
const result = previousTaskData(runData, currentRunData);
expect(result).toBeUndefined();
});
it('should return undefined when source is undefined', () => {
const runData = {};
const currentRunData: ITaskData = {
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
} as unknown as ITaskData; // Type assertion to match the expected type
const result = previousTaskData(runData, currentRunData);
expect(result).toBeUndefined();
});
it('should return undefined when previousNode is undefined', () => {
const runData = {};
const currentRunData: ITaskData = {
source: [{} as unknown as ISourceData],
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
};
const result = previousTaskData(runData, currentRunData);
expect(result).toBeUndefined();
});
it('should return undefined when run data for previousNode does not exist', () => {
const runData = {};
const currentRunData: ITaskData = {
source: [{ previousNode: 'node1' }],
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
};
const result = previousTaskData(runData, currentRunData);
expect(result).toBeUndefined();
});
it('should return undefined when run data for previousNode is empty', () => {
const runData = {
node1: [],
};
const currentRunData: ITaskData = {
source: [{ previousNode: 'node1' }],
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
};
const result = previousTaskData(runData, currentRunData);
expect(result).toBeUndefined();
});
it('should return the correct task data from previousNode', () => {
const expectedTaskData: ITaskData = {
data: { main: [[{ json: { test: 'value' } }]] },
executionTime: 100,
executionStatus: 'success',
executionIndex: 0,
startTime: 1000,
} as unknown as ITaskData;
const runData = {
node1: [expectedTaskData],
};
const currentRunData: ITaskData = {
source: [{ previousNode: 'node1' }],
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
};
const result = previousTaskData(runData, currentRunData);
expect(result).toBe(expectedTaskData);
});
it('should return correct task data using previousNodeRun index', () => {
const taskData1: ITaskData = {
data: { main: [[{ json: { run: 1 } }]] },
executionTime: 100,
executionStatus: 'success',
executionIndex: 0,
startTime: 1000,
} as unknown as ITaskData;
const taskData2: ITaskData = {
data: { main: [[{ json: { run: 2 } }]] },
executionTime: 200,
executionStatus: 'success',
executionIndex: 0,
startTime: 2000,
} as unknown as ITaskData;
const runData = {
node1: [taskData1, taskData2],
};
const currentRunData: ITaskData = {
source: [{ previousNode: 'node1', previousNodeRun: 1 }],
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
};
const result = previousTaskData(runData, currentRunData);
expect(result).toBe(taskData2);
});
it('should default to index 0 when previousNodeRun is undefined', () => {
const taskData1: ITaskData = {
data: { main: [[{ json: { run: 1 } }]] },
executionTime: 100,
executionStatus: 'success',
executionIndex: 0,
startTime: 1000,
} as unknown as ITaskData;
const taskData2: ITaskData = {
data: { main: [[{ json: { run: 2 } }]] },
executionTime: 200,
executionStatus: 'success',
executionIndex: 0,
startTime: 2000,
} as unknown as ITaskData;
const runData = {
node1: [taskData1, taskData2],
};
const currentRunData: ITaskData = {
source: [{ previousNode: 'node1' }],
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
executionIndex: 0,
startTime: 0,
};
const result = previousTaskData(runData, currentRunData);
expect(result).toBe(taskData1);
});
});
describe('findPairedItemTroughWorkflowData', () => {
it('should return undefined when lastNodeExecuted is undefined', () => {
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {},
lastNodeExecuted: undefined,
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBeUndefined();
});
it('should return undefined when no run data exists for lastNodeExecuted', () => {
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {},
lastNodeExecuted: 'node1',
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBeUndefined();
});
it('should return undefined when run data is empty', () => {
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [],
},
lastNodeExecuted: 'node1',
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBeUndefined();
});
it('should return undefined when task data is undefined', () => {
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [undefined as any],
},
lastNodeExecuted: 'node1',
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBeUndefined();
});
it('should return paired item when no previous task data exists', () => {
const expectedPairedItem: IPairedItemData = { item: 0 };
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [
{
data: { main: [[]] },
executionTime: 0,
executionStatus: 'success',
startTime: 0,
} as unknown as ITaskData,
],
},
lastNodeExecuted: 'node1',
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: expectedPairedItem,
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBe(expectedPairedItem);
});
it('should backtrack through workflow data with simple paired item', () => {
const finalPairedItem: IPairedItemData = { item: 5 };
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [
{
source: [{ previousNode: 'node2' }],
data: { main: [[item]] },
executionTime: 100,
executionStatus: 'success',
executionIndex: 0,
startTime: 1000,
},
],
node2: [
{
data: { main: [[{ json: { value: 2 }, pairedItem: finalPairedItem }]] },
executionTime: 200,
executionStatus: 'success',
executionIndex: 0,
startTime: 2000,
} as unknown as ITaskData,
],
},
lastNodeExecuted: 'node1',
},
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBe(finalPairedItem);
});
it('should backtrack through workflow data with object paired item', () => {
const finalPairedItem: IPairedItemData = { item: 3, input: 1 };
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0, input: 1 },
};
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [
{
source: [{ previousNode: 'node2' }],
data: { main: [[item]] },
executionTime: 100,
executionStatus: 'success',
startTime: 1000,
} as unknown as ITaskData,
],
node2: [
{
data: { main: [[], [{ json: { value: 2 }, pairedItem: finalPairedItem }]] },
executionTime: 200,
executionStatus: 'success',
startTime: 2000,
} as unknown as ITaskData,
],
},
lastNodeExecuted: 'node1',
},
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBe(finalPairedItem);
});
it('should use itemIndex parameter when paired item is numeric', () => {
const finalPairedItem: IPairedItemData = { item: 7 };
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: 2, // Numeric paired item
};
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [
{
source: [{ previousNode: 'node2' }],
data: { main: [[item]] },
executionTime: 100,
executionStatus: 'success',
startTime: 1000,
} as unknown as ITaskData,
],
node2: [
{
data: {
main: [
[
{ json: {} },
{ json: {} },
{ json: { value: 2 }, pairedItem: finalPairedItem },
],
],
},
executionTime: 200,
executionStatus: 'success',
startTime: 2000,
} as unknown as ITaskData,
],
},
lastNodeExecuted: 'node1',
},
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 5);
expect(result).toBe(finalPairedItem);
});
it('should handle multiple levels of backtracking', () => {
const finalPairedItem: IPairedItemData = { item: 10 };
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [
{
source: [{ previousNode: 'node2' }],
data: { main: [[{ json: { value: 1 }, pairedItem: { item: 0 } }]] },
executionTime: 100,
executionStatus: 'success',
startTime: 1000,
} as unknown as ITaskData,
],
node2: [
{
source: [{ previousNode: 'node3' }],
data: { main: [[{ json: { value: 2 }, pairedItem: { item: 1 } }]] },
executionTime: 200,
executionStatus: 'success',
startTime: 2000,
} as unknown as ITaskData,
],
node3: [
{
data: { main: [[null, { json: { value: 3 }, pairedItem: finalPairedItem }]] },
executionTime: 300,
executionStatus: 'success',
startTime: 3000,
} as unknown as ITaskData,
],
},
lastNodeExecuted: 'node1',
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBe(finalPairedItem);
});
it('should use last run data when multiple runs exist', () => {
const finalPairedItem: IPairedItemData = { item: 15 };
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [
{
source: [{ previousNode: 'node2' }],
data: { main: [[{ json: { value: 1 }, pairedItem: { item: 0 } }]] },
executionTime: 100,
executionStatus: 'success',
startTime: 1000,
} as unknown as ITaskData,
{
source: [{ previousNode: 'node2' }],
data: { main: [[{ json: { value: 2 }, pairedItem: { item: 0 } }]] },
executionTime: 150,
executionStatus: 'success',
startTime: 1500,
} as unknown as ITaskData,
],
node2: [
{
data: { main: [[{ json: { value: 3 }, pairedItem: finalPairedItem }]] },
executionTime: 200,
executionStatus: 'success',
startTime: 2000,
} as unknown as ITaskData,
],
},
lastNodeExecuted: 'node1',
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBe(finalPairedItem);
});
it('should handle missing nodeInformationArray gracefully', () => {
const workflowRunData: IRunExecutionData = {
resultData: {
runData: {
node1: [
{
source: [{ previousNode: 'node2' }],
data: {},
executionTime: 100,
executionStatus: 'success',
startTime: 1000,
} as unknown as ITaskData,
],
node2: [
{
data: { main: [[]] },
executionTime: 200,
executionStatus: 'success',
startTime: 2000,
} as unknown as ITaskData,
],
},
lastNodeExecuted: 'node1',
},
};
const item: INodeExecutionData = {
json: { test: 'value' },
pairedItem: { item: 0 },
};
const result = findPairedItemTroughWorkflowData(workflowRunData, item, 0);
expect(result).toBeUndefined();
});
});
});