mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
fix(Execute Workflow Node): Fix 'Continue (using error output)' mode to output errors correctly (#19240)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import type { IExecuteFunctions, IWorkflowDataProxyData } from 'n8n-workflow';
|
import type { IExecuteFunctions, IWorkflowDataProxyData, INode } from 'n8n-workflow';
|
||||||
|
|
||||||
import { ExecuteWorkflow } from './ExecuteWorkflow.node';
|
import { ExecuteWorkflow } from './ExecuteWorkflow.node';
|
||||||
import { getWorkflowInfo } from './GenericFunctions';
|
import { getWorkflowInfo } from './GenericFunctions';
|
||||||
@@ -81,13 +81,15 @@ describe('ExecuteWorkflow', () => {
|
|||||||
expect(result).toEqual([[{ json: { key: 'value' }, index: 0, pairedItem: { item: 0 } }]]);
|
expect(result).toEqual([[{ json: { key: 'value' }, index: 0, pairedItem: { item: 0 } }]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle errors and continue on fail', async () => {
|
test('should handle errors and continue on fail, no items, < 1.3 version', async () => {
|
||||||
executeFunctions.getNodeParameter
|
executeFunctions.getNodeParameter
|
||||||
.mockReturnValueOnce('database') // source
|
.mockReturnValueOnce('database') // source
|
||||||
.mockReturnValueOnce('each') // mode
|
.mockReturnValueOnce('each') // mode
|
||||||
.mockReturnValueOnce(true) // waitForSubWorkflow
|
.mockReturnValueOnce(true) // waitForSubWorkflow
|
||||||
.mockReturnValueOnce([]); // workflowInputs.schema
|
.mockReturnValueOnce([]); // workflowInputs.schema
|
||||||
|
|
||||||
|
executeFunctions.getNode.mockReturnValue({ typeVersion: 1.2 } as INode);
|
||||||
|
|
||||||
(getWorkflowInfo as jest.Mock).mockRejectedValue(new Error('Test error'));
|
(getWorkflowInfo as jest.Mock).mockRejectedValue(new Error('Test error'));
|
||||||
(executeFunctions.continueOnFail as jest.Mock).mockReturnValue(true);
|
(executeFunctions.continueOnFail as jest.Mock).mockReturnValue(true);
|
||||||
|
|
||||||
@@ -96,6 +98,77 @@ describe('ExecuteWorkflow', () => {
|
|||||||
expect(result).toEqual([[{ json: { error: 'Test error' }, pairedItem: { item: 0 } }]]);
|
expect(result).toEqual([[{ json: { error: 'Test error' }, pairedItem: { item: 0 } }]]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should handle errors and continue on fail, multiple items, < 1.3 version', async () => {
|
||||||
|
executeFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('database') // source
|
||||||
|
.mockReturnValueOnce('each') // mode
|
||||||
|
.mockReturnValueOnce(true) // waitForSubWorkflow
|
||||||
|
.mockReturnValue([]); // workflowInputs.schema
|
||||||
|
|
||||||
|
executeFunctions.getNode.mockReturnValue({ typeVersion: 1.2 } as INode);
|
||||||
|
executeFunctions.getInputData.mockReturnValueOnce([
|
||||||
|
{ json: { key: '1' } },
|
||||||
|
{ json: { key: '2' } },
|
||||||
|
{ json: { key: '3' } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
(getWorkflowInfo as jest.Mock).mockRejectedValue(new Error('Test error'));
|
||||||
|
(executeFunctions.continueOnFail as jest.Mock).mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = await executeWorkflow.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
[{ json: { error: 'Test error' }, pairedItem: { item: 0 }, metadata: undefined }],
|
||||||
|
[{ json: { error: 'Test error' }, pairedItem: { item: 1 }, metadata: undefined }],
|
||||||
|
[{ json: { error: 'Test error' }, pairedItem: { item: 2 }, metadata: undefined }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle errors and continue on fail, no items, >= 1.3 version', async () => {
|
||||||
|
executeFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('database') // source
|
||||||
|
.mockReturnValueOnce('each') // mode
|
||||||
|
.mockReturnValueOnce(true) // waitForSubWorkflow
|
||||||
|
.mockReturnValueOnce([]); // workflowInputs.schema
|
||||||
|
|
||||||
|
executeFunctions.getNode.mockReturnValue({ typeVersion: 1.3 } as INode);
|
||||||
|
|
||||||
|
(getWorkflowInfo as jest.Mock).mockRejectedValue(new Error('Test error'));
|
||||||
|
(executeFunctions.continueOnFail as jest.Mock).mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = await executeWorkflow.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(result).toEqual([[{ json: { error: 'Test error' }, pairedItem: { item: 0 } }]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle errors and continue on fail, multiple items, >= 1.3 version', async () => {
|
||||||
|
executeFunctions.getNodeParameter
|
||||||
|
.mockReturnValueOnce('database') // source
|
||||||
|
.mockReturnValueOnce('each') // mode
|
||||||
|
.mockReturnValueOnce(true) // waitForSubWorkflow
|
||||||
|
.mockReturnValueOnce([]); // workflowInputs.schema
|
||||||
|
|
||||||
|
executeFunctions.getNode.mockReturnValue({ typeVersion: 1.3 } as INode);
|
||||||
|
executeFunctions.getInputData.mockReturnValueOnce([
|
||||||
|
{ json: { key: '1' } },
|
||||||
|
{ json: { key: '2' } },
|
||||||
|
{ json: { key: '3' } },
|
||||||
|
]);
|
||||||
|
|
||||||
|
(getWorkflowInfo as jest.Mock).mockRejectedValue(new Error('Test error'));
|
||||||
|
(executeFunctions.continueOnFail as jest.Mock).mockReturnValue(true);
|
||||||
|
|
||||||
|
const result = await executeWorkflow.execute.call(executeFunctions);
|
||||||
|
|
||||||
|
expect(result).toEqual([
|
||||||
|
[
|
||||||
|
{ json: { error: 'Test error' }, pairedItem: { item: 0 }, metadata: undefined },
|
||||||
|
{ json: { error: 'Test error' }, pairedItem: { item: 1 }, metadata: undefined },
|
||||||
|
{ json: { error: 'Test error' }, pairedItem: { item: 2 }, metadata: undefined },
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
test('should throw error if not continuing on fail', async () => {
|
test('should throw error if not continuing on fail', async () => {
|
||||||
executeFunctions.getNodeParameter
|
executeFunctions.getNodeParameter
|
||||||
.mockReturnValueOnce('database') // source
|
.mockReturnValueOnce('database') // source
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class ExecuteWorkflow implements INodeType {
|
|||||||
icon: 'fa:sign-in-alt',
|
icon: 'fa:sign-in-alt',
|
||||||
iconColor: 'orange-red',
|
iconColor: 'orange-red',
|
||||||
group: ['transform'],
|
group: ['transform'],
|
||||||
version: [1, 1.1, 1.2],
|
version: [1, 1.1, 1.2, 1.3],
|
||||||
subtitle: '={{"Workflow: " + $parameter["workflowId"]}}',
|
subtitle: '={{"Workflow: " + $parameter["workflowId"]}}',
|
||||||
description: 'Execute another workflow',
|
description: 'Execute another workflow',
|
||||||
defaults: {
|
defaults: {
|
||||||
@@ -369,11 +369,16 @@ export class ExecuteWorkflow implements INodeType {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (this.continueOnFail()) {
|
if (this.continueOnFail()) {
|
||||||
if (returnData[i] === undefined) {
|
const nodeVersion = this.getNode().typeVersion;
|
||||||
returnData[i] = [];
|
// In versions < 1.3 using the "Continue (using error output)" mode
|
||||||
}
|
// the node would return items in extra "error branches" instead of
|
||||||
|
// returning an array of items on the error output. These branches weren't really shown correctly on the UI.
|
||||||
|
// In the fixed >= 1.3 versions the errors are now all output into the single error output as an array of error items.
|
||||||
|
const outputIndex = nodeVersion >= 1.3 ? 0 : i;
|
||||||
|
|
||||||
|
returnData[outputIndex] ??= [];
|
||||||
const metadata = parseErrorMetadata(error);
|
const metadata = parseErrorMetadata(error);
|
||||||
returnData[i].push({
|
returnData[outputIndex].push({
|
||||||
json: { error: error.message },
|
json: { error: error.message },
|
||||||
pairedItem: { item: i },
|
pairedItem: { item: i },
|
||||||
metadata,
|
metadata,
|
||||||
|
|||||||
Reference in New Issue
Block a user