fix(Slack Node): Make sure paginated calls use the defined limits (#14185)

This commit is contained in:
Jon
2025-03-28 15:51:56 +00:00
committed by GitHub
parent 9bd72eaa13
commit 24fad512da
2 changed files with 220 additions and 1 deletions

View File

@@ -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);

View File

@@ -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' }]);
});
});
});