mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-30 08:17:06 +02:00
fix(Pipedrive Node): Map user_id to owner_id for v2 deal and activity requests (#30890)
This commit is contained in:
parent
1c3901576b
commit
e9631b336f
|
|
@ -15,6 +15,7 @@ describe('Test PipedriveV2, activity => create', () => {
|
|||
deal_id: 8,
|
||||
person_id: 10,
|
||||
org_id: 7,
|
||||
owner_id: 25455458,
|
||||
due_date: '2026-04-01',
|
||||
})
|
||||
.reply(200, {
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
"deal_id": 8,
|
||||
"person_id": 10,
|
||||
"org_id": 7,
|
||||
"user_id": 25455458,
|
||||
"due_date": "2026-04-01T00:00:00.000Z"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ describe('Test PipedriveV2, activity => getAll', () => {
|
|||
|
||||
nock('https://api.pipedrive.com/api/v2')
|
||||
.get('/activities')
|
||||
.query({ limit: 2 })
|
||||
.query({ limit: 2, owner_id: 25455458 })
|
||||
.reply(200, {
|
||||
success: true,
|
||||
data: [
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
"operation": "getAll",
|
||||
"returnAll": false,
|
||||
"limit": 2,
|
||||
"filters": {}
|
||||
"filters": {
|
||||
"user_id": 25455458
|
||||
}
|
||||
},
|
||||
"id": "e5e5e5e5-f6f6-a7a7-b8b8-c9c9c9c9c9c9",
|
||||
"name": "Pipedrive",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ describe('Test PipedriveV2, activity => update', () => {
|
|||
.patch('/activities/10', {
|
||||
subject: 'Updated call',
|
||||
done: true,
|
||||
owner_id: 25455458,
|
||||
due_date: '2026-04-02',
|
||||
})
|
||||
.reply(200, {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"updateFields": {
|
||||
"subject": "Updated call",
|
||||
"done": true,
|
||||
"user_id": 25455458,
|
||||
"due_date": "2026-04-02T00:00:00.000Z"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ describe('Test PipedriveV2, deal => create', () => {
|
|||
value: 5000,
|
||||
currency: 'USD',
|
||||
status: 'open',
|
||||
owner_id: 25455458,
|
||||
})
|
||||
.reply(200, {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@
|
|||
"person_id": 10,
|
||||
"value": 5000,
|
||||
"currency": "USD",
|
||||
"status": "open"
|
||||
"status": "open",
|
||||
"user_id": 25455458
|
||||
}
|
||||
},
|
||||
"id": "bb222222-cc33-dd44-ee55-ff6666666666",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ describe('Test PipedriveV2, deal => getAll', () => {
|
|||
|
||||
nock('https://api.pipedrive.com/api/v2')
|
||||
.get('/deals')
|
||||
.query({ limit: 2 })
|
||||
.query({ limit: 2, owner_id: 25455458 })
|
||||
.reply(200, {
|
||||
success: true,
|
||||
data: [
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@
|
|||
"operation": "getAll",
|
||||
"returnAll": false,
|
||||
"limit": 2,
|
||||
"filters": {}
|
||||
"filters": {
|
||||
"user_id": 25455458
|
||||
}
|
||||
},
|
||||
"id": "b2b2b2b2-1111-2222-3333-444444444444",
|
||||
"name": "Pipedrive",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ describe('Test PipedriveV2, deal => update', () => {
|
|||
.patch('/deals/10', {
|
||||
title: 'Updated Deal',
|
||||
value: 7500,
|
||||
owner_id: 25455458,
|
||||
expected_close_date: '2026-04-13',
|
||||
})
|
||||
.reply(200, {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
"updateFields": {
|
||||
"title": "Updated Deal",
|
||||
"value": 7500,
|
||||
"user_id": 25455458,
|
||||
"expected_close_date": "2026-04-13T00:00:00.000Z"
|
||||
}
|
||||
},
|
||||
|
|
|
|||
184
packages/nodes-base/nodes/Pipedrive/test/v2/userIdRemap.test.ts
Normal file
184
packages/nodes-base/nodes/Pipedrive/test/v2/userIdRemap.test.ts
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
import type { IDataObject, IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import { execute as activityCreateExecute } from '../../v2/actions/activity/create.operation';
|
||||
import { execute as activityGetAllExecute } from '../../v2/actions/activity/getAll.operation';
|
||||
import { execute as activityUpdateExecute } from '../../v2/actions/activity/update.operation';
|
||||
import { execute as dealCreateExecute } from '../../v2/actions/deal/create.operation';
|
||||
import { execute as dealGetAllExecute } from '../../v2/actions/deal/getAll.operation';
|
||||
import { execute as dealUpdateExecute } from '../../v2/actions/deal/update.operation';
|
||||
import {
|
||||
pipedriveApiRequest,
|
||||
pipedriveApiRequestAllItemsCursor,
|
||||
pipedriveGetCustomProperties,
|
||||
} from '../../v2/transport';
|
||||
|
||||
jest.mock('../../v2/transport', () => ({
|
||||
pipedriveApiRequest: { call: jest.fn() },
|
||||
pipedriveApiRequestAllItemsCursor: { call: jest.fn() },
|
||||
pipedriveApiRequestAllItemsOffset: { call: jest.fn() },
|
||||
pipedriveGetCustomProperties: { call: jest.fn() },
|
||||
}));
|
||||
|
||||
const mockApiRequest = pipedriveApiRequest as unknown as { call: jest.Mock };
|
||||
const mockApiRequestAllItemsCursor = pipedriveApiRequestAllItemsCursor as unknown as {
|
||||
call: jest.Mock;
|
||||
};
|
||||
const mockGetCustomProperties = pipedriveGetCustomProperties as unknown as { call: jest.Mock };
|
||||
|
||||
function buildContext(params: Record<string, unknown>): IExecuteFunctions {
|
||||
return {
|
||||
getInputData: jest.fn(() => [{ json: {} }]),
|
||||
getNodeParameter: jest.fn((name: string, _i?: number, defaultValue?: unknown) => {
|
||||
if (Object.prototype.hasOwnProperty.call(params, name)) return params[name];
|
||||
return defaultValue;
|
||||
}),
|
||||
continueOnFail: jest.fn(() => false),
|
||||
helpers: {
|
||||
returnJsonArray: jest.fn((data: unknown) => (Array.isArray(data) ? data : [data])),
|
||||
constructExecutionMetaData: jest.fn((items: unknown) => items),
|
||||
},
|
||||
getNode: jest.fn(() => ({})),
|
||||
} as unknown as IExecuteFunctions;
|
||||
}
|
||||
|
||||
describe('Pipedrive v2 maps user_id parameter to owner_id on outgoing requests', () => {
|
||||
beforeEach(() => {
|
||||
mockApiRequest.call.mockReset().mockResolvedValue({ data: {}, additionalData: {} });
|
||||
mockApiRequestAllItemsCursor.call.mockReset().mockResolvedValue({ data: [] });
|
||||
mockGetCustomProperties.call.mockReset().mockResolvedValue({});
|
||||
});
|
||||
|
||||
it('deal/create sends owner_id in body', async () => {
|
||||
const ctx = buildContext({
|
||||
rawCustomFieldKeys: true,
|
||||
title: 'Test Deal',
|
||||
associateWith: 'organization',
|
||||
org_id: 7,
|
||||
additionalFields: { user_id: 25455458 },
|
||||
});
|
||||
|
||||
await dealCreateExecute.call(ctx);
|
||||
|
||||
const [, method, endpoint, body] = mockApiRequest.call.mock.calls[0] as [
|
||||
unknown,
|
||||
string,
|
||||
string,
|
||||
IDataObject,
|
||||
];
|
||||
expect(method).toBe('POST');
|
||||
expect(endpoint).toBe('/deals');
|
||||
expect(body.owner_id).toBe(25455458);
|
||||
expect(body).not.toHaveProperty('user_id');
|
||||
});
|
||||
|
||||
it('deal/update sends owner_id in body', async () => {
|
||||
const ctx = buildContext({
|
||||
rawCustomFieldKeys: true,
|
||||
dealId: 10,
|
||||
updateFields: { user_id: 25455458 },
|
||||
});
|
||||
|
||||
await dealUpdateExecute.call(ctx);
|
||||
|
||||
const [, method, endpoint, body] = mockApiRequest.call.mock.calls[0] as [
|
||||
unknown,
|
||||
string,
|
||||
string,
|
||||
IDataObject,
|
||||
];
|
||||
expect(method).toBe('PATCH');
|
||||
expect(endpoint).toBe('/deals/10');
|
||||
expect(body.owner_id).toBe(25455458);
|
||||
expect(body).not.toHaveProperty('user_id');
|
||||
});
|
||||
|
||||
it('deal/getAll sends owner_id as query filter', async () => {
|
||||
const ctx = buildContext({
|
||||
rawCustomFieldOutput: true,
|
||||
returnAll: false,
|
||||
limit: 50,
|
||||
filters: { user_id: 25455458 },
|
||||
});
|
||||
|
||||
await dealGetAllExecute.call(ctx);
|
||||
|
||||
const [, method, endpoint, , qs] = mockApiRequest.call.mock.calls[0] as [
|
||||
unknown,
|
||||
string,
|
||||
string,
|
||||
IDataObject,
|
||||
IDataObject,
|
||||
];
|
||||
expect(method).toBe('GET');
|
||||
expect(endpoint).toBe('/deals');
|
||||
expect(qs.owner_id).toBe(25455458);
|
||||
expect(qs).not.toHaveProperty('user_id');
|
||||
});
|
||||
|
||||
it('activity/create sends owner_id in body', async () => {
|
||||
const ctx = buildContext({
|
||||
rawCustomFieldKeys: true,
|
||||
subject: 'Call client',
|
||||
done: false,
|
||||
type: 'call',
|
||||
additionalFields: { user_id: 25455458 },
|
||||
});
|
||||
|
||||
await activityCreateExecute.call(ctx);
|
||||
|
||||
const [, method, endpoint, body] = mockApiRequest.call.mock.calls[0] as [
|
||||
unknown,
|
||||
string,
|
||||
string,
|
||||
IDataObject,
|
||||
];
|
||||
expect(method).toBe('POST');
|
||||
expect(endpoint).toBe('/activities');
|
||||
expect(body.owner_id).toBe(25455458);
|
||||
expect(body).not.toHaveProperty('user_id');
|
||||
});
|
||||
|
||||
it('activity/update sends owner_id in body', async () => {
|
||||
const ctx = buildContext({
|
||||
rawCustomFieldKeys: true,
|
||||
activityId: 10,
|
||||
updateFields: { user_id: 25455458 },
|
||||
});
|
||||
|
||||
await activityUpdateExecute.call(ctx);
|
||||
|
||||
const [, method, endpoint, body] = mockApiRequest.call.mock.calls[0] as [
|
||||
unknown,
|
||||
string,
|
||||
string,
|
||||
IDataObject,
|
||||
];
|
||||
expect(method).toBe('PATCH');
|
||||
expect(endpoint).toBe('/activities/10');
|
||||
expect(body.owner_id).toBe(25455458);
|
||||
expect(body).not.toHaveProperty('user_id');
|
||||
});
|
||||
|
||||
it('activity/getAll sends owner_id as query filter', async () => {
|
||||
const ctx = buildContext({
|
||||
rawCustomFieldOutput: true,
|
||||
returnAll: false,
|
||||
limit: 50,
|
||||
filters: { user_id: 25455458 },
|
||||
});
|
||||
|
||||
await activityGetAllExecute.call(ctx);
|
||||
|
||||
const [, method, endpoint, , qs] = mockApiRequest.call.mock.calls[0] as [
|
||||
unknown,
|
||||
string,
|
||||
string,
|
||||
IDataObject,
|
||||
IDataObject,
|
||||
];
|
||||
expect(method).toBe('GET');
|
||||
expect(endpoint).toBe('/activities');
|
||||
expect(qs.owner_id).toBe(25455458);
|
||||
expect(qs).not.toHaveProperty('user_id');
|
||||
});
|
||||
});
|
||||
|
|
@ -137,6 +137,13 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
|||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
addFieldsToBody(body, additionalFields);
|
||||
|
||||
// Pipedrive v2 activities API renamed `user_id` to `owner_id`; remap so the existing
|
||||
// `user_id` parameter (kept for backward compatibility with saved workflows) is accepted.
|
||||
if (body.user_id !== undefined) {
|
||||
body.owner_id = body.user_id;
|
||||
delete body.user_id;
|
||||
}
|
||||
|
||||
if (body.due_date) {
|
||||
body.due_date = toDateOnly(body.due_date as string);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,8 +152,10 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
|||
qs.type = (filters.type as string[]).join(',');
|
||||
}
|
||||
|
||||
// Pipedrive v2 activities filter param was renamed from `user_id` to `owner_id`;
|
||||
// keep the UI field key as `user_id` for backward compatibility with saved workflows.
|
||||
if (filters.user_id) {
|
||||
qs.user_id = filters.user_id;
|
||||
qs.owner_id = filters.user_id;
|
||||
}
|
||||
|
||||
if (filters.done !== undefined) {
|
||||
|
|
|
|||
|
|
@ -155,6 +155,13 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
|||
const updateFields = this.getNodeParameter('updateFields', i);
|
||||
addFieldsToBody(body, updateFields);
|
||||
|
||||
// Pipedrive v2 activities API renamed `user_id` to `owner_id`; remap so the existing
|
||||
// `user_id` parameter (kept for backward compatibility with saved workflows) is accepted.
|
||||
if (body.user_id !== undefined) {
|
||||
body.owner_id = body.user_id;
|
||||
delete body.user_id;
|
||||
}
|
||||
|
||||
// Coerce done to boolean for v2 API
|
||||
if (body.done !== undefined) {
|
||||
body.done = coerceToBoolean(body.done);
|
||||
|
|
|
|||
|
|
@ -246,6 +246,13 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
|||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
addFieldsToBody(body, additionalFields);
|
||||
|
||||
// Pipedrive v2 deals API renamed `user_id` to `owner_id`; remap so the existing
|
||||
// `user_id` parameter (kept for backward compatibility with saved workflows) is accepted.
|
||||
if (body.user_id !== undefined) {
|
||||
body.owner_id = body.user_id;
|
||||
delete body.user_id;
|
||||
}
|
||||
|
||||
if (body.expected_close_date) {
|
||||
body.expected_close_date = toDateOnly(body.expected_close_date as string);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,8 +154,10 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
|||
qs.status = filters.status;
|
||||
}
|
||||
|
||||
// Pipedrive v2 deals filter param was renamed from `user_id` to `owner_id`;
|
||||
// keep the UI field key as `user_id` for backward compatibility with saved workflows.
|
||||
if (filters.user_id) {
|
||||
qs.user_id = filters.user_id;
|
||||
qs.owner_id = filters.user_id;
|
||||
}
|
||||
|
||||
let responseData;
|
||||
|
|
|
|||
|
|
@ -194,6 +194,13 @@ export async function execute(this: IExecuteFunctions): Promise<INodeExecutionDa
|
|||
const updateFields = this.getNodeParameter('updateFields', i);
|
||||
addFieldsToBody(body, updateFields);
|
||||
|
||||
// Pipedrive v2 deals API renamed `user_id` to `owner_id`; remap so the existing
|
||||
// `user_id` parameter (kept for backward compatibility with saved workflows) is accepted.
|
||||
if (body.user_id !== undefined) {
|
||||
body.owner_id = body.user_id;
|
||||
delete body.user_id;
|
||||
}
|
||||
|
||||
// Clear label when set to 'null' string
|
||||
if (body.label === 'null') {
|
||||
body.label = null;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user