diff --git a/packages/cli/src/__tests__/object-to-error.test.ts b/packages/cli/src/__tests__/object-to-error.test.ts new file mode 100644 index 0000000000000..311f4dce5536c --- /dev/null +++ b/packages/cli/src/__tests__/object-to-error.test.ts @@ -0,0 +1,43 @@ +import { mock } from 'jest-mock-extended'; +import type { INode } from 'n8n-workflow'; +import { NodeOperationError, type Workflow } from 'n8n-workflow'; + +import { objectToError } from '../workflow-execute-additional-data'; + +describe('objectToError', () => { + describe('node error handling', () => { + it('should create `NodeOperationError` when node is found', () => { + const errorObject = { + message: 'Test error', + node: { + name: 'testNode', + }, + }; + const workflow = mock(); + const node = mock(); + workflow.getNode.mockReturnValue(node); + + const result = objectToError(errorObject, workflow); + + expect(workflow.getNode).toHaveBeenCalledWith('testNode'); + expect(result).toBeInstanceOf(NodeOperationError); + }); + + it('should create `Error` when node is not found', () => { + const errorObject = { + message: 'Test error', + node: { + // missing `name` + }, + }; + const workflow = mock(); + + const result = objectToError(errorObject, workflow); + + expect(workflow.getNode).not.toHaveBeenCalled(); + expect(result).toBeInstanceOf(Error); + expect(result).not.toBeInstanceOf(NodeOperationError); + expect(result.message).toBe('Test error'); + }); + }); +}); diff --git a/packages/cli/src/utils.ts b/packages/cli/src/utils.ts index ff172de181a18..700f74f9d068a 100644 --- a/packages/cli/src/utils.ts +++ b/packages/cli/src/utils.ts @@ -58,7 +58,7 @@ export function isStringArray(value: unknown): value is string[] { export const isIntegerString = (value: string) => /^\d+$/.test(value); -export function isObjectLiteral(item: unknown): item is { [key: string]: string } { +export function isObjectLiteral(item: unknown): item is { [key: string]: unknown } { return typeof item === 'object' && item !== null && !Array.isArray(item); } diff --git a/packages/cli/src/workflow-execute-additional-data.ts b/packages/cli/src/workflow-execute-additional-data.ts index b7d966afa5913..2588a442d9975 100644 --- a/packages/cli/src/workflow-execute-additional-data.ts +++ b/packages/cli/src/workflow-execute-additional-data.ts @@ -52,7 +52,7 @@ import type { IWorkflowErrorData, UpdateExecutionPayload } from '@/interfaces'; import { NodeTypes } from '@/node-types'; import { Push } from '@/push'; import { WorkflowStatisticsService } from '@/services/workflow-statistics.service'; -import { findSubworkflowStart, isWorkflowIdValid } from '@/utils'; +import { findSubworkflowStart, isObjectLiteral, isWorkflowIdValid } from '@/utils'; import * as WorkflowHelpers from '@/workflow-helpers'; import { WorkflowRepository } from './databases/repositories/workflow.repository'; @@ -80,11 +80,20 @@ export function objectToError(errorObject: unknown, workflow: Workflow): Error { if (errorObject instanceof Error) { // If it's already an Error instance, return it as is. return errorObject; - } else if (errorObject && typeof errorObject === 'object' && 'message' in errorObject) { + } else if ( + isObjectLiteral(errorObject) && + 'message' in errorObject && + typeof errorObject.message === 'string' + ) { // If it's an object with a 'message' property, create a new Error instance. let error: Error | undefined; - if ('node' in errorObject) { - const node = workflow.getNode((errorObject.node as { name: string }).name); + if ( + 'node' in errorObject && + isObjectLiteral(errorObject.node) && + typeof errorObject.node.name === 'string' + ) { + const node = workflow.getNode(errorObject.node.name); + if (node) { error = new NodeOperationError( node, @@ -95,7 +104,7 @@ export function objectToError(errorObject: unknown, workflow: Workflow): Error { } if (error === undefined) { - error = new Error(errorObject.message as string); + error = new Error(errorObject.message); } if ('description' in errorObject) {