diff --git a/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts b/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts index f17af1e88e1..f9f5d2bbd0c 100644 --- a/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Slack/V2/GenericFunctions.ts @@ -124,7 +124,7 @@ export async function slackApiRequestAllItems( if (endpoint.includes('files.list')) { query.count = 100; } else { - query.limit = 100; + query.limit = query.limit ?? 100; } do { responseData = await slackApiRequest.call(this, method, endpoint, body as IDataObject, query); diff --git a/packages/nodes-base/nodes/Slack/test/v2/GenericFunctions.test.ts b/packages/nodes-base/nodes/Slack/test/v2/GenericFunctions.test.ts new file mode 100644 index 00000000000..8d52fdd9276 --- /dev/null +++ b/packages/nodes-base/nodes/Slack/test/v2/GenericFunctions.test.ts @@ -0,0 +1,219 @@ +import type { IExecuteFunctions } from 'n8n-workflow'; + +import { slackApiRequest, slackApiRequestAllItems } from '../../V2/GenericFunctions'; + +jest.mock('n8n-workflow', () => ({ + ...jest.requireActual('n8n-workflow'), + NodeApiError: jest.fn(), +})); + +describe('Slack V2 > GenericFunctions', () => { + let mockExecuteFunctions: IExecuteFunctions; + + beforeEach(() => { + jest.clearAllMocks(); + mockExecuteFunctions = { + helpers: { + requestWithAuthentication: jest.fn(), + }, + getNode: jest.fn().mockReturnValue({ type: 'n8n-nodes-base.slack', typeVersion: 2 }), + getNodeParameter: jest.fn().mockReturnValue('accessToken'), + } as unknown as IExecuteFunctions; + }); + + describe('slackApiRequest', () => { + it('should handle paid_teams_only error', async () => { + const mockResponse = { ok: false, error: 'paid_teams_only' }; + mockExecuteFunctions.helpers.requestWithAuthentication = jest + .fn() + .mockResolvedValue(mockResponse); + + await expect(slackApiRequest.call(mockExecuteFunctions, 'GET', '/test')).rejects.toThrow(); + }); + }); + + describe('slackApiRequestAllItems', () => { + it('should use default limit of 100 when no limit is provided', async () => { + const mockResponse = { + ok: true, + channels: [{ id: 'ch1' }], + response_metadata: { next_cursor: undefined }, + }; + + mockExecuteFunctions.helpers.requestWithAuthentication = jest + .fn() + .mockResolvedValue(mockResponse); + + await slackApiRequestAllItems.bind(mockExecuteFunctions)( + 'channels', + 'GET', + 'conversations.list', + ); + + expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith( + 'slackApi', + expect.objectContaining({ + headers: expect.any(Object), + qs: { + limit: 100, + page: 2, + cursor: undefined, + }, + json: true, + method: 'GET', + uri: expect.any(String), + }), + expect.any(Object), + ); + }); + + it('should use provided limit value when specified', async () => { + const mockResponse = { + ok: true, + channels: [{ id: 'ch1' }], + response_metadata: { next_cursor: undefined }, + }; + + mockExecuteFunctions.helpers.requestWithAuthentication = jest + .fn() + .mockResolvedValue(mockResponse); + + await slackApiRequestAllItems.call( + mockExecuteFunctions, + 'channels', + 'GET', + 'conversations.list', + {}, + { limit: 50 }, + ); + + expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith( + 'slackApi', + expect.objectContaining({ + headers: expect.any(Object), + qs: { + limit: 50, + page: 2, + cursor: undefined, + }, + json: true, + method: 'GET', + uri: expect.any(String), + }), + expect.any(Object), + ); + }); + + it('should use count instead of limit for files.list endpoint', async () => { + const mockResponse = { + ok: true, + files: [{ id: 'file1' }], + response_metadata: { next_cursor: undefined }, + }; + + mockExecuteFunctions.helpers.requestWithAuthentication = jest + .fn() + .mockResolvedValue(mockResponse); + + await slackApiRequestAllItems.call(mockExecuteFunctions, 'files', 'GET', 'files.list'); + + expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledWith( + 'slackApi', + expect.objectContaining({ + headers: expect.any(Object), + qs: { + count: 100, + page: 2, + cursor: undefined, + }, + json: true, + method: 'GET', + uri: expect.any(String), + }), + expect.any(Object), + ); + }); + + it('should handle files.list pagination', async () => { + const responses = [ + { + ok: true, + files: [{ id: '1' }], + response_metadata: { next_cursor: 'cursor1' }, + }, + { + ok: true, + files: [{ id: '2' }], + response_metadata: { next_cursor: '' }, + }, + ]; + + mockExecuteFunctions.helpers.requestWithAuthentication = jest + .fn() + .mockImplementationOnce(() => responses[0]) + .mockImplementationOnce(() => responses[1]); + + const result = await slackApiRequestAllItems.call( + mockExecuteFunctions, + 'files', + 'GET', + 'files.list', + ); + + expect(result).toEqual([{ id: '1' }, { id: '2' }]); + expect(mockExecuteFunctions.helpers.requestWithAuthentication).toHaveBeenCalledTimes(2); + }); + + it('should handle regular pagination with paging info', async () => { + const responses = [ + { + ok: true, + channels: [{ id: 'ch1' }], + paging: { pages: 2, page: 1 }, + }, + { + ok: true, + channels: [{ id: 'ch2' }], + paging: { pages: 2, page: 2 }, + }, + ]; + + mockExecuteFunctions.helpers.requestWithAuthentication = jest + .fn() + .mockImplementationOnce(() => responses[0]) + .mockImplementationOnce(() => responses[1]); + + const result = await slackApiRequestAllItems.call( + mockExecuteFunctions, + 'channels', + 'GET', + 'conversations.list', + ); + + expect(result).toEqual([{ id: 'ch1' }, { id: 'ch2' }]); + }); + + it('should handle matches in response', async () => { + const response = { + ok: true, + messages: { + matches: [{ text: 'test' }], + }, + response_metadata: { next_cursor: '' }, + }; + + mockExecuteFunctions.helpers.requestWithAuthentication = jest + .fn() + .mockResolvedValue(response); + + const result = await slackApiRequestAllItems.call( + mockExecuteFunctions, + 'messages', + 'GET', + 'search.messages', + ); + + expect(result).toEqual([{ text: 'test' }]); + }); + }); +});