feat: Option to skip validation in getNodeParameter (#14726)

This commit is contained in:
Michael Kret
2025-04-18 10:05:25 +03:00
committed by GitHub
parent e1632348f2
commit b615e51f13
5 changed files with 74 additions and 3 deletions

View File

@@ -18,6 +18,7 @@ import { ApplicationError, ExpressionError, NodeConnectionTypes } from 'n8n-work
import { describeCommonTests } from './shared-tests';
import { ExecuteContext } from '../execute-context';
import * as validateUtil from '../utils/validate-value-against-schema';
describe('ExecuteContext', () => {
const testCredentialType = 'testCredential';
@@ -177,6 +178,18 @@ describe('ExecuteContext', () => {
const parameter = executeContext.getNodeParameter('testParameter', 0);
expect(parameter).toEqual([{ name: undefined, value: undefined }]);
});
it('should not validate parameter if skipValidation in options', () => {
const validateSpy = jest.spyOn(validateUtil, 'validateValueAgainstSchema');
executeContext.getNodeParameter('testParameter', 0, '', {
skipValidation: true,
});
expect(validateSpy).not.toHaveBeenCalled();
validateSpy.mockRestore();
});
});
describe('getCredentials', () => {

View File

@@ -406,6 +406,8 @@ export abstract class NodeExecutionContext implements Omit<FunctionsBase, 'getCr
});
}
if (options?.skipValidation) return returnData;
// Validate parameter value if it has a schema defined(RMC) or validateType defined
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
returnData = validateValueAgainstSchema(

View File

@@ -1,3 +1,7 @@
import type { MockProxy } from 'jest-mock-extended';
import { mock } from 'jest-mock-extended';
import type { IExecuteFunctions } from 'n8n-workflow';
import * as update from '../../../../v2/actions/record/update.operation';
import * as transport from '../../../../v2/transport';
import { createMockExecuteFunction } from '../helpers';
@@ -38,6 +42,44 @@ jest.mock('../../../../v2/transport', () => {
});
describe('Test AirtableV2, update operation', () => {
let mockExecuteFunctions: MockProxy<IExecuteFunctions>;
afterEach(() => {
jest.clearAllMocks();
});
it('should skip validation if typecast option is true', async () => {
mockExecuteFunctions = mock<IExecuteFunctions>();
mockExecuteFunctions.helpers.constructExecutionMetaData = jest.fn(() => []);
mockExecuteFunctions.getNodeParameter.mockImplementation((key: string) => {
if (key === 'columns.mappingMode') {
return 'defineBelow';
}
if (key === 'columns.matchingColumns') {
return ['id'];
}
if (key === 'options') {
return {
typecast: true,
};
}
if (key === 'columns.value') {
return {
id: 'recXXX',
field1: 'foo 1',
field2: 'bar 1',
};
}
return undefined;
});
await update.execute.call(mockExecuteFunctions, [{ json: {} }], 'base', 'table');
expect(mockExecuteFunctions.getNodeParameter).toHaveBeenCalledWith('columns.value', 0, [], {
skipValidation: true,
});
});
it('should update a record by id, autoMapInputData', async () => {
const nodeParameters = {
operation: 'update',

View File

@@ -80,6 +80,7 @@ export async function execute(
try {
const records: UpdateRecord[] = [];
const options = this.getNodeParameter('options', i, {});
const typecast = options.typecast ? true : false;
if (dataMode === 'autoMapInputData') {
if (columnsToMatchOn.includes('id')) {
@@ -107,11 +108,22 @@ export async function execute(
}
if (dataMode === 'defineBelow') {
const getNodeParameterOptions = typecast ? { skipValidation: true } : undefined;
if (columnsToMatchOn.includes('id')) {
const { id, ...fields } = this.getNodeParameter('columns.value', i, []) as IDataObject;
const { id, ...fields } = this.getNodeParameter(
'columns.value',
i,
[],
getNodeParameterOptions,
) as IDataObject;
records.push({ id: id as string, fields });
} else {
const fields = this.getNodeParameter('columns.value', i, []) as IDataObject;
const fields = this.getNodeParameter(
'columns.value',
i,
[],
getNodeParameterOptions,
) as IDataObject;
const matches = findMatches(
tableData,
@@ -127,7 +139,7 @@ export async function execute(
}
}
const body: IDataObject = { typecast: options.typecast ? true : false };
const body: IDataObject = { typecast };
const responseData = await batchUpdate.call(this, endpoint, body, records);

View File

@@ -571,6 +571,8 @@ export interface IGetNodeParameterOptions {
extractValue?: boolean;
// get raw value of parameter with unresolved expressions
rawExpressions?: boolean;
// skip validation of parameter
skipValidation?: boolean;
}
namespace ExecuteFunctions {