diff --git a/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts b/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts index a348cc7df9..04397af52b 100644 --- a/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts +++ b/packages/core/src/execution-engine/__tests__/workflow-execute-process-process-run-execution-data.test.ts @@ -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(); + }); + }); }); diff --git a/packages/core/src/execution-engine/workflow-execute.ts b/packages/core/src/execution-engine/workflow-execute.ts index da1773f652..1ba0a745e8 100644 --- a/packages/core/src/execution-engine/workflow-execute.ts +++ b/packages/core/src/execution-engine/workflow-execute.ts @@ -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 = '';