From 5dfc376353d3b75a4e9561413f87a304003980d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Sun, 17 Mar 2024 11:22:30 +0100 Subject: [PATCH] fix(core): Improve handling of invalid objects in `cleanupParameterData` Fixes https://community.n8n.io/t/error-cannot-read-properties-of-undefined-reading-name/30114 --- packages/core/src/NodeExecuteFunctions.ts | 16 +++++++----- .../core/test/NodeExecuteFunctions.test.ts | 26 +++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index cac599b5cad13..ca1ea6028bae3 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -33,6 +33,7 @@ import { Agent, type AgentOptions } from 'https'; import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; import pick from 'lodash/pick'; +import { DateTime } from 'luxon'; import { extension, lookup } from 'mime-types'; import type { BinaryHelperFunctions, @@ -2096,7 +2097,7 @@ export async function getCredentials( * Clean up parameter data to make sure that only valid data gets returned * INFO: Currently only converts Luxon Dates as we know for sure it will not be breaking */ -function cleanupParameterData(inputData: NodeParameterValueType): void { +export function cleanupParameterData(inputData: NodeParameterValueType): void { if (typeof inputData !== 'object' || inputData === null) { return; } @@ -2107,14 +2108,15 @@ function cleanupParameterData(inputData: NodeParameterValueType): void { } if (typeof inputData === 'object') { - Object.keys(inputData).forEach((key) => { - if (typeof inputData[key as keyof typeof inputData] === 'object') { - if (inputData[key as keyof typeof inputData]?.constructor.name === 'DateTime') { + type Key = keyof typeof inputData; + (Object.keys(inputData) as Key[]).forEach((key) => { + const value = inputData[key]; + if (typeof value === 'object') { + if (value instanceof DateTime) { // Is a special luxon date so convert to string - inputData[key as keyof typeof inputData] = - inputData[key as keyof typeof inputData]?.toString(); + inputData[key] = value.toString(); } else { - cleanupParameterData(inputData[key as keyof typeof inputData]); + cleanupParameterData(value); } } }); diff --git a/packages/core/test/NodeExecuteFunctions.test.ts b/packages/core/test/NodeExecuteFunctions.test.ts index 13791d126caaa..a644cd6793ec5 100644 --- a/packages/core/test/NodeExecuteFunctions.test.ts +++ b/packages/core/test/NodeExecuteFunctions.test.ts @@ -1,4 +1,5 @@ import { + cleanupParameterData, copyInputItems, getBinaryDataBuffer, parseIncomingMessage, @@ -7,6 +8,7 @@ import { removeEmptyBody, setBinaryDataBuffer, } from '@/NodeExecuteFunctions'; +import { DateTime } from 'luxon'; import { mkdtempSync, readFileSync } from 'fs'; import type { IncomingMessage } from 'http'; import { mock } from 'jest-mock-extended'; @@ -18,6 +20,7 @@ import type { IRequestOptions, ITaskDataConnections, IWorkflowExecuteAdditionalData, + NodeParameterValue, Workflow, WorkflowHooks, } from 'n8n-workflow'; @@ -414,6 +417,29 @@ describe('NodeExecuteFunctions', () => { }); }); + describe('cleanupParameterData', () => { + it('should stringify Luxon dates in-place', () => { + const input = { x: 1, y: DateTime.now() as unknown as NodeParameterValue }; + expect(typeof input.y).toBe('object'); + cleanupParameterData(input); + expect(typeof input.y).toBe('string'); + }); + + it('should handle objects with nameless constructors', () => { + const input = { x: 1, y: { constructor: {} } as NodeParameterValue }; + expect(typeof input.y).toBe('object'); + cleanupParameterData(input); + expect(typeof input.y).toBe('object'); + }); + + it('should handle objects without a constructor', () => { + const input = { x: 1, y: { constructor: undefined } as unknown as NodeParameterValue }; + expect(typeof input.y).toBe('object'); + cleanupParameterData(input); + expect(typeof input.y).toBe('object'); + }); + }); + describe('copyInputItems', () => { it('should pick only selected properties', () => { const output = copyInputItems(