first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled

This commit is contained in:
2026-03-17 16:22:57 +03:30
commit 3d5eaf9445
15349 changed files with 2847338 additions and 0 deletions
@@ -0,0 +1,133 @@
import get from 'lodash/get';
import { constructExecutionMetaData } from 'n8n-core';
import {
NodeOperationError,
type IDataObject,
type IExecuteFunctions,
type IGetNodeParameterOptions,
type INode,
} from 'n8n-workflow';
import { type SetNodeOptions } from '../../v2/helpers/interfaces';
import { execute } from '../../v2/manual.mode';
const node: INode = {
id: '11',
name: 'Set Node',
type: 'n8n-nodes-base.set',
typeVersion: 3.4,
position: [42, 42],
parameters: {
mode: 'manual',
fields: {
values: [],
},
include: 'none',
options: {},
},
};
const createMockExecuteFunction = (
nodeParameters: IDataObject,
continueOnFail: boolean = false,
) => {
const fakeExecuteFunction = {
getNodeParameter(
parameterName: string,
_itemIndex: number,
fallbackValue?: IDataObject,
options?: IGetNodeParameterOptions,
) {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue);
},
getNode() {
return node;
},
getWorkflowSettings() {
return {};
},
helpers: { constructExecutionMetaData },
continueOnFail: () => continueOnFail,
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
describe('test Set2, manual Mode', () => {
const item = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const options: SetNodeOptions = {
include: 'none',
};
const rawData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
afterEach(() => jest.resetAllMocks());
describe('fixed mode', () => {
const assignments = {
assignments: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'test',
value: '{ ok: 1 }',
type: 'object',
},
],
};
const fakeExecuteFunction = createMockExecuteFunction({ assignments });
it('should parse json with the jsonOutput in node parameter and compose a return item', async () => {
const result = await execute.call(fakeExecuteFunction, item, 0, options, rawData, node);
expect(result).toEqual({ json: { test: { ok: 1 } }, pairedItem: { item: 0 } });
});
});
describe('error handling', () => {
const assignments = {
assignments: [
{
// eslint-disable-next-line n8n-nodes-base/node-param-display-name-miscased
name: 'test',
value: '{ ok: 1',
type: 'object',
},
],
};
it('should return an error object with pairedItem when continueOnFail is true', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ assignments }, true);
const output = await execute.call(fakeExecuteFunction, item, 0, options, {}, node);
expect(output).toEqual({
json: { error: "'test' expects a object but we got '{ ok: 1' [item 0]" },
pairedItem: { item: 0 },
});
});
it('should throw an error when continueOnFail is false', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ assignments }, false);
await expect(execute.call(fakeExecuteFunction, item, 0, options, {}, node)).rejects.toThrow(
NodeOperationError,
);
});
});
});
@@ -0,0 +1,145 @@
import get from 'lodash/get';
import { constructExecutionMetaData } from 'n8n-core';
import {
NodeOperationError,
type IDataObject,
type IExecuteFunctions,
type IGetNodeParameterOptions,
type INode,
} from 'n8n-workflow';
import { type SetNodeOptions } from '../../v2/helpers/interfaces';
import * as utils from '../../v2/helpers/utils';
import { execute } from '../../v2/raw.mode';
const node: INode = {
id: '11',
name: 'Set Node',
type: 'n8n-nodes-base.set',
typeVersion: 3,
position: [42, 42],
parameters: {
mode: 'raw',
fields: {
values: [],
},
include: 'none',
options: {},
},
};
const createMockExecuteFunction = (
nodeParameters: IDataObject,
continueOnFail: boolean = false,
) => {
const fakeExecuteFunction = {
getNodeParameter(
parameterName: string,
_itemIndex: number,
fallbackValue?: IDataObject,
options?: IGetNodeParameterOptions,
) {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue);
},
getNode() {
return node;
},
helpers: { constructExecutionMetaData },
continueOnFail: () => continueOnFail,
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
describe('test Set2, rawMode/json Mode', () => {
const item = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const options: SetNodeOptions = {
include: 'none',
};
afterEach(() => jest.resetAllMocks());
describe('fixed mode', () => {
const jsonData = { jsonData: 1 };
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: jsonData });
const rawData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
it('should parse json with the jsonOutput in node parameter and compose a return item', async () => {
jest.spyOn(utils, 'parseJsonParameter');
jest.spyOn(utils, 'composeReturnItem');
const result = await execute.call(fakeExecuteFunction, item, 0, options, rawData, node);
expect(result).toEqual({ json: jsonData, pairedItem: { item: 0 } });
expect(utils.parseJsonParameter).toHaveBeenCalledWith(jsonData, node, 0);
expect(utils.composeReturnItem).toHaveBeenCalledWith(0, item, jsonData, options, 3);
});
});
describe('expression mode', () => {
const jsonData = { my_field_1: 'value' };
const jsonDataString = '{"my_field_1": "value"}';
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: jsonDataString });
const rawData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
jsonOutput: jsonDataString,
};
it('should parse json with resolved expression data and compose a return item', async () => {
jest.spyOn(utils, 'parseJsonParameter');
jest.spyOn(utils, 'composeReturnItem');
jest.spyOn(utils, 'resolveRawData');
const result = await execute.call(fakeExecuteFunction, item, 0, options, rawData, node);
expect(utils.parseJsonParameter).toHaveBeenCalledWith(jsonDataString, node, 0);
expect(utils.composeReturnItem).toHaveBeenCalledWith(0, item, jsonData, options, 3);
expect(utils.resolveRawData).toHaveBeenCalledWith(jsonDataString, 0);
expect(result).toEqual({ json: jsonData, pairedItem: { item: 0 } });
});
});
describe('error handling', () => {
it('should return an error object with pairedItem when continueOnFail is true', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: 'jsonData' }, true);
const output = await execute.call(fakeExecuteFunction, item, 0, options, {}, node);
expect(output).toEqual({
json: { error: "The 'JSON Output' in item 0 does not contain a valid JSON object" },
pairedItem: { item: 0 },
});
});
it('should throw an error when continueOnFail is false', async () => {
const fakeExecuteFunction = createMockExecuteFunction({ jsonOutput: 'jsonData' }, false);
await expect(execute.call(fakeExecuteFunction, item, 0, options, {}, node)).rejects.toThrow(
NodeOperationError,
);
});
});
});
@@ -0,0 +1,435 @@
import get from 'lodash/get';
import { constructExecutionMetaData } from 'n8n-core';
import type { IDataObject, IExecuteFunctions, IGetNodeParameterOptions, INode } from 'n8n-workflow';
import type { SetNodeOptions } from '../../v2/helpers/interfaces';
import {
composeReturnItem,
parseJsonParameter,
validateEntry,
resolveRawData,
} from '../../v2/helpers/utils';
export const node: INode = {
id: '11',
name: 'Edit Fields',
type: 'n8n-nodes-base.set',
typeVersion: 3,
position: [42, 42],
parameters: {
mode: 'manual',
fields: {
values: [],
},
include: 'none',
options: {},
},
};
export const createMockExecuteFunction = (nodeParameters: IDataObject) => {
const fakeExecuteFunction = {
getNodeParameter(
parameterName: string,
_itemIndex: number,
fallbackValue?: IDataObject,
options?: IGetNodeParameterOptions,
) {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue);
},
getNode() {
return node;
},
helpers: { constructExecutionMetaData },
continueOnFail: () => false,
} as unknown as IExecuteFunctions;
return fakeExecuteFunction;
};
describe('test Set2, composeReturnItem', () => {
it('should compose return item including no other fields', () => {
const fakeExecuteFunction = createMockExecuteFunction({});
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const options: SetNodeOptions = {
include: 'none',
};
const result = composeReturnItem.call(fakeExecuteFunction, 0, inputItem, newData, options, 3.4);
expect(result).toEqual({
json: {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
},
pairedItem: {
item: 0,
},
});
});
it('should compose return item including selected fields', () => {
const fakeExecuteFunction = createMockExecuteFunction({ includeFields: 'input1, input2' });
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const options: SetNodeOptions = {
include: 'selected',
};
const result = composeReturnItem.call(fakeExecuteFunction, 0, inputItem, newData, options, 3.4);
expect(result).toEqual({
json: {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
input1: 'value1',
input2: 2,
obj: {
key: 'value',
},
},
pairedItem: {
item: 0,
},
});
});
it('should include binary when expected in version <3.4', () => {
const fakeExecuteFunction = createMockExecuteFunction({});
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
binary: {
data: {
data: 'content',
mimeType: 'image/jpg',
},
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const resultWithIncludeBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
includeBinary: true,
},
3.3,
);
expect(resultWithIncludeBinary.binary).toEqual(inputItem.binary);
const resultWithoutIncludeBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
},
3.3,
);
expect(resultWithoutIncludeBinary.binary).toBeUndefined();
});
it('should include binary when expected in version >=3.4', () => {
const fakeExecuteFunction = createMockExecuteFunction({});
const inputItem = {
json: {
input1: 'value1',
input2: 2,
input3: [1, 2, 3],
},
pairedItem: {
item: 0,
input: undefined,
},
binary: {
data: {
data: 'content',
mimeType: 'image/jpg',
},
},
};
const newData = {
num1: 55,
str1: '42',
arr1: ['foo', 'bar'],
obj: {
key: 'value',
},
};
const resultWithStripBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
stripBinary: true,
},
3.4,
);
expect(resultWithStripBinary.binary).toBeUndefined();
const resultWithoutStripBinary = composeReturnItem.call(
fakeExecuteFunction,
0,
inputItem,
newData,
{
include: 'all',
},
3.4,
);
expect(resultWithoutStripBinary.binary).toEqual(inputItem.binary);
});
});
describe('test Set2, parseJsonParameter', () => {
it('should parse valid JSON string', () => {
const result = parseJsonParameter('{"foo": "bar"}', node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate single quotes in string', () => {
const result = parseJsonParameter("{'foo': 'bar'}", node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate unquoted keys', () => {
const result = parseJsonParameter("{foo: 'bar'}", node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate trailing comma', () => {
const result = parseJsonParameter('{"foo": "bar"},', node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
});
});
it('should tolerate trailing commas in objects', () => {
const result = parseJsonParameter("{foo: 'bar', baz: {'foo': 'bar',}, }", node, 0, 'test');
expect(result).toEqual({
foo: 'bar',
baz: {
foo: 'bar',
},
});
});
});
describe('test Set2, validateEntry', () => {
it('should convert number to string', () => {
const result = validateEntry('foo', 'string', 42 as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: '42',
});
});
it('should convert array to string', () => {
const result = validateEntry('foo', 'string', [1, 2, 3] as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: '[1,2,3]',
});
});
it('should convert object to string', () => {
const result = validateEntry('foo', 'string', { foo: 'bar' } as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: '{"foo":"bar"}',
});
});
it('should convert boolean to string', () => {
const result = validateEntry('foo', 'string', true as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: 'true',
});
});
it('should convert undefined to string', () => {
const result = validateEntry('foo', 'string', undefined as unknown as string, node, 0);
expect(result).toEqual({
name: 'foo',
value: 'undefined',
});
});
});
describe('test Set2, resolveRawData', () => {
const createMockExecuteFunctionForResolve = (returnValue: any) => {
return {
evaluateExpression: jest.fn().mockReturnValue(returnValue),
} as unknown as IExecuteFunctions;
};
it('should handle strings with special replacement patterns like $&', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world $&');
const input = '{{ "hello world $&" }}';
const result = resolveRawData.call(mockFunction, input, 0);
// The result should contain the literal $& and not have it replaced
expect(result).toBe('hello world $&');
expect(mockFunction.evaluateExpression).toHaveBeenCalledWith('{{ "hello world $&" }}', 0);
});
it('should handle strings with $` pattern', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world $`');
const input = '{{ "hello world $`" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world $`');
});
it("should handle strings with $' pattern", () => {
const mockFunction = createMockExecuteFunctionForResolve("hello world $'");
const input = '{{ "hello world $\'" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe("hello world $'");
});
it('should handle strings with $n pattern', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world $1 $2');
const input = '{{ "hello world $1 $2" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world $1 $2');
});
it('should handle JSON objects with special patterns', () => {
const mockFunction = createMockExecuteFunctionForResolve({ message: 'hello world $&' });
const input = '{{ {"message": "hello world $&"} }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('{"message":"hello world $&"}');
});
it('should handle multiple expressions with special patterns', () => {
const mockFunction = {
evaluateExpression: jest.fn().mockReturnValueOnce('hello $&').mockReturnValueOnce('world $`'),
} as unknown as IExecuteFunctions;
const input = 'start {{ expr1 }} middle {{ expr2 }} end';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('start hello $& middle world $` end');
});
it('should handle regular strings without special patterns', () => {
const mockFunction = createMockExecuteFunctionForResolve('hello world');
const input = '{{ "hello world" }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world');
});
it('should handle objects and stringify them', () => {
const mockFunction = createMockExecuteFunctionForResolve({ message: 'hello world', count: 42 });
const input = '{{ someExpression }}';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('{"message":"hello world","count":42}');
});
it('should return raw data when no resolvables are found', () => {
const mockFunction = createMockExecuteFunctionForResolve('unused');
const input = 'hello world without expressions';
const result = resolveRawData.call(mockFunction, input, 0);
expect(result).toBe('hello world without expressions');
expect(mockFunction.evaluateExpression).not.toHaveBeenCalled();
});
});