mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
✅ Add first basic tests for workflow execution
This commit is contained in:
625
packages/core/test/WorkflowExecute.test.ts
Normal file
625
packages/core/test/WorkflowExecute.test.ts
Normal file
@@ -0,0 +1,625 @@
|
||||
|
||||
import {
|
||||
IConnections,
|
||||
INode,
|
||||
IRun,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
createDeferredPromise,
|
||||
WorkflowExecute,
|
||||
} from '../src';
|
||||
|
||||
import * as Helpers from './Helpers';
|
||||
|
||||
|
||||
describe('WorkflowExecute', () => {
|
||||
|
||||
describe('run', () => {
|
||||
|
||||
const tests: Array<{
|
||||
description: string;
|
||||
input: {
|
||||
workflowData: {
|
||||
nodes: INode[],
|
||||
connections: IConnections,
|
||||
}
|
||||
},
|
||||
output: {
|
||||
nodeExecutionOrder: string[];
|
||||
nodeData: {
|
||||
[key: string]: any[][]; // tslint:disable-line:no-any
|
||||
};
|
||||
},
|
||||
}> = [
|
||||
{
|
||||
description: 'should run basic two node workflow',
|
||||
input: {
|
||||
// Leave the workflowData in regular JSON to be able to easily
|
||||
// copy it from/in the UI
|
||||
workflowData: {
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"name": "Start",
|
||||
"type": "n8n-nodes-base.start",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
100,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"number": [
|
||||
{
|
||||
"name": "value1",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Set",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
280,
|
||||
300
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Start": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
output: {
|
||||
nodeExecutionOrder: [
|
||||
'Start',
|
||||
'Set',
|
||||
],
|
||||
nodeData: {
|
||||
Set: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'should run node twice when it has two input connections',
|
||||
input: {
|
||||
// Leave the workflowData in regular JSON to be able to easily
|
||||
// copy it from/in the UI
|
||||
workflowData: {
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"name": "Start",
|
||||
"type": "n8n-nodes-base.start",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
100,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"number": [
|
||||
{
|
||||
"name": "value1",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Set1",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
300,
|
||||
250
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"number": [
|
||||
{
|
||||
"name": "value2",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Set2",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
500,
|
||||
400
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Start": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Set2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
output: {
|
||||
nodeExecutionOrder: [
|
||||
'Start',
|
||||
'Set1',
|
||||
'Set2',
|
||||
'Set2',
|
||||
],
|
||||
nodeData: {
|
||||
Set1: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
},
|
||||
]
|
||||
],
|
||||
Set2: [
|
||||
[
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
value2: 2,
|
||||
},
|
||||
],
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: 'should run complicated multi node workflow',
|
||||
input: {
|
||||
// Leave the workflowData in regular JSON to be able to easily
|
||||
// copy it from/in the UI
|
||||
workflowData: {
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "passThrough"
|
||||
},
|
||||
"name": "Merge4",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1150,
|
||||
500
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"number": [
|
||||
{
|
||||
"name": "value2",
|
||||
"value": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Set2",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
290,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"number": [
|
||||
{
|
||||
"name": "value4",
|
||||
"value": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Set4",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
850,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"number": [
|
||||
{
|
||||
"name": "value3",
|
||||
"value": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Set3",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
650,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "passThrough"
|
||||
},
|
||||
"name": "Merge4",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1150,
|
||||
500
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"name": "Merge3",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
1000,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"mode": "passThrough",
|
||||
"output": "input2"
|
||||
},
|
||||
"name": "Merge2",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
700,
|
||||
400
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"name": "Merge1",
|
||||
"type": "n8n-nodes-base.merge",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
500,
|
||||
300
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"values": {
|
||||
"number": [
|
||||
{
|
||||
"name": "value1",
|
||||
"value": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"name": "Set1",
|
||||
"type": "n8n-nodes-base.set",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
300,
|
||||
200
|
||||
]
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"name": "Start",
|
||||
"type": "n8n-nodes-base.start",
|
||||
"typeVersion": 1,
|
||||
"position": [
|
||||
100,
|
||||
300
|
||||
]
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"Set2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge1",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
},
|
||||
{
|
||||
"node": "Merge2",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set4": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set3": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set4",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge3": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge4",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge3",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Merge1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Set1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Merge1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Set3",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Start": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Set1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Set2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Merge4",
|
||||
"type": "main",
|
||||
"index": 1
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
output: {
|
||||
nodeExecutionOrder: [
|
||||
'Start',
|
||||
'Set1',
|
||||
'Set2',
|
||||
'Set3',
|
||||
'Merge1',
|
||||
'Set4',
|
||||
'Merge2',
|
||||
'Merge3',
|
||||
'Merge4',
|
||||
],
|
||||
nodeData: {
|
||||
Set1: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
},
|
||||
],
|
||||
],
|
||||
Set2: [
|
||||
[
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
],
|
||||
],
|
||||
Set3: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
value3: 3,
|
||||
},
|
||||
],
|
||||
],
|
||||
Set4: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
value3: 3,
|
||||
value4: 4,
|
||||
},
|
||||
],
|
||||
],
|
||||
Merge1: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
},
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
Merge2: [
|
||||
[
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
Merge3: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
value3: 3,
|
||||
value4: 4,
|
||||
},
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
Merge4: [
|
||||
[
|
||||
{
|
||||
value1: 1,
|
||||
value3: 3,
|
||||
value4: 4,
|
||||
},
|
||||
{
|
||||
value2: 2,
|
||||
},
|
||||
]
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
const executionMode = 'manual';
|
||||
const nodeTypes = Helpers.NodeTypes();
|
||||
|
||||
for (const testData of tests) {
|
||||
test(testData.description, async () => {
|
||||
|
||||
const workflowInstance = new Workflow('test', testData.input.workflowData.nodes, testData.input.workflowData.connections, false, nodeTypes);
|
||||
|
||||
const waitPromise = await createDeferredPromise<IRun>();
|
||||
const nodeExecutionOrder: string[] = [];
|
||||
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
||||
|
||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
||||
|
||||
const executionId = await workflowExecute.run(workflowInstance, undefined);
|
||||
expect(executionId).toBeDefined();
|
||||
|
||||
const result = await waitPromise.promise();
|
||||
|
||||
// Check if the output data of the nodes is correct
|
||||
for (const nodeName of Object.keys(testData.output.nodeData)) {
|
||||
if (result.data.resultData.runData[nodeName] === undefined) {
|
||||
throw new Error(`Data for node "${nodeName}" is missing!`);
|
||||
}
|
||||
|
||||
const resultData = result.data.resultData.runData[nodeName].map((nodeData) => {
|
||||
if (nodeData.data === undefined) {
|
||||
return null;
|
||||
}
|
||||
return nodeData.data.main[0]!.map((entry) => entry.json );
|
||||
});
|
||||
|
||||
// expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||
expect(resultData).toEqual(testData.output.nodeData[nodeName]);
|
||||
}
|
||||
|
||||
// Check if the nodes did execute in the correct order
|
||||
expect(nodeExecutionOrder).toEqual(testData.output.nodeExecutionOrder);
|
||||
|
||||
// Check if other data has correct value
|
||||
expect(result.finished).toEqual(true);
|
||||
expect(result.data.executionData!.contextData).toEqual({});
|
||||
expect(result.data.executionData!.nodeExecutionStack).toEqual([]);
|
||||
expect(result.data.executionData!.waitingExecution).toEqual({});
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
Reference in New Issue
Block a user