diff --git a/packages/workflow/test/ExpressionExtensions/ArrayExtensions.test.ts b/packages/workflow/test/ExpressionExtensions/ArrayExtensions.test.ts index d85ec4a257..bec75c3f6a 100644 --- a/packages/workflow/test/ExpressionExtensions/ArrayExtensions.test.ts +++ b/packages/workflow/test/ExpressionExtensions/ArrayExtensions.test.ts @@ -2,8 +2,9 @@ * @jest-environment jsdom */ +import { arrayExtensions } from '@/Extensions/ArrayExtensions'; + import { evaluate } from './Helpers'; -import { arrayExtensions } from '../../src/Extensions/ArrayExtensions'; describe('Data Transformation Functions', () => { describe('Array Data Transformation Functions', () => { diff --git a/packages/workflow/test/ExpressionExtensions/BooleanExtensions.test.ts b/packages/workflow/test/ExpressionExtensions/BooleanExtensions.test.ts index 842158a5b3..8aeb57fb20 100644 --- a/packages/workflow/test/ExpressionExtensions/BooleanExtensions.test.ts +++ b/packages/workflow/test/ExpressionExtensions/BooleanExtensions.test.ts @@ -2,8 +2,9 @@ * @jest-environment jsdom */ +import { booleanExtensions } from '@/Extensions/BooleanExtensions'; + import { evaluate } from './Helpers'; -import { booleanExtensions } from '../../src/Extensions/BooleanExtensions'; describe('Data Transformation Functions', () => { describe('Boolean Data Transformation Functions', () => { diff --git a/packages/workflow/test/ExpressionExtensions/DateExtensions.test.ts b/packages/workflow/test/ExpressionExtensions/DateExtensions.test.ts index 09a5761d65..3ac22f96ec 100644 --- a/packages/workflow/test/ExpressionExtensions/DateExtensions.test.ts +++ b/packages/workflow/test/ExpressionExtensions/DateExtensions.test.ts @@ -4,10 +4,10 @@ import { DateTime } from 'luxon'; +import { dateExtensions } from '@/Extensions/DateExtensions'; import { getGlobalState } from '@/GlobalState'; import { evaluate, getLocalISOString } from './Helpers'; -import { dateExtensions } from '../../src/Extensions/DateExtensions'; const { defaultTimezone } = getGlobalState(); @@ -20,41 +20,43 @@ describe('Data Transformation Functions', () => { expect(evaluate('={{ DateTime.local(2023, 1, 23).isWeekend() }}')).toBe(false); }); - test('.beginningOf("week") should work correctly on a date', () => { - expect(evaluate('={{ DateTime.local(2023, 1, 20).beginningOf("week") }}')).toEqual( - DateTime.local(2023, 1, 16, { zone: defaultTimezone }), - ); + describe('.beginningOf', () => { + test('.beginningOf("week") should work correctly on a date', () => { + expect(evaluate('={{ DateTime.local(2023, 1, 20).beginningOf("week") }}')).toEqual( + DateTime.local(2023, 1, 16, { zone: defaultTimezone }), + ); - expect(evaluate('={{ new Date(2023, 0, 20).beginningOf("week") }}')).toEqual( - DateTime.local(2023, 1, 16, { zone: defaultTimezone }).toJSDate(), - ); - }); + expect(evaluate('={{ new Date(2023, 0, 20).beginningOf("week") }}')).toEqual( + DateTime.local(2023, 1, 16, { zone: defaultTimezone }).toJSDate(), + ); + }); - test('.beginningOf("week") should work correctly on a string', () => { - const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("week") }}'); - const expectedDate = DateTime.local(2023, 1, 23, { zone: defaultTimezone }).toJSDate(); + test('.beginningOf("week") should work correctly on a string', () => { + const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("week") }}'); + const expectedDate = DateTime.local(2023, 1, 23, { zone: defaultTimezone }).toJSDate(); - if (evaluatedDate && evaluatedDate instanceof Date) { - expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString()); - } - }); + if (evaluatedDate && evaluatedDate instanceof Date) { + expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString()); + } + }); - test('.beginningOf("month") should work correctly on a string', () => { - const evaluatedDate = evaluate('={{ "2023-06-16".toDate().beginningOf("month") }}'); - const expectedDate = DateTime.local(2023, 6, 1, { zone: defaultTimezone }).toJSDate(); + test('.beginningOf("month") should work correctly on a string', () => { + const evaluatedDate = evaluate('={{ "2023-06-16".toDate().beginningOf("month") }}'); + const expectedDate = DateTime.local(2023, 6, 1, { zone: defaultTimezone }).toJSDate(); - if (evaluatedDate && evaluatedDate instanceof Date) { - expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString()); - } - }); + if (evaluatedDate && evaluatedDate instanceof Date) { + expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString()); + } + }); - test('.beginningOf("year") should work correctly on a string', () => { - const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("year") }}'); - const expectedDate = DateTime.local(2023, 1, 1, { zone: defaultTimezone }).toJSDate(); + test('.beginningOf("year") should work correctly on a string', () => { + const evaluatedDate = evaluate('={{ "2023-01-30".toDate().beginningOf("year") }}'); + const expectedDate = DateTime.local(2023, 1, 1, { zone: defaultTimezone }).toJSDate(); - if (evaluatedDate && evaluatedDate instanceof Date) { - expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString()); - } + if (evaluatedDate && evaluatedDate instanceof Date) { + expect(evaluatedDate.toDateString()).toEqual(expectedDate.toDateString()); + } + }); }); test('.endOfMonth() should work correctly on a date', () => { @@ -66,18 +68,75 @@ describe('Data Transformation Functions', () => { ); }); - test('.extract("day") should work correctly on a date', () => { - expect(evaluate('={{ DateTime.local(2023, 1, 20).extract("day") }}')).toEqual(20); + describe('.extract', () => { + test('.extract("day") should work correctly on a date', () => { + expect(evaluate('={{ DateTime.local(2023, 1, 20).extract("day") }}')).toEqual(20); + }); + + test('should extract year from a date', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49").extract("year") }}')).toEqual(2024); + }); + + test('should extract yearDayNumber from a date', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49").extract("yearDayNumber") }}')).toEqual( + 90, + ); + }); + + test('should extract month from a date', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49").extract("month") }}')).toEqual(3); + }); + + test('should extract week from a date', () => { + expect(evaluate('={{ DateTime.local(2023, 1, 20).extract() }}')).toEqual(3); + expect(evaluate('={{ DateTime.local(2023, 1, 20).extract("week") }}')).toEqual(3); + }); + + test('should extract day from a date', () => { + expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("day") }}')).toEqual(30); + }); + + test('should extract hour from a date', () => { + expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("hour") }}')).toEqual(18); + }); + + test('should extract minute from a date', () => { + expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("minute") }}')).toEqual( + 49, + ); + }); + + test('should extract second from a date', () => { + expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("second") }}')).toEqual( + 0, + ); + }); + + test('should extract millisecond from a date', () => { + expect( + evaluate('={{ DateTime.fromISO("2024-03-30T18:49:00.123Z").extract("millisecond") }}'), + ).toEqual(123); + }); + + test('should return undefined for invalid unit', () => { + expect(evaluate('={{ DateTime.fromISO("2024-03-30T18:49").extract("invalid") }}')).toBe( + null, + ); + }); }); - test('.extract() should extract week for no args', () => { - expect(evaluate('={{ DateTime.local(2023, 1, 20).extract() }}')).toEqual(3); - }); + describe('.format', () => { + test('should format date with custom format', () => { + expect( + evaluate('={{ DateTime.fromISO("2024-03-30T18:49").format("yyyy LLL dd") }}'), + ).toEqual('2024 Mar 30'); + }); - test('.format("yyyy LLL dd") should work correctly on a date', () => { - expect(evaluate('={{ DateTime.local(2023, 1, 16).format("yyyy LLL dd") }}')).toEqual( - '2023 Jan 16', - ); + test('should format date with ISO format', () => { + expect( + evaluate('={{ DateTime.fromISO("2024-03-30T18:49").format("yyyy-MM-dd\'T\'HH:mm:ss") }}'), + ).toEqual('2024-03-30T18:49:00'); + }); }); test('.toDate() should work on a string', () => { @@ -85,49 +144,103 @@ describe('Data Transformation Functions', () => { expect(evaluate(`={{ "${getLocalISOString(date)}".toDate() }}`)).toEqual(date); }); - test('.inBetween() should work on string and Date', () => { - expect(evaluate("={{ $now.isBetween('2023-06-23'.toDate(), '2023-06-23') }}")).toBeDefined(); + describe('.inBetween', () => { + test('should work on string and Date', () => { + expect( + evaluate("={{ $now.isBetween('2023-06-23'.toDate(), '2023-06-23') }}"), + ).toBeDefined(); + }); + + test('should work on string and DateTime', () => { + expect(evaluate("={{ $now.isBetween($now, '2023-06-23') }}")).toBeDefined(); + }); + + test('should not work for invalid strings', () => { + expect(evaluate("={{ $now.isBetween($now, 'invalid') }}")).toBeUndefined(); + }); + + test('should not work for numbers', () => { + expect(evaluate('={{ $now.isBetween($now, 1) }}')).toBeUndefined(); + }); + + test('should not work for a single argument', () => { + expect(() => evaluate('={{ $now.isBetween($now) }}')).toThrow(); + }); + + test('should not work for a more than two arguments', () => { + expect(() => + evaluate("={{ $now.isBetween($now, '2023-06-23', '2023-09-21'.toDate()) }}"), + ).toThrow(); + }); }); - test('.inBetween() should work on string and DateTime', () => { - expect(evaluate("={{ $now.isBetween($now, '2023-06-23') }}")).toBeDefined(); + describe('.diffTo', () => { + test('should work with a single unit', () => { + expect( + evaluate( + "={{ '2025-01-01'.toDateTime().diffTo('2024-03-30T18:49:07.234', 'days').floor() }}", + ), + ).toEqual(276); + }); + + test('should work with an array of units', () => { + expect( + evaluate( + "={{ '2025-01-01T00:00:00.000'.toDateTime().diffTo('2024-03-30T18:49:07.234', ['months', 'days']) }}", + ), + ).toEqual({ months: 9, days: 1.2158884953703704 }); + }); + + test('should return difference in days', () => { + expect( + evaluate( + '={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-25T18:49:00Z", "days") }}', + ), + ).toEqual(5); + }); + + test('should return difference in hours', () => { + expect( + evaluate( + '={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T12:49:00Z", "hours") }}', + ), + ).toEqual(6); + }); + + test('should return difference in minutes', () => { + expect( + evaluate( + '={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T18:44:00Z", "minutes") }}', + ), + ).toEqual(5); + }); + + test('should return difference in seconds', () => { + expect( + evaluate( + '={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T18:48:55Z", "seconds") }}', + ), + ).toEqual(5); + }); + + test('should return difference in milliseconds', () => { + expect( + evaluate( + '={{ DateTime.fromISO("2024-03-30T18:49:00.500Z").diffTo("2024-03-30T18:49:00.000Z", "milliseconds") }}', + ), + ).toEqual(500); + }); + + test('should throw for invalid unit', () => { + expect(() => + evaluate( + '={{ DateTime.fromISO("2024-03-30T18:49:00Z").diffTo("2024-03-30T18:49:00Z", "invalid") }}', + ), + ).toThrow('Unsupported unit'); + }); }); - test('.inBetween() should not work for invalid strings', () => { - expect(evaluate("={{ $now.isBetween($now, 'invalid') }}")).toBeUndefined(); - }); - - test('.inBetween() should not work for numbers', () => { - expect(evaluate('={{ $now.isBetween($now, 1) }}')).toBeUndefined(); - }); - - test('.inBetween() should not work for a single argument', () => { - expect(() => evaluate('={{ $now.isBetween($now) }}')).toThrow(); - }); - - test('.inBetween() should not work for a more than two arguments', () => { - expect(() => - evaluate("={{ $now.isBetween($now, '2023-06-23', '2023-09-21'.toDate()) }}"), - ).toThrow(); - }); - - test('.diffTo() should work with a single unit', () => { - expect( - evaluate( - "={{ '2025-01-01'.toDateTime().diffTo('2024-03-30T18:49:07.234', 'days').floor() }}", - ), - ).toEqual(276); - }); - - test('.diffTo() should work with an array of units', () => { - expect( - evaluate( - "={{ '2025-01-01T00:00:00.000'.toDateTime().diffTo('2024-03-30T18:49:07.234', ['months', 'days']) }}", - ), - ).toEqual({ months: 9, days: 1.2158884953703704 }); - }); - - describe('toDateTime', () => { + describe('.toDateTime', () => { test('should return itself for DateTime', () => { const result = evaluate( "={{ DateTime.fromFormat('01-01-2024', 'dd-MM-yyyy').toDateTime() }}", @@ -149,7 +262,7 @@ describe('Data Transformation Functions', () => { }); }); - describe('toInt/toFloat', () => { + describe('.toInt/.toFloat', () => { test('should return milliseconds for DateTime', () => { expect(evaluate("={{ DateTime.fromISO('2024-01-01T00:00:00.000Z').toInt() }}")).toEqual( 1704067200000, @@ -168,7 +281,7 @@ describe('Data Transformation Functions', () => { }); }); - describe('toBoolean', () => { + describe('.toBoolean', () => { test('should return undefined', () => { expect(evaluate('={{ new Date("2024-01-01T00:00:00.000Z").toBoolean() }}')).toBeUndefined(); }); @@ -177,5 +290,93 @@ describe('Data Transformation Functions', () => { expect(dateExtensions.functions.toBoolean.doc).toBeUndefined(); }); }); + + describe('.isInLast', () => { + it('should return true if the date is within the last n minutes', () => { + expect( + evaluate( + `={{ new Date("${DateTime.now().minus({ minutes: 5 }).toISO()}").isInLast(10, "minutes") }}`, + ), + ).toBe(true); + }); + + it('should return false if the date is not within the last n minutes', () => { + expect( + evaluate( + `={{ new Date("${DateTime.now().minus({ minutes: 15 }).toISO()}").isInLast(10, "minutes") }}`, + ), + ).toBe(false); + }); + + it('should handle default unit as minutes', () => { + expect( + evaluate( + `={{ new Date("${DateTime.now().minus({ minutes: 5 }).toISO()}").isInLast(10) }}`, + ), + ).toBe(true); + }); + }); + + describe('.minus', () => { + it('should subtract days from the date', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").minus(7, "days") }}')).toEqual( + new Date('2024-03-23T18:49:00.000Z'), + ); + }); + + it('should subtract years from the date', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").minus(4, "years") }}')).toEqual( + new Date('2020-03-30T18:49:00.000Z'), + ); + }); + + it('should handle default unit as milliseconds', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").minus(1000) }}')).toEqual( + new Date('2024-03-30T18:48:59.000Z'), + ); + }); + + it('should handle DateTime instances', () => { + expect( + evaluate('={{ DateTime.fromISO("2024-03-30T18:49:00Z").minus(1, "day").toJSDate() }}'), + ).toEqual(new Date('2024-03-29T18:49:00.000Z')); + }); + }); + + describe('.plus', () => { + it('should subtract days from the date', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").plus(7, "days") }}')).toEqual( + new Date('2024-04-06T18:49:00.000Z'), + ); + }); + + it('should subtract years from the date', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").plus(4, "years") }}')).toEqual( + new Date('2028-03-30T18:49:00.000Z'), + ); + }); + + it('should handle default unit as milliseconds', () => { + expect(evaluate('={{ new Date("2024-03-30T18:49:00Z").plus(1000) }}')).toEqual( + new Date('2024-03-30T18:49:01.000Z'), + ); + }); + + it('should handle DateTime instances', () => { + expect( + evaluate('={{ DateTime.fromISO("2024-03-30T18:49:00Z").plus(1, "day").toJSDate() }}'), + ).toEqual(new Date('2024-03-31T18:49:00.000Z')); + }); + }); + + describe('.isDst', () => { + test('should return true for a date in DST', () => { + expect(evaluate('={{ DateTime.fromISO("2024-06-30T18:49:00Z").isDst() }}')).toBe(true); + }); + + test('should return false for a date not in DST', () => { + expect(evaluate('={{ DateTime.fromISO("2024-01-30T18:49:00Z").isDst() }}')).toBe(false); + }); + }); }); }); diff --git a/packages/workflow/test/ExpressionExtensions/ExpressionExtension.test.ts b/packages/workflow/test/ExpressionExtensions/ExpressionExtension.test.ts index e08f4f05c4..fc107484cb 100644 --- a/packages/workflow/test/ExpressionExtensions/ExpressionExtension.test.ts +++ b/packages/workflow/test/ExpressionExtensions/ExpressionExtension.test.ts @@ -4,11 +4,11 @@ /* eslint-disable n8n-local-rules/no-interpolation-in-regular-string */ +import { ExpressionExtensionError } from '@/errors/expression-extension.error'; import { extendTransform, extend } from '@/Extensions'; import { joinExpression, splitExpression } from '@/Extensions/ExpressionParser'; import { evaluate } from './Helpers'; -import { ExpressionExtensionError } from '../../src/errors/expression-extension.error'; describe('Expression Extension Transforms', () => { describe('extend() transform', () => { diff --git a/packages/workflow/test/ExpressionExtensions/ObjectExtensions.test.ts b/packages/workflow/test/ExpressionExtensions/ObjectExtensions.test.ts index 1107ceae54..120e5aa9d7 100644 --- a/packages/workflow/test/ExpressionExtensions/ObjectExtensions.test.ts +++ b/packages/workflow/test/ExpressionExtensions/ObjectExtensions.test.ts @@ -1,16 +1,31 @@ +import { objectExtensions } from '@/Extensions/ObjectExtensions'; + import { evaluate } from './Helpers'; -import { objectExtensions } from '../../src/Extensions/ObjectExtensions'; describe('Data Transformation Functions', () => { describe('Object Data Transformation Functions', () => { - test('.isEmpty() should work correctly on an object', () => { - expect(evaluate('={{({}).isEmpty()}}')).toEqual(true); - expect(evaluate('={{({ test1: 1 }).isEmpty()}}')).toEqual(false); + describe('.isEmpty', () => { + test('should return true for an empty object', () => { + expect(evaluate('={{ ({}).isEmpty() }}')).toBe(true); + }); + + test('should return false for a non-empty object', () => { + expect(evaluate('={{ ({ test: 1 }).isEmpty() }}')).toBe(false); + }); + + test('should return true for an object with only null/undefined values', () => { + expect(evaluate('={{ ({ test1: null, test2: undefined }).isEmpty() }}')).toBe(false); + }); }); - test('.hasField should work on an object', () => { - expect(evaluate('={{ ({ test1: 1 }).hasField("test1") }}')).toEqual(true); - expect(evaluate('={{ ({ test1: 1 }).hasField("test2") }}')).toEqual(false); + describe('.hasField', () => { + test('should return true if the key exists in the object', () => { + expect(evaluate('={{ ({ test1: 1 }).hasField("test1") }}')).toBe(true); + }); + + test('should return false if the key does not exist in the object', () => { + expect(evaluate('={{ ({ test1: 1 }).hasField("test2") }}')).toBe(false); + }); }); test('.removeField should work on an object', () => { @@ -27,68 +42,119 @@ describe('Data Transformation Functions', () => { }); }); - test('.removeFieldsContaining should work on an object', () => { - expect( - evaluate( - '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).removeFieldsContaining("removed") }}', - ), - ).toEqual({ - test1: 'i exist', + describe('.removeFieldsContaining', () => { + test('should work on an object', () => { + expect( + evaluate( + '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).removeFieldsContaining("removed") }}', + ), + ).toEqual({ + test1: 'i exist', + }); + }); + + test('should not work for empty string', () => { + expect(() => + evaluate( + '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).removeFieldsContaining("") }}', + ), + ).toThrow(); }); }); - test('.removeFieldsContaining should not work for empty string', () => { - expect(() => - evaluate( - '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).removeFieldsContaining("") }}', - ), - ).toThrow(); - }); + describe('.keepFieldsContaining', () => { + test('.keepFieldsContaining should work on an object', () => { + expect( + evaluate( + '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).keepFieldsContaining("exist") }}', + ), + ).toEqual({ + test1: 'i exist', + }); + }); - test('.keepFieldsContaining should work on an object', () => { - expect( - evaluate( - '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).keepFieldsContaining("exist") }}', - ), - ).toEqual({ - test1: 'i exist', + test('.keepFieldsContaining should work on a nested object', () => { + expect( + evaluate( + '={{ ({ test1: "i exist", test2: "i should be removed", test3: { test4: "me too" } }).keepFieldsContaining("exist") }}', + ), + ).toEqual({ + test1: 'i exist', + }); + }); + + test('.keepFieldsContaining should not work for empty string', () => { + expect(() => + evaluate( + '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).keepFieldsContaining("") }}', + ), + ).toThrow(); }); }); - test('.keepFieldsContaining should work on a nested object', () => { - expect( - evaluate( - '={{ ({ test1: "i exist", test2: "i should be removed", test3: { test4: "me too" } }).keepFieldsContaining("exist") }}', - ), - ).toEqual({ - test1: 'i exist', + describe('.compact', () => { + test('should work on an object', () => { + expect( + evaluate('={{ ({ test1: 1, test2: "2", test3: undefined, test4: null }).compact() }}'), + ).toEqual({ test1: 1, test2: '2' }); }); - }); - test('.keepFieldsContaining should not work for empty string', () => { - expect(() => - evaluate( - '={{ ({ test1: "i exist", test2: "i should be removed", test3: "i should also be removed" }).keepFieldsContaining("") }}', - ), - ).toThrow(); - }); + test('should remove fields with null, undefined, empty string, or "nil"', () => { + expect( + evaluate( + '={{ ({ test1: 0, test2: false, test3: "", test4: "nil", test5: NaN }).compact() }}', + ), + ).toEqual({ test1: 0, test2: false, test5: NaN }); + }); - test('.compact should work on an object', () => { - expect( - evaluate('={{ ({ test1: 1, test2: "2", test3: undefined, test4: null }).compact() }}'), - ).toEqual({ test1: 1, test2: '2' }); + test('should work on an empty object', () => { + expect(evaluate('={{ ({}).compact() }}')).toEqual({}); + }); + + test('should work on an object with all null/undefined values', () => { + expect(evaluate('={{ ({ test1: undefined, test2: null }).compact() }}')).toEqual({}); + }); + + test('should work on an object with nested null/undefined values', () => { + expect( + evaluate( + '={{ ({ test1: 1, test2: { nested1: null, nested2: "value" }, test3: undefined }).compact() }}', + ), + ).toEqual({ test1: 1, test2: { nested2: 'value' } }); + }); + + test('should not allow prototype pollution', () => { + ['{__proto__: {polluted: true}}', '{constructor: {prototype: {polluted: true}}}'].forEach( + (testExpression) => { + expect(evaluate(`={{ (${testExpression}).compact() }}`)).toEqual(null); + expect(({} as any).polluted).toBeUndefined(); + }, + ); + }); }); test('.urlEncode should work on an object', () => { expect(evaluate('={{ ({ test1: 1, test2: "2" }).urlEncode() }}')).toEqual('test1=1&test2=2'); }); - test('.keys should work on an object', () => { - expect(evaluate('={{ ({ test1: 1, test2: "2" }).keys() }}')).toEqual(['test1', 'test2']); + describe('.keys', () => { + test('should return an array of keys from the object', () => { + expect(evaluate('={{ ({ test1: 1, test2: 2 }).keys() }}')).toEqual(['test1', 'test2']); + }); + + test('should return an empty array for an empty object', () => { + expect(evaluate('={{ ({}).keys() }}')).toEqual([]); + }); }); - test('.values should work on an object', () => { - expect(evaluate('={{ ({ test1: 1, test2: "2" }).values() }}')).toEqual([1, '2']); + describe('.values', () => { + test('should return an array of values from the object', () => { + expect(evaluate('={{ ({ test1: 1, test2: "value" }).values() }}')).toEqual([1, 'value']); + }); + + test('should return an empty array for an empty object', () => { + expect(evaluate('={{ ({}).values() }}')).toEqual([]); + }); }); test('.toJsonString() should work on an object', () => { diff --git a/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts b/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts index 35b2318868..8986e5ad0e 100644 --- a/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts +++ b/packages/workflow/test/ExpressionExtensions/StringExtensions.test.ts @@ -3,21 +3,39 @@ */ import { DateTime } from 'luxon'; +import { ExpressionExtensionError } from '@/errors'; + import { evaluate } from './Helpers'; -import { ExpressionExtensionError } from '../../src/errors'; describe('Data Transformation Functions', () => { describe('String Data Transformation Functions', () => { - test('.isEmpty() should work correctly on a string that is not empty', () => { - expect(evaluate('={{"NotBlank".isEmpty()}}')).toEqual(false); + describe('.isEmpty', () => { + test('should work correctly on a string that is not empty', () => { + expect(evaluate('={{"NotBlank".isEmpty()}}')).toEqual(false); + }); + + test('should work correctly on a string that is empty', () => { + expect(evaluate('={{"".isEmpty()}}')).toEqual(true); + }); }); - test('.isEmpty() should work correctly on a string that is empty', () => { - expect(evaluate('={{"".isEmpty()}}')).toEqual(true); + describe('.isNotEmpty', () => { + test('should work correctly on a string that is not empty', () => { + expect(evaluate('={{"NotBlank".isNotEmpty()}}')).toEqual(true); + }); + + test('should work correctly on a string that is empty', () => { + expect(evaluate('={{"".isNotEmpty()}}')).toEqual(false); + }); + }); + + test('.length should return the string length', () => { + expect(evaluate('={{"String".length()}}')).toEqual(6); }); describe('.hash()', () => { test.each([ + ['base64', 'MTIzNDU='], ['md5', '827ccb0eea8a706c4c34a16891f84e7b'], ['sha1', '8cb2237d0679ca88db6464eac60da96345513964'], ['sha224', 'a7470858e79c282bc2f6adfd831b132672dfd1224c1e78cbf5bcd057'], @@ -38,6 +56,10 @@ describe('Data Transformation Functions', () => { expect(evaluate(`={{ "12345".hash("${hashFn}") }}`)).toEqual(hashValue); expect(evaluate(`={{ "12345".hash("${hashFn.toLowerCase()}") }}`)).toEqual(hashValue); }); + + test('should throw on invalid algorithm', () => { + expect(() => evaluate('={{ "12345".hash("invalid") }}')).toThrow('Unknown algorithm'); + }); }); test('.urlDecode should work correctly on a string', () => { @@ -64,10 +86,18 @@ describe('Data Transformation Functions', () => { expect(evaluate('={{ "TEST".toLowerCase() }}')).toEqual('test'); }); - test('.toDate should work correctly on a date string', () => { - expect(evaluate('={{ "2022-09-01T19:42:28.164Z".toDate() }}')).toEqual( - new Date('2022-09-01T19:42:28.164Z'), - ); + describe('.toDate', () => { + test('should work correctly on a date string', () => { + expect(evaluate('={{ "2022-09-01T19:42:28.164Z".toDate() }}')).toEqual( + new Date('2022-09-01T19:42:28.164Z'), + ); + }); + + test('should throw on invalid date', () => { + expect(() => evaluate('={{ "2022-09-32T19:42:28.164Z".toDate() }}')).toThrow( + 'cannot convert to date', + ); + }); }); test('.toFloat should work correctly on a string', () => { diff --git a/packages/workflow/test/NodeErrors.test.ts b/packages/workflow/test/NodeErrors.test.ts index 04aafb1610..e7e4def835 100644 --- a/packages/workflow/test/NodeErrors.test.ts +++ b/packages/workflow/test/NodeErrors.test.ts @@ -1,9 +1,8 @@ +import { UNKNOWN_ERROR_DESCRIPTION, UNKNOWN_ERROR_MESSAGE } from '@/Constants'; import { NodeOperationError } from '@/errors'; import { NodeApiError } from '@/errors/node-api.error'; import type { INode, JsonObject } from '@/Interfaces'; -import { UNKNOWN_ERROR_DESCRIPTION, UNKNOWN_ERROR_MESSAGE } from '../src/Constants'; - const node: INode = { id: '1', name: 'Postgres node', diff --git a/packages/workflow/test/WorkflowDataProxyEnvProvider.test.ts b/packages/workflow/test/WorkflowDataProxyEnvProvider.test.ts index 15ce485140..81049b1f55 100644 --- a/packages/workflow/test/WorkflowDataProxyEnvProvider.test.ts +++ b/packages/workflow/test/WorkflowDataProxyEnvProvider.test.ts @@ -1,5 +1,5 @@ -import { ExpressionError } from '../src/errors/expression.error'; -import { createEnvProvider, createEnvProviderState } from '../src/WorkflowDataProxyEnvProvider'; +import { ExpressionError } from '@/errors/expression.error'; +import { createEnvProvider, createEnvProviderState } from '@/WorkflowDataProxyEnvProvider'; describe('createEnvProviderState', () => { afterEach(() => {