fix(Airtable Node): Create record: skip type validation when typecast is enabled (#18393)

This commit is contained in:
Elias Meire
2025-08-15 18:59:31 +02:00
committed by GitHub
parent 726f0ff37a
commit dcd060ce33
3 changed files with 74 additions and 20 deletions

View File

@@ -15,23 +15,25 @@ export const node: INode = {
export const createMockExecuteFunction = (nodeParameters: IDataObject) => { export const createMockExecuteFunction = (nodeParameters: IDataObject) => {
const fakeExecuteFunction = { const fakeExecuteFunction = {
getInputData() { getInputData: jest.fn(() => {
return [{ json: {} }]; return [{ json: {} }];
}, }),
getNodeParameter( getNodeParameter: jest.fn(
(
parameterName: string, parameterName: string,
_itemIndex: number, _itemIndex: number,
fallbackValue?: IDataObject, fallbackValue?: IDataObject,
options?: IGetNodeParameterOptions, options?: IGetNodeParameterOptions,
) { ) => {
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName; const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
return get(nodeParameters, parameter, fallbackValue); return get(nodeParameters, parameter, fallbackValue);
}, },
getNode() { ),
getNode: jest.fn(() => {
return node; return node;
}, }),
helpers: { constructExecutionMetaData }, helpers: { constructExecutionMetaData: jest.fn(constructExecutionMetaData) },
continueOnFail: () => false, continueOnFail: jest.fn(() => false),
} as unknown as IExecuteFunctions; } as unknown as IExecuteFunctions;
return fakeExecuteFunction; return fakeExecuteFunction;
}; };

View File

@@ -154,4 +154,55 @@ describe('Test AirtableV2, create operation', () => {
typecast: false, typecast: false,
}); });
}); });
it('should skip validation if typecast option is true', async () => {
const nodeParameters = {
operation: 'create',
columns: {
mappingMode: 'defineBelow',
value: {
bar: 'bar 1',
foo: 'foo 1',
},
matchingColumns: [],
schema: [
{
id: 'foo',
displayName: 'foo',
required: false,
defaultMatch: false,
display: true,
type: 'string',
},
{
id: 'bar',
displayName: 'bar',
required: false,
defaultMatch: false,
display: true,
type: 'string',
},
],
},
options: {
typecast: true,
},
};
const mockExecuteFunctions = createMockExecuteFunction(nodeParameters);
await create.execute.call(mockExecuteFunctions, [{ json: {} }], 'appYoLbase', 'tblltable');
expect(mockExecuteFunctions.getNodeParameter).toHaveBeenCalledWith('columns.value', 0, [], {
skipValidation: true,
});
expect(transport.apiRequest).toHaveBeenCalledTimes(1);
expect(transport.apiRequest).toHaveBeenCalledWith('POST', 'appYoLbase/tblltable', {
fields: {
foo: 'foo 1',
bar: 'bar 1',
},
typecast: true,
});
});
}); });

View File

@@ -63,17 +63,18 @@ export async function execute(
for (let i = 0; i < items.length; i++) { for (let i = 0; i < items.length; i++) {
try { try {
const options = this.getNodeParameter('options', i, {}); const options = this.getNodeParameter('options', i, {});
const typecast = Boolean(options.typecast);
const body: IDataObject = { const body: IDataObject = { typecast };
typecast: options.typecast ? true : false,
};
if (dataMode === 'autoMapInputData') { if (dataMode === 'autoMapInputData') {
body.fields = removeIgnored(items[i].json, options.ignoreFields as string); body.fields = removeIgnored(items[i].json, options.ignoreFields as string);
} }
if (dataMode === 'defineBelow') { if (dataMode === 'defineBelow') {
const fields = this.getNodeParameter('columns.value', i, []) as IDataObject; const fields = this.getNodeParameter('columns.value', i, [], {
skipValidation: typecast,
}) as IDataObject;
body.fields = fields; body.fields = fields;
} }
@@ -85,7 +86,7 @@ export async function execute(
{ itemData: { item: i } }, { itemData: { item: i } },
); );
returnData.push(...executionData); returnData.push.apply(returnData, executionData);
} catch (error) { } catch (error) {
error = processAirtableError(error as NodeApiError, undefined, i); error = processAirtableError(error as NodeApiError, undefined, i);
if (this.continueOnFail()) { if (this.continueOnFail()) {