mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
feat(supabase): add returnData toggle for RLS compatibility
- Added returnData option to create operation - Updated tests for true/false and non-boolean cases - Preserved original getAll test
This commit is contained in:
parent
37d4b00e3f
commit
83fe8ac118
|
@ -22,6 +22,7 @@ export async function supabaseApiRequest(
|
|||
qs: IDataObject = {},
|
||||
uri?: string,
|
||||
headers: IDataObject = {},
|
||||
returnData: boolean = true,
|
||||
) {
|
||||
const credentials = await this.getCredentials<{
|
||||
host: string;
|
||||
|
@ -29,9 +30,7 @@ export async function supabaseApiRequest(
|
|||
}>('supabaseApi');
|
||||
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
Prefer: 'return=representation',
|
||||
},
|
||||
headers: returnData ? { Prefer: 'return=representation' } : { Prefer: 'return=minimal' },
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
|
|
|
@ -170,6 +170,19 @@ export const rowFields: INodeProperties[] = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'Return Inserted Data',
|
||||
name: 'returnData',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['row'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
description: 'Whether to return the inserted data. Disable if RLS policies restrict SELECT.',
|
||||
},
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* row:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
|
|
@ -132,6 +132,7 @@ export class Supabase implements INodeType {
|
|||
|
||||
if (operation === 'create') {
|
||||
const records: IDataObject[] = [];
|
||||
const returnDataParam = this.getNodeParameter('returnData', 0, true) as boolean;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
const record: IDataObject = {};
|
||||
|
@ -159,12 +160,17 @@ export class Supabase implements INodeType {
|
|||
const endpoint = `/${tableId}`;
|
||||
|
||||
try {
|
||||
const createdRows: IDataObject[] = await supabaseApiRequest.call(
|
||||
const response = await supabaseApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
endpoint,
|
||||
records,
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
returnDataParam,
|
||||
);
|
||||
const createdRows = returnDataParam ? (response as IDataObject[]) : [];
|
||||
createdRows.forEach((row, i) => {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(row),
|
||||
|
@ -172,6 +178,13 @@ export class Supabase implements INodeType {
|
|||
);
|
||||
returnData.push(...executionData);
|
||||
});
|
||||
if (!returnDataParam) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray({ success: true, message: 'Row inserted' }),
|
||||
{ itemData: mapPairedItemsFrom(records) },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
|
|
|
@ -15,7 +15,8 @@ import { Supabase } from '../Supabase.node';
|
|||
describe('Test Supabase Node', () => {
|
||||
const node = new Supabase();
|
||||
|
||||
const input = [{ json: {} }];
|
||||
// Input for create tests
|
||||
const input = [{ json: { email: 'test@example.com', phone: '+1234567890' } }];
|
||||
|
||||
const createMockExecuteFunction = (
|
||||
nodeParameters: IDataObject,
|
||||
|
@ -29,7 +30,6 @@ describe('Test Supabase Node', () => {
|
|||
options?: IGetNodeParameterOptions | undefined,
|
||||
) {
|
||||
const parameter = options?.extractValue ? `${parameterName}.value` : parameterName;
|
||||
|
||||
const parameterValue = get(nodeParameters, parameter, fallbackValue);
|
||||
|
||||
if ((parameterValue as IDataObject)?.nodeOperationError) {
|
||||
|
@ -45,10 +45,11 @@ describe('Test Supabase Node', () => {
|
|||
getInputData: () => input,
|
||||
helpers: {
|
||||
constructExecutionMetaData: (
|
||||
_inputData: INodeExecutionData[],
|
||||
inputData: INodeExecutionData[],
|
||||
_options: { itemData: IPairedItemData | IPairedItemData[] },
|
||||
) => [],
|
||||
returnJsonArray: (_jsonData: IDataObject | IDataObject[]) => [],
|
||||
) => inputData,
|
||||
returnJsonArray: (jsonData: IDataObject | IDataObject[]) =>
|
||||
Array.isArray(jsonData) ? jsonData.map((data) => ({ json: data })) : [{ json: jsonData }],
|
||||
},
|
||||
} as unknown as IExecuteFunctions;
|
||||
return fakeExecuteFunction;
|
||||
|
@ -96,4 +97,98 @@ describe('Test Supabase Node', () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('Create Operation', () => {
|
||||
it('should create a row and return data when returnData is true', async () => {
|
||||
const supabaseApiRequest = jest
|
||||
.spyOn(utils, 'supabaseApiRequest')
|
||||
.mockResolvedValue([{ email: 'test@example.com', phone: '+1234567890' }]);
|
||||
|
||||
const fakeExecuteFunction = createMockExecuteFunction({
|
||||
resource: 'row',
|
||||
operation: 'create',
|
||||
tableId: 'signups',
|
||||
dataToSend: 'autoMapInputData',
|
||||
inputsToIgnore: '',
|
||||
returnData: true,
|
||||
});
|
||||
|
||||
const result = await node.execute.call(fakeExecuteFunction);
|
||||
|
||||
expect(supabaseApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/signups',
|
||||
[{ email: 'test@example.com', phone: '+1234567890' }],
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
true,
|
||||
);
|
||||
expect(result[0][0].json).toEqual({
|
||||
email: 'test@example.com',
|
||||
phone: '+1234567890',
|
||||
});
|
||||
});
|
||||
|
||||
it('should create a row without returning data when returnData is false', async () => {
|
||||
const supabaseApiRequest = jest.spyOn(utils, 'supabaseApiRequest').mockResolvedValue({});
|
||||
|
||||
const fakeExecuteFunction = createMockExecuteFunction({
|
||||
resource: 'row',
|
||||
operation: 'create',
|
||||
tableId: 'signups',
|
||||
dataToSend: 'autoMapInputData',
|
||||
inputsToIgnore: '',
|
||||
returnData: false,
|
||||
});
|
||||
|
||||
const result = await node.execute.call(fakeExecuteFunction);
|
||||
|
||||
expect(supabaseApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/signups',
|
||||
[{ email: 'test@example.com', phone: '+1234567890' }],
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
false,
|
||||
);
|
||||
expect(result[0][0].json).toEqual({
|
||||
success: true,
|
||||
message: 'Row inserted',
|
||||
});
|
||||
});
|
||||
|
||||
it('should coerce non-boolean returnData to boolean and proceed', async () => {
|
||||
const supabaseApiRequest = jest
|
||||
.spyOn(utils, 'supabaseApiRequest')
|
||||
.mockResolvedValue([{ email: 'test@example.com', phone: '+1234567890' }]);
|
||||
|
||||
const fakeExecuteFunction = createMockExecuteFunction({
|
||||
resource: 'row',
|
||||
operation: 'create',
|
||||
tableId: 'signups',
|
||||
dataToSend: 'autoMapInputData',
|
||||
inputsToIgnore: '',
|
||||
returnData: 'hello', // Non-boolean expression result
|
||||
});
|
||||
|
||||
const result = await node.execute.call(fakeExecuteFunction);
|
||||
|
||||
// 'hello' is truthy, so expect returnData to be treated as true
|
||||
expect(supabaseApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/signups',
|
||||
[{ email: 'test@example.com', phone: '+1234567890' }],
|
||||
{},
|
||||
undefined,
|
||||
{},
|
||||
'hello', // Passed as-is, but coerced to true in JS
|
||||
);
|
||||
expect(result[0][0].json).toEqual({
|
||||
email: 'test@example.com',
|
||||
phone: '+1234567890',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue