mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-13 16:14:07 -08:00
test: Add unit testing to nodes (no-changelog) (#4890)
* 🧪 Add base for building unit testing within nodes * Improve helper functions * 🧪 If node test * 🧪 Airtable node test * 🧪 If node test improvements * 🧪 Airtable node test improvements * ♻️ cleanup node unit tests * ♻️ refactor getting node result data to use helper method * ⚡ removed unused variables * ♻️ Helper to read json files --------- Co-authored-by: Marcus <marcus@n8n.io> Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
parent
dbcbe595cc
commit
5b9c650e55
|
@ -0,0 +1,64 @@
|
||||||
|
import { INodeType } from 'n8n-workflow';
|
||||||
|
import { executeWorkflow } from '../ExecuteWorkflow';
|
||||||
|
import * as Helpers from '../Helpers';
|
||||||
|
import { WorkflowTestData } from '../types';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { ManualTrigger } from '../../../nodes/ManualTrigger/ManualTrigger.node';
|
||||||
|
import { Airtable } from '../../../nodes/Airtable/Airtable.node';
|
||||||
|
|
||||||
|
const records = [
|
||||||
|
{
|
||||||
|
id: 'rec2BWBoyS5QsS7pT',
|
||||||
|
createdTime: '2022-08-25T08:22:34.000Z',
|
||||||
|
fields: {
|
||||||
|
name: 'Tim',
|
||||||
|
email: 'tim@email.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
describe('Execute Airtable Node', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
nock('https://api.airtable.com/v0')
|
||||||
|
.get('/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh?pageSize=100')
|
||||||
|
.reply(200, { records });
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
nock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const tests: Array<WorkflowTestData> = [
|
||||||
|
{
|
||||||
|
description: 'List Airtable Records',
|
||||||
|
input: {
|
||||||
|
workflowData: Helpers.readJsonFileSync('test/nodes/Airtable/workflow.json'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
nodeData: {
|
||||||
|
Airtable: [[...records]],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodes: INodeType[] = [new ManualTrigger(), new Airtable()];
|
||||||
|
const nodeTypes = Helpers.setup(nodes);
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => {
|
||||||
|
// execute workflow
|
||||||
|
const { result } = await executeWorkflow(testData, nodeTypes);
|
||||||
|
|
||||||
|
// check if result node data matches expected test data
|
||||||
|
const resultNodeData = Helpers.getResultNodeData(result, testData);
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) =>
|
||||||
|
expect(resultData).toEqual(testData.output.nodeData[nodeName]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
63
packages/nodes-base/test/nodes/Airtable/workflow.json
Normal file
63
packages/nodes-base/test/nodes/Airtable/workflow.json
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "f857c37f-36c1-4c9c-9b5f-f6ef49db67e3",
|
||||||
|
"name": "On clicking 'execute'",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
820,
|
||||||
|
380
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"operation": "list",
|
||||||
|
"application": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "https://airtable.com/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh/viwInsMdsxffad0aU",
|
||||||
|
"mode": "url",
|
||||||
|
"__regex": "https://airtable.com/([a-zA-Z0-9]{2,})"
|
||||||
|
},
|
||||||
|
"table": {
|
||||||
|
"__rl": true,
|
||||||
|
"value": "https://airtable.com/appIaXXdDqS5ORr4V/tbljyBEdYzCPF0NDh/viwInsMdsxffad0aU",
|
||||||
|
"mode": "url",
|
||||||
|
"__regex": "https://airtable.com/[a-zA-Z0-9]{2,}/([a-zA-Z0-9]{2,})"
|
||||||
|
},
|
||||||
|
"additionalOptions": {}
|
||||||
|
},
|
||||||
|
"id": "5654d3b3-fe83-4988-889b-94f107d41807",
|
||||||
|
"name": "Airtable",
|
||||||
|
"type": "n8n-nodes-base.airtable",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1020,
|
||||||
|
380
|
||||||
|
],
|
||||||
|
"credentials": {
|
||||||
|
"airtableApi": {
|
||||||
|
"id": "20",
|
||||||
|
"name": "Airtable account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"On clicking 'execute'": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Airtable",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
24
packages/nodes-base/test/nodes/ExecuteWorkflow.ts
Normal file
24
packages/nodes-base/test/nodes/ExecuteWorkflow.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { WorkflowExecute } from 'n8n-core';
|
||||||
|
import { createDeferredPromise, INodeTypes, IRun, Workflow } from 'n8n-workflow';
|
||||||
|
import * as Helpers from './Helpers';
|
||||||
|
|
||||||
|
export async function executeWorkflow(testData, nodeTypes: INodeTypes) {
|
||||||
|
const executionMode = 'manual';
|
||||||
|
const workflowInstance = new Workflow({
|
||||||
|
id: 'test',
|
||||||
|
nodes: testData.input.workflowData.nodes,
|
||||||
|
connections: testData.input.workflowData.connections,
|
||||||
|
active: false,
|
||||||
|
nodeTypes,
|
||||||
|
});
|
||||||
|
|
||||||
|
const waitPromise = await createDeferredPromise<IRun>();
|
||||||
|
const nodeExecutionOrder: string[] = [];
|
||||||
|
const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder);
|
||||||
|
|
||||||
|
const workflowExecute = new WorkflowExecute(additionalData, executionMode);
|
||||||
|
|
||||||
|
const executionData = await workflowExecute.run(workflowInstance);
|
||||||
|
const result = await waitPromise.promise();
|
||||||
|
return { executionData, result, nodeExecutionOrder };
|
||||||
|
}
|
183
packages/nodes-base/test/nodes/Helpers.ts
Normal file
183
packages/nodes-base/test/nodes/Helpers.ts
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { Credentials } from 'n8n-core';
|
||||||
|
import {
|
||||||
|
ICredentialDataDecryptedObject,
|
||||||
|
ICredentialsHelper,
|
||||||
|
IDeferredPromise,
|
||||||
|
IExecuteWorkflowInfo,
|
||||||
|
IHttpRequestHelper,
|
||||||
|
IHttpRequestOptions,
|
||||||
|
ILogger,
|
||||||
|
INode,
|
||||||
|
INodeCredentialsDetails,
|
||||||
|
INodeType,
|
||||||
|
INodeTypeData,
|
||||||
|
INodeTypes,
|
||||||
|
IRun,
|
||||||
|
ITaskData,
|
||||||
|
IVersionedNodeType,
|
||||||
|
IWorkflowBase,
|
||||||
|
IWorkflowExecuteAdditionalData,
|
||||||
|
LoggerProxy,
|
||||||
|
NodeHelpers,
|
||||||
|
WorkflowHooks,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import { WorkflowTestData } from './types';
|
||||||
|
|
||||||
|
export class CredentialsHelper extends ICredentialsHelper {
|
||||||
|
async authenticate(
|
||||||
|
credentials: ICredentialDataDecryptedObject,
|
||||||
|
typeName: string,
|
||||||
|
requestParams: IHttpRequestOptions,
|
||||||
|
): Promise<IHttpRequestOptions> {
|
||||||
|
return requestParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
async preAuthentication(
|
||||||
|
helpers: IHttpRequestHelper,
|
||||||
|
credentials: ICredentialDataDecryptedObject,
|
||||||
|
typeName: string,
|
||||||
|
node: INode,
|
||||||
|
credentialsExpired: boolean,
|
||||||
|
): Promise<ICredentialDataDecryptedObject | undefined> {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
getParentTypes(name: string): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDecrypted(
|
||||||
|
nodeCredentials: INodeCredentialsDetails,
|
||||||
|
type: string,
|
||||||
|
): Promise<ICredentialDataDecryptedObject> {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCredentials(
|
||||||
|
nodeCredentials: INodeCredentialsDetails,
|
||||||
|
type: string,
|
||||||
|
): Promise<Credentials> {
|
||||||
|
return new Credentials({ id: null, name: '' }, '', [], '');
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCredentials(
|
||||||
|
nodeCredentials: INodeCredentialsDetails,
|
||||||
|
type: string,
|
||||||
|
data: ICredentialDataDecryptedObject,
|
||||||
|
): Promise<void> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WorkflowExecuteAdditionalData(
|
||||||
|
waitPromise: IDeferredPromise<IRun>,
|
||||||
|
nodeExecutionOrder: string[],
|
||||||
|
): IWorkflowExecuteAdditionalData {
|
||||||
|
const hookFunctions = {
|
||||||
|
nodeExecuteAfter: [
|
||||||
|
async (nodeName: string, data: ITaskData): Promise<void> => {
|
||||||
|
nodeExecutionOrder.push(nodeName);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
workflowExecuteAfter: [
|
||||||
|
async (fullRunData: IRun): Promise<void> => {
|
||||||
|
waitPromise.resolve(fullRunData);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const workflowData: IWorkflowBase = {
|
||||||
|
name: '',
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
active: true,
|
||||||
|
nodes: [],
|
||||||
|
connections: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
credentialsHelper: new CredentialsHelper(''),
|
||||||
|
hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData),
|
||||||
|
executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise<any> => {},
|
||||||
|
sendMessageToUI: (message: string) => {},
|
||||||
|
restApiUrl: '',
|
||||||
|
encryptionKey: 'test',
|
||||||
|
timezone: 'America/New_York',
|
||||||
|
webhookBaseUrl: 'webhook',
|
||||||
|
webhookWaitingBaseUrl: 'webhook-waiting',
|
||||||
|
webhookTestBaseUrl: 'webhook-test',
|
||||||
|
userId: '123',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeTypesClass implements INodeTypes {
|
||||||
|
nodeTypes: INodeTypeData = {};
|
||||||
|
getByName(nodeType: string): INodeType | IVersionedNodeType {
|
||||||
|
return this.nodeTypes[nodeType].type;
|
||||||
|
}
|
||||||
|
|
||||||
|
addNode(nodeTypeName: string, nodeType: INodeType | IVersionedNodeType) {
|
||||||
|
const loadedNode = {
|
||||||
|
[nodeTypeName]: {
|
||||||
|
sourcePath: '',
|
||||||
|
type: nodeType,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.nodeTypes = {
|
||||||
|
...this.nodeTypes,
|
||||||
|
...loadedNode,
|
||||||
|
};
|
||||||
|
//Object.assign(this.nodeTypes, loadedNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
getByNameAndVersion(nodeType: string, version?: number): INodeType {
|
||||||
|
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let nodeTypesInstance: NodeTypesClass | undefined;
|
||||||
|
|
||||||
|
export function NodeTypes(): NodeTypesClass {
|
||||||
|
if (nodeTypesInstance === undefined) {
|
||||||
|
nodeTypesInstance = new NodeTypesClass();
|
||||||
|
}
|
||||||
|
return nodeTypesInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setup(nodes: INodeType[]) {
|
||||||
|
const nodeTypes = NodeTypes();
|
||||||
|
for (const node of nodes) {
|
||||||
|
nodeTypes.addNode('n8n-nodes-base.' + node.description.name, node);
|
||||||
|
}
|
||||||
|
const fakeLogger = {
|
||||||
|
log: () => {},
|
||||||
|
debug: () => {},
|
||||||
|
verbose: () => {},
|
||||||
|
info: () => {},
|
||||||
|
warn: () => {},
|
||||||
|
error: () => {},
|
||||||
|
} as ILogger;
|
||||||
|
LoggerProxy.init(fakeLogger);
|
||||||
|
return nodeTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResultNodeData(result: IRun, testData: WorkflowTestData) {
|
||||||
|
return Object.keys(testData.output.nodeData).map((nodeName) => {
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
nodeName,
|
||||||
|
resultData,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readJsonFileSync(path: string) {
|
||||||
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
||||||
|
}
|
58
packages/nodes-base/test/nodes/If/If.node.test.ts
Normal file
58
packages/nodes-base/test/nodes/If/If.node.test.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import { INodeType } from 'n8n-workflow';
|
||||||
|
import * as Helpers from '../Helpers';
|
||||||
|
import { WorkflowTestData } from '../types';
|
||||||
|
|
||||||
|
import { ManualTrigger } from '../../../nodes/ManualTrigger/ManualTrigger.node';
|
||||||
|
import { Set } from '../../../nodes/Set/Set.node';
|
||||||
|
import { If } from '../../../nodes/If/If.node';
|
||||||
|
import { NoOp } from '../../../nodes/NoOp/NoOp.node';
|
||||||
|
import { Code } from '../../../nodes/Code/Code.node';
|
||||||
|
|
||||||
|
import { executeWorkflow } from '../ExecuteWorkflow';
|
||||||
|
|
||||||
|
describe('Execute If Node', () => {
|
||||||
|
const tests: Array<WorkflowTestData> = [
|
||||||
|
{
|
||||||
|
description: 'should execute IF node true/false boolean',
|
||||||
|
input: {
|
||||||
|
workflowData: Helpers.readJsonFileSync('test/nodes/If/workflow.json'),
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
nodeData: {
|
||||||
|
'On True': [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'On False': [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodes: INodeType[] = [new ManualTrigger(), new Code(), new Set(), new If(), new NoOp()];
|
||||||
|
const nodeTypes = Helpers.setup(nodes);
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => {
|
||||||
|
// execute workflow
|
||||||
|
const { result } = await executeWorkflow(testData, nodeTypes);
|
||||||
|
|
||||||
|
// check if result node data matches expected test data
|
||||||
|
const resultNodeData = Helpers.getResultNodeData(result, testData);
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) =>
|
||||||
|
expect(resultData).toEqual(testData.output.nodeData[nodeName]),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
115
packages/nodes-base/test/nodes/If/workflow.json
Normal file
115
packages/nodes-base/test/nodes/If/workflow.json
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"instanceId": "104a4d08d8897b8bdeb38aaca515021075e0bd8544c983c2bb8c86e6a8e6081c"
|
||||||
|
},
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "47003824-c11f-4ae3-80a5-0e1a6d840b21",
|
||||||
|
"name": "On clicking 'execute'",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
720,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"conditions": {
|
||||||
|
"boolean": [
|
||||||
|
{
|
||||||
|
"value1": "={{ $json[\"value\"] }}",
|
||||||
|
"value2": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"id": "5420fe7d-a216-44e0-b91f-188ba5b6a340",
|
||||||
|
"name": "IF",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1160,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "52d58f32-7faf-4874-afff-e6842bd02430",
|
||||||
|
"name": "On False",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1400,
|
||||||
|
580
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "9be683ac-cd3f-4ba1-8fa4-052102c3d891",
|
||||||
|
"name": "On True",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
1400,
|
||||||
|
340
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"jsCode": "return [\n { value: true },\n { value: false }\n];"
|
||||||
|
},
|
||||||
|
"id": "5b3207e7-37e3-43c8-a4da-1ffebb0de134",
|
||||||
|
"name": "Code",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [
|
||||||
|
940,
|
||||||
|
460
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"connections": {
|
||||||
|
"On clicking 'execute'": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Code",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"IF": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "On True",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "On False",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Code": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "IF",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
200
packages/nodes-base/test/nodes/Set/SetNode.test.ts
Normal file
200
packages/nodes-base/test/nodes/Set/SetNode.test.ts
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
import { INodeType } from 'n8n-workflow';
|
||||||
|
import * as Helpers from '../Helpers';
|
||||||
|
import { Start } from '../../../nodes/Start/Start.node';
|
||||||
|
import { Set } from '../../../nodes/Set/Set.node';
|
||||||
|
import { executeWorkflow } from '../ExecuteWorkflow';
|
||||||
|
import { WorkflowTestData } from '../types';
|
||||||
|
|
||||||
|
describe('Execute Set Node', () => {
|
||||||
|
const tests: Array<WorkflowTestData> = [
|
||||||
|
{
|
||||||
|
description: 'should set value',
|
||||||
|
input: {
|
||||||
|
workflowData: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'uuid-1',
|
||||||
|
parameters: {},
|
||||||
|
name: 'Start',
|
||||||
|
type: 'n8n-nodes-base.start',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [100, 300],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uuid-2',
|
||||||
|
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 set multiple values',
|
||||||
|
input: {
|
||||||
|
workflowData: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'uuid-1',
|
||||||
|
parameters: {},
|
||||||
|
name: 'Start',
|
||||||
|
type: 'n8n-nodes-base.start',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [100, 300],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uuid-2',
|
||||||
|
parameters: {
|
||||||
|
values: {
|
||||||
|
number: [
|
||||||
|
{
|
||||||
|
name: 'value1',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
boolean: [
|
||||||
|
{
|
||||||
|
name: 'value2',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'Set',
|
||||||
|
type: 'n8n-nodes-base.set',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [280, 300],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'uuid-3',
|
||||||
|
parameters: {
|
||||||
|
values: {
|
||||||
|
number: [
|
||||||
|
{
|
||||||
|
name: 'value1',
|
||||||
|
value: 2,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
boolean: [
|
||||||
|
{
|
||||||
|
name: 'value2',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'Set1',
|
||||||
|
type: 'n8n-nodes-base.set',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [280, 300],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
connections: {
|
||||||
|
Start: {
|
||||||
|
main: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
node: 'Set',
|
||||||
|
type: 'main',
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Set: {
|
||||||
|
main: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
node: 'Set1',
|
||||||
|
type: 'main',
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
nodeExecutionOrder: ['Start', 'Set'],
|
||||||
|
nodeData: {
|
||||||
|
Set: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
value1: 1,
|
||||||
|
value2: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
Set1: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
value1: 2,
|
||||||
|
value2: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodes: INodeType[] = [new Start(), new Set()];
|
||||||
|
const nodeTypes = Helpers.setup(nodes);
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => {
|
||||||
|
// execute workflow
|
||||||
|
const { result } = await executeWorkflow(testData, nodeTypes);
|
||||||
|
|
||||||
|
// check if result node data matches expected test data
|
||||||
|
const resultNodeData = Helpers.getResultNodeData(result, testData);
|
||||||
|
resultNodeData.forEach(({ nodeName, resultData }) =>
|
||||||
|
expect(resultData).toEqual(testData.output.nodeData[nodeName]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if other data has correct value
|
||||||
|
expect(result.finished).toEqual(true);
|
||||||
|
expect(result.data.executionData!.contextData).toEqual({});
|
||||||
|
expect(result.data.executionData!.nodeExecutionStack).toEqual([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
48
packages/nodes-base/test/nodes/Start/StartNode.test.ts
Normal file
48
packages/nodes-base/test/nodes/Start/StartNode.test.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { INodeType } from 'n8n-workflow';
|
||||||
|
import * as Helpers from '../Helpers';
|
||||||
|
import { Start } from '../../../nodes/Start/Start.node';
|
||||||
|
import { WorkflowTestData } from '../types';
|
||||||
|
import { executeWorkflow } from '../ExecuteWorkflow';
|
||||||
|
|
||||||
|
describe('Execute Start Node', () => {
|
||||||
|
const tests: Array<WorkflowTestData> = [
|
||||||
|
{
|
||||||
|
description: 'should run start node',
|
||||||
|
input: {
|
||||||
|
workflowData: {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: 'uuid-1',
|
||||||
|
parameters: {},
|
||||||
|
name: 'Start',
|
||||||
|
type: 'n8n-nodes-base.start',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [100, 300],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
connections: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
nodeExecutionOrder: ['Start'],
|
||||||
|
nodeData: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodes: INodeType[] = [new Start()];
|
||||||
|
const nodeTypes = Helpers.setup(nodes);
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => {
|
||||||
|
// execute workflow
|
||||||
|
const { result, nodeExecutionOrder } = await executeWorkflow(testData, nodeTypes);
|
||||||
|
// 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([]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
17
packages/nodes-base/test/nodes/types.ts
Normal file
17
packages/nodes-base/test/nodes/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import { INode, IConnections } from 'n8n-workflow';
|
||||||
|
|
||||||
|
export interface WorkflowTestData {
|
||||||
|
description: string;
|
||||||
|
input: {
|
||||||
|
workflowData: {
|
||||||
|
nodes: INode[];
|
||||||
|
connections: IConnections;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
output: {
|
||||||
|
nodeExecutionOrder?: string[];
|
||||||
|
nodeData: {
|
||||||
|
[key: string]: any[][];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in a new issue