Files
alighasami 3d5eaf9445
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
first commit
2026-03-17 16:22:57 +03:30

405 lines
11 KiB
TypeScript

import { mock } from 'jest-mock-extended';
import type { IExecuteFunctions, INode } from 'n8n-workflow';
import type { Context } from '../GenericFunctions';
import { todoistApiGetAllRequest } from '../GenericFunctions';
const createMockContext = (typeVersion: number = 2.2) => {
const mockRequestWithAuth = jest.fn();
const mockCtx = mock<IExecuteFunctions>({
getNode: () => mock<INode>({ typeVersion }),
getNodeParameter: jest.fn((param: string) => {
if (param === 'authentication') return 'oAuth2';
return '';
}) as any,
helpers: {
requestWithAuthentication: {
call: mockRequestWithAuth,
} as any,
} as any,
});
return {
ctx: mockCtx as unknown as Context,
mockRequestWithAuth,
};
};
describe('GenericFunctions', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('todoistApiGetAllRequest', () => {
describe('Legacy mode (node version < 2.2)', () => {
it('should fetch all items without pagination for version 2.0', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.0);
const resource = '/tasks';
const qs = { project_id: '123' };
const mockResponse = [
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
{ id: '3', content: 'Task 3' },
];
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await todoistApiGetAllRequest(ctx, resource, qs);
expect(mockRequestWithAuth).toHaveBeenCalled();
expect(result).toEqual(mockResponse);
});
it('should fetch all items without pagination for version 2.1', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.1);
const resource = '/tasks';
const qs = { project_id: '123' };
const mockResponse = [
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
{ id: '3', content: 'Task 3' },
];
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await todoistApiGetAllRequest(ctx, resource, qs);
expect(mockRequestWithAuth).toHaveBeenCalled();
expect(result).toEqual(mockResponse);
});
it('should apply limit when specified for old node versions', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.1);
const resource = '/tasks';
const qs = { project_id: '123' };
const limit = 2;
const mockResponse = [
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
{ id: '3', content: 'Task 3' },
];
mockRequestWithAuth.mockResolvedValue([...mockResponse]);
const result = await todoistApiGetAllRequest(ctx, resource, qs, limit);
expect(mockRequestWithAuth).toHaveBeenCalled();
expect(result).toEqual([
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
]);
});
it('should handle limit greater than available items', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.1);
const resource = '/tasks';
const limit = 10;
const mockResponse = [
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
];
mockRequestWithAuth.mockResolvedValue([...mockResponse]);
const result = await todoistApiGetAllRequest(ctx, resource, {}, limit);
expect(mockRequestWithAuth).toHaveBeenCalled();
expect(result).toEqual(mockResponse);
});
it('should handle empty response for old node versions', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.1);
const resource = '/tasks';
mockRequestWithAuth.mockResolvedValue([]);
const result = await todoistApiGetAllRequest(ctx, resource);
expect(mockRequestWithAuth).toHaveBeenCalled();
expect(result).toEqual([]);
});
it('should handle query parameters correctly', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.0);
const resource = '/tasks';
const qs = { project_id: '456', filter: 'today' };
const mockResponse = [{ id: '1', content: 'Task 1' }];
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await todoistApiGetAllRequest(ctx, resource, qs);
expect(mockRequestWithAuth).toHaveBeenCalled();
expect(result).toEqual(mockResponse);
});
it('should work without query parameters', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.1);
const resource = '/projects';
const mockResponse = [
{ id: '1', name: 'Project 1' },
{ id: '2', name: 'Project 2' },
];
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await todoistApiGetAllRequest(ctx, resource);
expect(mockRequestWithAuth).toHaveBeenCalled();
expect(result).toEqual(mockResponse);
});
});
describe('Pagination mode (node version >= 2.2)', () => {
it('should fetch all items with pagination for version 2.2', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const qs = { project_id: '123' };
const mockResponsePage1 = {
results: [
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
],
next_cursor: 'cursor-123',
};
const mockResponsePage2 = {
results: [
{ id: '3', content: 'Task 3' },
{ id: '4', content: 'Task 4' },
],
next_cursor: null,
};
mockRequestWithAuth
.mockResolvedValueOnce(mockResponsePage1)
.mockResolvedValueOnce(mockResponsePage2);
const result = await todoistApiGetAllRequest(ctx, resource, qs);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(2);
expect(result).toEqual([
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
{ id: '3', content: 'Task 3' },
{ id: '4', content: 'Task 4' },
]);
});
it('should fetch all items with pagination for version greater then 2.2', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.3);
const resource = '/projects';
const mockResponsePage1 = {
results: [{ id: '1', name: 'Project 1' }],
next_cursor: 'next-page',
};
const mockResponsePage2 = {
results: [{ id: '2', name: 'Project 2' }],
next_cursor: null,
};
mockRequestWithAuth
.mockResolvedValueOnce(mockResponsePage1)
.mockResolvedValueOnce(mockResponsePage2);
const result = await todoistApiGetAllRequest(ctx, resource);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(2);
expect(result).toHaveLength(2);
});
it('should respect limit and stop fetching when limit is reached', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const qs = { project_id: '123' };
const limit = 3;
const mockResponsePage1 = {
results: [
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
],
next_cursor: 'cursor-123',
};
const mockResponsePage2 = {
results: [
{ id: '3', content: 'Task 3' },
{ id: '4', content: 'Task 4' },
],
next_cursor: 'cursor-456',
};
mockRequestWithAuth
.mockResolvedValueOnce(mockResponsePage1)
.mockResolvedValueOnce(mockResponsePage2);
const result = await todoistApiGetAllRequest(ctx, resource, qs, limit);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(2);
expect(result).toEqual([
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
{ id: '3', content: 'Task 3' },
]);
});
it('should handle single page response with no next cursor', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const mockResponse = {
results: [
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
],
next_cursor: null,
};
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await todoistApiGetAllRequest(ctx, resource);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
expect(result).toEqual([
{ id: '1', content: 'Task 1' },
{ id: '2', content: 'Task 2' },
]);
});
it('should handle empty results array', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const mockResponse = {
results: [],
next_cursor: null,
};
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await todoistApiGetAllRequest(ctx, resource);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
expect(result).toEqual([]);
});
it('should handle response without results property', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const mockResponse = {
next_cursor: null,
};
mockRequestWithAuth.mockResolvedValue(mockResponse);
const result = await todoistApiGetAllRequest(ctx, resource);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
expect(result).toEqual([]);
});
it('should respect limit of 200 items per request', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const limit = 250;
const mockResponsePage1 = {
results: Array.from({ length: 200 }, (_, i) => ({ id: String(i + 1) })),
next_cursor: 'cursor-123',
};
const mockResponsePage2 = {
results: Array.from({ length: 50 }, (_, i) => ({ id: String(i + 201) })),
next_cursor: null,
};
mockRequestWithAuth
.mockResolvedValueOnce(mockResponsePage1)
.mockResolvedValueOnce(mockResponsePage2);
const result = await todoistApiGetAllRequest(ctx, resource, {}, limit);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(2);
expect(result).toHaveLength(250);
});
it('should handle multiple pages with different result sizes', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const mockResponsePage1 = {
results: [{ id: '1' }, { id: '2' }],
next_cursor: 'cursor-1',
};
const mockResponsePage2 = {
results: [{ id: '3' }],
next_cursor: 'cursor-2',
};
const mockResponsePage3 = {
results: [{ id: '4' }, { id: '5' }, { id: '6' }],
next_cursor: null,
};
mockRequestWithAuth
.mockResolvedValueOnce(mockResponsePage1)
.mockResolvedValueOnce(mockResponsePage2)
.mockResolvedValueOnce(mockResponsePage3);
const result = await todoistApiGetAllRequest(ctx, resource);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(3);
expect(result).toEqual([
{ id: '1' },
{ id: '2' },
{ id: '3' },
{ id: '4' },
{ id: '5' },
{ id: '6' },
]);
});
it('should stop pagination when limit is exactly reached', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const limit = 2;
const mockResponsePage1 = {
results: [{ id: '1' }, { id: '2' }],
next_cursor: 'cursor-123',
};
mockRequestWithAuth.mockResolvedValueOnce(mockResponsePage1);
const result = await todoistApiGetAllRequest(ctx, resource, {}, limit);
expect(mockRequestWithAuth).toHaveBeenCalledTimes(1);
expect(result).toHaveLength(2);
});
it('should handle limit of 0', async () => {
const { ctx, mockRequestWithAuth } = createMockContext(2.2);
const resource = '/tasks';
const limit = 0;
const result = await todoistApiGetAllRequest(ctx, resource, {}, limit);
expect(mockRequestWithAuth).not.toHaveBeenCalled();
expect(result).toEqual([]);
});
});
});
});