mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
refactor(core): Add test for workflow issue check and extract logic (#18845)
This commit is contained in:
@@ -46,38 +46,6 @@ describe('processRunExecutionData', () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('throws if workflow contains nodes with missing required properties', () => {
|
||||
// ARRANGE
|
||||
const node = createNodeData({ name: 'node', type: types.testNodeWithRequiredProperty });
|
||||
const workflow = new DirectedGraph()
|
||||
.addNodes(node)
|
||||
.toWorkflow({ name: '', active: false, nodeTypes, settings: { executionOrder: 'v1' } });
|
||||
|
||||
const taskDataConnection = { main: [[{ json: { foo: 1 } }]] };
|
||||
const executionData: IRunExecutionData = {
|
||||
startData: { startNodes: [{ name: node.name, sourceData: null }] },
|
||||
resultData: { runData: {} },
|
||||
executionData: {
|
||||
contextData: {},
|
||||
nodeExecutionStack: [{ data: taskDataConnection, node, source: null }],
|
||||
metadata: {},
|
||||
waitingExecution: {},
|
||||
waitingExecutionSource: {},
|
||||
},
|
||||
};
|
||||
|
||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode, executionData);
|
||||
|
||||
// ACT & ASSERT
|
||||
// The function returns a Promise, but throws synchronously, so we can't await it.
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
expect(() => workflowExecute.processRunExecutionData(workflow)).toThrowError(
|
||||
new ApplicationError(
|
||||
'The workflow has issues and cannot be executed for that reason. Please fix them first.',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('returns input data verbatim', async () => {
|
||||
// ARRANGE
|
||||
const node = createNodeData({ name: 'node', type: types.passThrough });
|
||||
@@ -183,4 +151,71 @@ describe('processRunExecutionData', () => {
|
||||
expect(result.data.resultData.runData.waitingNode[0].executionStatus).toEqual('success');
|
||||
});
|
||||
});
|
||||
|
||||
describe('workflow issues', () => {
|
||||
test('throws if workflow contains nodes with missing required properties', () => {
|
||||
// ARRANGE
|
||||
const node = createNodeData({ name: 'node', type: types.testNodeWithRequiredProperty });
|
||||
const workflow = new DirectedGraph()
|
||||
.addNodes(node)
|
||||
.toWorkflow({ name: '', active: false, nodeTypes, settings: { executionOrder: 'v1' } });
|
||||
|
||||
const taskDataConnection = { main: [[{ json: { foo: 1 } }]] };
|
||||
const executionData: IRunExecutionData = {
|
||||
startData: { startNodes: [{ name: node.name, sourceData: null }] },
|
||||
resultData: { runData: {} },
|
||||
executionData: {
|
||||
contextData: {},
|
||||
nodeExecutionStack: [{ data: taskDataConnection, node, source: null }],
|
||||
metadata: {},
|
||||
waitingExecution: {},
|
||||
waitingExecutionSource: {},
|
||||
},
|
||||
};
|
||||
|
||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode, executionData);
|
||||
|
||||
// ACT & ASSERT
|
||||
// The function returns a Promise, but throws synchronously, so we can't await it.
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
expect(() => workflowExecute.processRunExecutionData(workflow)).toThrowError(
|
||||
new ApplicationError(
|
||||
'The workflow has issues and cannot be executed for that reason. Please fix them first.',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('does not complain about nodes with issue past the destination node', async () => {
|
||||
// ARRANGE
|
||||
const node1 = createNodeData({ name: 'node1', type: types.passThrough });
|
||||
const node2 = createNodeData({ name: 'node2', type: types.testNodeWithRequiredProperty });
|
||||
const workflow = new DirectedGraph()
|
||||
.addNodes(node1, node2)
|
||||
.addConnection({ from: node1, to: node2 })
|
||||
.toWorkflow({ name: '', active: false, nodeTypes, settings: { executionOrder: 'v1' } });
|
||||
|
||||
const taskDataConnection = { main: [[{ json: { foo: 1 } }]] };
|
||||
const executionData: IRunExecutionData = {
|
||||
startData: {
|
||||
startNodes: [{ name: node1.name, sourceData: null }],
|
||||
destinationNode: node1.name,
|
||||
},
|
||||
resultData: { runData: {} },
|
||||
executionData: {
|
||||
contextData: {},
|
||||
nodeExecutionStack: [{ data: taskDataConnection, node: node1, source: null }],
|
||||
metadata: {},
|
||||
waitingExecution: {},
|
||||
waitingExecutionSource: {},
|
||||
},
|
||||
};
|
||||
|
||||
const workflowExecute = new WorkflowExecute(additionalData, executionMode, executionData);
|
||||
|
||||
// ACT & ASSERT
|
||||
// The function returns a Promise, but throws synchronously, so we can't await it.
|
||||
// eslint-disable-next-line @typescript-eslint/promise-function-async
|
||||
expect(() => workflowExecute.processRunExecutionData(workflow)).not.toThrowError();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1513,6 +1513,27 @@ export class WorkflowExecute {
|
||||
}
|
||||
}
|
||||
|
||||
private checkForWorkflowIssues(workflow: Workflow): void {
|
||||
this.assertExecutionDataExists(this.runExecutionData.executionData, workflow);
|
||||
// Node execution stack will be empty for an execution containing only Chat
|
||||
// Trigger.
|
||||
const startNode = this.runExecutionData.executionData.nodeExecutionStack.at(0)?.node.name;
|
||||
|
||||
let destinationNode: string | undefined;
|
||||
if (this.runExecutionData.startData && this.runExecutionData.startData.destinationNode) {
|
||||
destinationNode = this.runExecutionData.startData.destinationNode;
|
||||
}
|
||||
const pinDataNodeNames = Object.keys(this.runExecutionData.resultData.pinData ?? {});
|
||||
const workflowIssues = this.checkReadyForExecution(workflow, {
|
||||
startNode,
|
||||
destinationNode,
|
||||
pinDataNodeNames,
|
||||
});
|
||||
if (workflowIssues !== null) {
|
||||
throw new WorkflowHasIssuesError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given execution data.
|
||||
*
|
||||
@@ -1528,38 +1549,9 @@ export class WorkflowExecute {
|
||||
|
||||
this.status = 'running';
|
||||
|
||||
const { hooks, executionId } = this.additionalData;
|
||||
const { hooks } = this.additionalData;
|
||||
assert.ok(hooks, 'Failed to run workflow due to missing execution lifecycle hooks');
|
||||
|
||||
if (!this.runExecutionData.executionData) {
|
||||
throw new ApplicationError('Failed to run workflow due to missing execution data', {
|
||||
extra: {
|
||||
workflowId: workflow.id,
|
||||
executionId,
|
||||
mode: this.mode,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** Node execution stack will be empty for an execution containing only Chat Trigger. */
|
||||
const startNode = this.runExecutionData.executionData.nodeExecutionStack.at(0)?.node.name;
|
||||
|
||||
let destinationNode: string | undefined;
|
||||
if (this.runExecutionData.startData && this.runExecutionData.startData.destinationNode) {
|
||||
destinationNode = this.runExecutionData.startData.destinationNode;
|
||||
}
|
||||
|
||||
const pinDataNodeNames = Object.keys(this.runExecutionData.resultData.pinData ?? {});
|
||||
|
||||
const workflowIssues = this.checkReadyForExecution(workflow, {
|
||||
startNode,
|
||||
destinationNode,
|
||||
pinDataNodeNames,
|
||||
});
|
||||
if (workflowIssues !== null) {
|
||||
throw new WorkflowHasIssuesError();
|
||||
}
|
||||
|
||||
// Variables which hold temporary data for each node-execution
|
||||
let executionData: IExecuteData;
|
||||
let executionError: ExecutionBaseError | undefined;
|
||||
@@ -1570,6 +1562,7 @@ export class WorkflowExecute {
|
||||
this.runExecutionData.startData = {};
|
||||
}
|
||||
|
||||
this.checkForWorkflowIssues(workflow);
|
||||
this.handleWaitingState(workflow);
|
||||
|
||||
let currentExecutionTry = '';
|
||||
|
||||
Reference in New Issue
Block a user