mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
1181 lines
30 KiB
TypeScript
1181 lines
30 KiB
TypeScript
import { mock } from 'jest-mock-extended';
|
|
import type { IExecuteFunctions, INode } from 'n8n-workflow';
|
|
|
|
import { todoistApiRequest, todoistSyncRequest } from '../../GenericFunctions';
|
|
import {
|
|
CreateHandler,
|
|
CloseHandler,
|
|
DeleteHandler,
|
|
GetHandler,
|
|
GetAllHandler,
|
|
ReopenHandler,
|
|
UpdateHandler,
|
|
MoveHandler,
|
|
ProjectCreateHandler,
|
|
ProjectDeleteHandler,
|
|
ProjectGetHandler,
|
|
ProjectGetAllHandler,
|
|
ProjectUpdateHandler,
|
|
ProjectArchiveHandler,
|
|
ProjectUnarchiveHandler,
|
|
ProjectGetCollaboratorsHandler,
|
|
SectionCreateHandler,
|
|
SectionDeleteHandler,
|
|
SectionGetHandler,
|
|
SectionGetAllHandler,
|
|
SectionUpdateHandler,
|
|
CommentCreateHandler,
|
|
CommentDeleteHandler,
|
|
CommentGetHandler,
|
|
CommentGetAllHandler,
|
|
CommentUpdateHandler,
|
|
LabelCreateHandler,
|
|
LabelDeleteHandler,
|
|
LabelGetHandler,
|
|
LabelGetAllHandler,
|
|
LabelUpdateHandler,
|
|
QuickAddHandler,
|
|
} from '../OperationHandler';
|
|
|
|
// Mock the GenericFunctions
|
|
jest.mock('../../GenericFunctions', () => ({
|
|
todoistApiRequest: jest.fn(),
|
|
todoistSyncRequest: jest.fn(),
|
|
FormatDueDatetime: jest.fn((dateTime: string) => dateTime),
|
|
}));
|
|
|
|
// Mock uuid
|
|
jest.mock('uuid', () => ({
|
|
v4: jest.fn(() => 'mock-uuid-123'),
|
|
}));
|
|
|
|
const mockTodoistApiRequest = todoistApiRequest as jest.MockedFunction<typeof todoistApiRequest>;
|
|
const mockTodoistSyncRequest = todoistSyncRequest as jest.MockedFunction<typeof todoistSyncRequest>;
|
|
|
|
// Mock Context interface
|
|
const createMockContext = (params: Record<string, any> = {}) =>
|
|
mock<IExecuteFunctions>({
|
|
getNodeParameter: jest.fn((key: string, _idx?: number, defaultValue?: any) =>
|
|
key in params ? params[key] : defaultValue,
|
|
),
|
|
getNode: jest.fn(() => mock<INode>({ typeVersion: 2.1 })),
|
|
});
|
|
|
|
describe('OperationHandler', () => {
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
});
|
|
|
|
describe('Task Handlers', () => {
|
|
describe('CreateHandler', () => {
|
|
it('should create a task successfully', async () => {
|
|
const handler = new CreateHandler();
|
|
const mockCtx = createMockContext({
|
|
content: 'Test task',
|
|
project: '123456',
|
|
labels: ['work', 'urgent'],
|
|
options: {
|
|
description: 'Test description',
|
|
priority: 3,
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '789',
|
|
content: 'Test task',
|
|
project_id: '123456',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/tasks',
|
|
expect.objectContaining({
|
|
content: 'Test task',
|
|
project_id: '123456',
|
|
description: 'Test description',
|
|
priority: 3,
|
|
labels: ['work', 'urgent'],
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
|
|
it('should handle task creation with due date', async () => {
|
|
const handler = new CreateHandler();
|
|
const mockCtx = createMockContext({
|
|
content: 'Task with due date',
|
|
project: '123456',
|
|
options: {
|
|
dueDate: '2025-12-31',
|
|
dueDateTime: '2025-12-31T15:30:00',
|
|
dueString: 'tomorrow',
|
|
dueLang: 'en',
|
|
},
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue({ id: '789' });
|
|
|
|
await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/tasks',
|
|
expect.objectContaining({
|
|
content: 'Task with due date',
|
|
project_id: '123456',
|
|
due_date: '2025-12-31',
|
|
due_datetime: '2025-12-31T15:30:00',
|
|
due_string: 'tomorrow',
|
|
due_lang: 'en',
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('CloseHandler', () => {
|
|
it('should close a task successfully', async () => {
|
|
const handler = new CloseHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('POST', '/tasks/123456/close');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('DeleteHandler', () => {
|
|
it('should delete a task successfully', async () => {
|
|
const handler = new DeleteHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('DELETE', '/tasks/123456');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('GetHandler', () => {
|
|
it('should get a task successfully', async () => {
|
|
const handler = new GetHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
content: 'Test task',
|
|
project_id: '789',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/tasks/123456');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('GetAllHandler', () => {
|
|
it('should get all tasks successfully', async () => {
|
|
const handler = new GetAllHandler();
|
|
const mockCtx = createMockContext({
|
|
returnAll: false,
|
|
limit: 10,
|
|
filters: {
|
|
projectId: '123456',
|
|
labelId: '789',
|
|
},
|
|
});
|
|
|
|
const mockApiResponse = [
|
|
{ id: '1', content: 'Task 1' },
|
|
{ id: '2', content: 'Task 2' },
|
|
];
|
|
|
|
mockTodoistApiRequest.mockResolvedValue([...mockApiResponse]);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'GET',
|
|
'/tasks',
|
|
{},
|
|
expect.objectContaining({
|
|
project_id: '123456',
|
|
label: '789',
|
|
}),
|
|
);
|
|
// splice(0, 10) on a 2-element array removes and returns all 2 elements
|
|
expect(result).toEqual({ data: mockApiResponse });
|
|
});
|
|
|
|
it('should return all tasks when returnAll is true', async () => {
|
|
const handler = new GetAllHandler();
|
|
const mockCtx = createMockContext({
|
|
returnAll: true,
|
|
filters: {},
|
|
});
|
|
|
|
const expectedResponse = Array.from({ length: 150 }, (_, i) => ({ id: i.toString() }));
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('ReopenHandler', () => {
|
|
it('should reopen a task successfully', async () => {
|
|
const handler = new ReopenHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('POST', '/tasks/123456/reopen');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('UpdateHandler', () => {
|
|
it('should update a task successfully', async () => {
|
|
const handler = new UpdateHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
updateFields: {
|
|
content: 'Updated task',
|
|
description: 'Updated description',
|
|
priority: 2,
|
|
labels: ['updated'],
|
|
},
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/tasks/123456',
|
|
expect.objectContaining({
|
|
content: 'Updated task',
|
|
description: 'Updated description',
|
|
priority: 2,
|
|
labels: ['updated'],
|
|
}),
|
|
);
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('MoveHandler', () => {
|
|
it('should move a task successfully', async () => {
|
|
const handler = new MoveHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
project: '789',
|
|
options: {},
|
|
});
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
commands: [
|
|
expect.objectContaining({
|
|
type: 'item_move',
|
|
uuid: 'mock-uuid-123',
|
|
args: expect.objectContaining({
|
|
id: '123456',
|
|
project_id: '789',
|
|
}),
|
|
}),
|
|
],
|
|
}),
|
|
);
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
|
|
it('should move a task to a section', async () => {
|
|
const handler = new MoveHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
project: '789',
|
|
options: {
|
|
section: '456',
|
|
},
|
|
});
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(undefined);
|
|
|
|
await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
commands: [
|
|
expect.objectContaining({
|
|
args: expect.objectContaining({
|
|
id: '123456',
|
|
section_id: '456',
|
|
}),
|
|
}),
|
|
],
|
|
}),
|
|
);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Project Handlers', () => {
|
|
describe('ProjectCreateHandler', () => {
|
|
it('should create a project successfully', async () => {
|
|
const handler = new ProjectCreateHandler();
|
|
const mockCtx = createMockContext({
|
|
name: 'Test Project',
|
|
projectOptions: {
|
|
color: 'blue',
|
|
is_favorite: true,
|
|
view_style: 'board',
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
name: 'Test Project',
|
|
color: 'blue',
|
|
is_favorite: true,
|
|
view_style: 'board',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/projects',
|
|
expect.objectContaining({
|
|
name: 'Test Project',
|
|
color: 'blue',
|
|
is_favorite: true,
|
|
view_style: 'board',
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('ProjectGetHandler', () => {
|
|
it('should get a project successfully', async () => {
|
|
const handler = new ProjectGetHandler();
|
|
const mockCtx = createMockContext({
|
|
projectId: '123456',
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
name: 'Test Project',
|
|
color: 'blue',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/projects/123456');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('ProjectGetAllHandler', () => {
|
|
it('should get all projects successfully', async () => {
|
|
const handler = new ProjectGetAllHandler();
|
|
const mockCtx = createMockContext({});
|
|
|
|
const expectedResponse = [
|
|
{ id: '1', name: 'Project 1' },
|
|
{ id: '2', name: 'Project 2' },
|
|
];
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/projects');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('ProjectUpdateHandler', () => {
|
|
it('should update a project successfully', async () => {
|
|
const handler = new ProjectUpdateHandler();
|
|
const mockCtx = createMockContext({
|
|
projectId: '123456',
|
|
projectUpdateFields: {
|
|
name: 'Updated Project',
|
|
color: 'red',
|
|
is_favorite: false,
|
|
},
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/projects/123456',
|
|
expect.objectContaining({
|
|
name: 'Updated Project',
|
|
color: 'red',
|
|
is_favorite: false,
|
|
}),
|
|
);
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('ProjectDeleteHandler', () => {
|
|
it('should delete a project successfully', async () => {
|
|
const handler = new ProjectDeleteHandler();
|
|
const mockCtx = createMockContext({
|
|
projectId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('DELETE', '/projects/123456');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('ProjectArchiveHandler', () => {
|
|
it('should archive a project successfully', async () => {
|
|
const handler = new ProjectArchiveHandler();
|
|
const mockCtx = createMockContext({
|
|
projectId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('POST', '/projects/123456/archive');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('ProjectUnarchiveHandler', () => {
|
|
it('should unarchive a project successfully', async () => {
|
|
const handler = new ProjectUnarchiveHandler();
|
|
const mockCtx = createMockContext({
|
|
projectId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('POST', '/projects/123456/unarchive');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('ProjectGetCollaboratorsHandler', () => {
|
|
it('should get project collaborators successfully', async () => {
|
|
const handler = new ProjectGetCollaboratorsHandler();
|
|
const mockCtx = createMockContext({
|
|
projectId: '123456',
|
|
});
|
|
|
|
const expectedResponse = [
|
|
{ id: '1', name: 'User 1', email: 'user1@example.com' },
|
|
{ id: '2', name: 'User 2', email: 'user2@example.com' },
|
|
];
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/projects/123456/collaborators');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Section Handlers', () => {
|
|
describe('SectionCreateHandler', () => {
|
|
it('should create a section successfully', async () => {
|
|
const handler = new SectionCreateHandler();
|
|
const mockCtx = createMockContext({
|
|
sectionName: 'Test Section',
|
|
sectionProject: '123456',
|
|
sectionOptions: {
|
|
order: 1,
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '789',
|
|
name: 'Test Section',
|
|
project_id: '123456',
|
|
order: 1,
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/sections',
|
|
expect.objectContaining({
|
|
name: 'Test Section',
|
|
project_id: '123456',
|
|
order: 1,
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('SectionGetHandler', () => {
|
|
it('should get a section successfully', async () => {
|
|
const handler = new SectionGetHandler();
|
|
const mockCtx = createMockContext({
|
|
sectionId: '123456',
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
name: 'Test Section',
|
|
project_id: '789',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/sections/123456');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('SectionGetAllHandler', () => {
|
|
it('should get all sections successfully', async () => {
|
|
const handler = new SectionGetAllHandler();
|
|
const mockCtx = createMockContext({
|
|
sectionFilters: {
|
|
project_id: '123456',
|
|
},
|
|
});
|
|
|
|
const expectedResponse = [
|
|
{ id: '1', name: 'Section 1', project_id: '123456' },
|
|
{ id: '2', name: 'Section 2', project_id: '123456' },
|
|
];
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'GET',
|
|
'/sections',
|
|
{},
|
|
expect.objectContaining({
|
|
project_id: '123456',
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('SectionUpdateHandler', () => {
|
|
it('should update a section successfully', async () => {
|
|
const handler = new SectionUpdateHandler();
|
|
const mockCtx = createMockContext({
|
|
sectionId: '123456',
|
|
sectionUpdateFields: {
|
|
name: 'Updated Section',
|
|
},
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/sections/123456',
|
|
expect.objectContaining({
|
|
name: 'Updated Section',
|
|
}),
|
|
);
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('SectionDeleteHandler', () => {
|
|
it('should delete a section successfully', async () => {
|
|
const handler = new SectionDeleteHandler();
|
|
const mockCtx = createMockContext({
|
|
sectionId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('DELETE', '/sections/123456');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Label Handlers', () => {
|
|
describe('LabelCreateHandler', () => {
|
|
it('should create a label successfully', async () => {
|
|
const handler = new LabelCreateHandler();
|
|
const mockCtx = createMockContext({
|
|
labelName: 'Test Label',
|
|
labelOptions: {
|
|
color: 'red',
|
|
order: 1,
|
|
is_favorite: true,
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
name: 'Test Label',
|
|
color: 'red',
|
|
order: 1,
|
|
is_favorite: true,
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/labels',
|
|
expect.objectContaining({
|
|
name: 'Test Label',
|
|
color: 'red',
|
|
order: 1,
|
|
is_favorite: true,
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('LabelGetHandler', () => {
|
|
it('should get a label successfully', async () => {
|
|
const handler = new LabelGetHandler();
|
|
const mockCtx = createMockContext({
|
|
labelId: '123456',
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
name: 'Test Label',
|
|
color: 'red',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/labels/123456');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('LabelGetAllHandler', () => {
|
|
it('should get all labels successfully', async () => {
|
|
const handler = new LabelGetAllHandler();
|
|
const mockCtx = createMockContext({});
|
|
|
|
const expectedResponse = [
|
|
{ id: '1', name: 'Label 1', color: 'red' },
|
|
{ id: '2', name: 'Label 2', color: 'blue' },
|
|
];
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/labels');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('LabelUpdateHandler', () => {
|
|
it('should update a label successfully', async () => {
|
|
const handler = new LabelUpdateHandler();
|
|
const mockCtx = createMockContext({
|
|
labelId: '123456',
|
|
labelUpdateFields: {
|
|
name: 'Updated Label',
|
|
color: 'green',
|
|
is_favorite: false,
|
|
},
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/labels/123456',
|
|
expect.objectContaining({
|
|
name: 'Updated Label',
|
|
color: 'green',
|
|
is_favorite: false,
|
|
}),
|
|
);
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('LabelDeleteHandler', () => {
|
|
it('should delete a label successfully', async () => {
|
|
const handler = new LabelDeleteHandler();
|
|
const mockCtx = createMockContext({
|
|
labelId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('DELETE', '/labels/123456');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Comment Handlers', () => {
|
|
describe('CommentCreateHandler', () => {
|
|
it('should create a comment successfully', async () => {
|
|
const handler = new CommentCreateHandler();
|
|
const mockCtx = createMockContext({
|
|
commentTaskId: '123456',
|
|
commentContent: 'Test comment',
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '789',
|
|
task_id: '123456',
|
|
content: 'Test comment',
|
|
posted_at: '2025-08-03T12:00:00Z',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/comments',
|
|
expect.objectContaining({
|
|
task_id: '123456',
|
|
content: 'Test comment',
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('CommentGetHandler', () => {
|
|
it('should get a comment successfully', async () => {
|
|
const handler = new CommentGetHandler();
|
|
const mockCtx = createMockContext({
|
|
commentId: '123456',
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
task_id: '789',
|
|
content: 'Test comment',
|
|
};
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('GET', '/comments/123456');
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('CommentGetAllHandler', () => {
|
|
it('should get all comments with task filter successfully', async () => {
|
|
const handler = new CommentGetAllHandler();
|
|
const mockCtx = createMockContext({
|
|
commentFilters: {
|
|
task_id: '123456',
|
|
},
|
|
});
|
|
|
|
const expectedResponse = [
|
|
{ id: '1', task_id: '123456', content: 'Comment 1' },
|
|
{ id: '2', task_id: '123456', content: 'Comment 2' },
|
|
];
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'GET',
|
|
'/comments',
|
|
{},
|
|
expect.objectContaining({
|
|
task_id: '123456',
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
|
|
it('should get all comments with project filter successfully', async () => {
|
|
const handler = new CommentGetAllHandler();
|
|
const mockCtx = createMockContext({
|
|
commentFilters: {
|
|
project_id: '789',
|
|
},
|
|
});
|
|
|
|
const expectedResponse = [{ id: '1', project_id: '789', content: 'Comment 1' }];
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'GET',
|
|
'/comments',
|
|
{},
|
|
expect.objectContaining({
|
|
project_id: '789',
|
|
}),
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
|
|
describe('CommentUpdateHandler', () => {
|
|
it('should update a comment successfully', async () => {
|
|
const handler = new CommentUpdateHandler();
|
|
const mockCtx = createMockContext({
|
|
commentId: '123456',
|
|
commentUpdateFields: {
|
|
content: 'Updated comment',
|
|
},
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith(
|
|
'POST',
|
|
'/comments/123456',
|
|
expect.objectContaining({
|
|
content: 'Updated comment',
|
|
}),
|
|
);
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
|
|
describe('CommentDeleteHandler', () => {
|
|
it('should delete a comment successfully', async () => {
|
|
const handler = new CommentDeleteHandler();
|
|
const mockCtx = createMockContext({
|
|
commentId: '123456',
|
|
});
|
|
|
|
mockTodoistApiRequest.mockResolvedValue(undefined);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistApiRequest).toHaveBeenCalledWith('DELETE', '/comments/123456');
|
|
expect(result).toEqual({ success: true });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Special Operations', () => {
|
|
describe('QuickAddHandler', () => {
|
|
it('should quick add a task successfully', async () => {
|
|
const handler = new QuickAddHandler();
|
|
const mockCtx = createMockContext({
|
|
text: 'Buy milk tomorrow @shopping',
|
|
options: {},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '123456',
|
|
content: 'Buy milk',
|
|
project_id: '789',
|
|
labels: ['shopping'],
|
|
due: {
|
|
date: '2025-08-04',
|
|
string: 'tomorrow',
|
|
},
|
|
};
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
{ text: 'Buy milk tomorrow @shopping' },
|
|
{},
|
|
'/quick/add',
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
|
|
it('should quick add a task with all optional parameters', async () => {
|
|
const handler = new QuickAddHandler();
|
|
const mockCtx = createMockContext({
|
|
text: 'Meeting with team tomorrow at 2pm',
|
|
options: {
|
|
note: 'Discuss project roadmap and priorities',
|
|
reminder: 'tomorrow at 1:30pm',
|
|
auto_reminder: true,
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '789123',
|
|
content: 'Meeting with team',
|
|
project_id: '456',
|
|
due: {
|
|
date: '2025-08-04',
|
|
datetime: '2025-08-04T14:00:00',
|
|
string: 'tomorrow at 2pm',
|
|
},
|
|
};
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
{
|
|
text: 'Meeting with team tomorrow at 2pm',
|
|
note: 'Discuss project roadmap and priorities',
|
|
reminder: 'tomorrow at 1:30pm',
|
|
auto_reminder: true,
|
|
},
|
|
{},
|
|
'/quick/add',
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
|
|
it('should quick add a task with note only', async () => {
|
|
const handler = new QuickAddHandler();
|
|
const mockCtx = createMockContext({
|
|
text: 'Review documents',
|
|
options: {
|
|
note: 'Check the quarterly reports',
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '456789',
|
|
content: 'Review documents',
|
|
project_id: '123',
|
|
};
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
{
|
|
text: 'Review documents',
|
|
note: 'Check the quarterly reports',
|
|
},
|
|
{},
|
|
'/quick/add',
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
|
|
it('should quick add a task with reminder only', async () => {
|
|
const handler = new QuickAddHandler();
|
|
const mockCtx = createMockContext({
|
|
text: 'Call dentist',
|
|
options: {
|
|
reminder: 'next Monday at 9am',
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '321654',
|
|
content: 'Call dentist',
|
|
project_id: '456',
|
|
};
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
{
|
|
text: 'Call dentist',
|
|
reminder: 'next Monday at 9am',
|
|
},
|
|
{},
|
|
'/quick/add',
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
|
|
it('should quick add a task with auto_reminder only', async () => {
|
|
const handler = new QuickAddHandler();
|
|
const mockCtx = createMockContext({
|
|
text: 'Presentation due Friday at 5pm',
|
|
options: {
|
|
auto_reminder: true,
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '987654',
|
|
content: 'Presentation due',
|
|
project_id: '789',
|
|
due: {
|
|
date: '2025-08-08',
|
|
datetime: '2025-08-08T17:00:00',
|
|
string: 'Friday at 5pm',
|
|
},
|
|
};
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
{
|
|
text: 'Presentation due Friday at 5pm',
|
|
auto_reminder: true,
|
|
},
|
|
{},
|
|
'/quick/add',
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
|
|
it('should handle empty optional parameters correctly', async () => {
|
|
const handler = new QuickAddHandler();
|
|
const mockCtx = createMockContext({
|
|
text: 'Simple task',
|
|
options: {
|
|
note: '',
|
|
reminder: '',
|
|
auto_reminder: false,
|
|
},
|
|
});
|
|
|
|
const expectedResponse = {
|
|
id: '111222',
|
|
content: 'Simple task',
|
|
project_id: '333',
|
|
};
|
|
|
|
mockTodoistSyncRequest.mockResolvedValue(expectedResponse);
|
|
|
|
const result = await handler.handleOperation(mockCtx, 0);
|
|
|
|
// Should only include text since other options are empty/false
|
|
expect(mockTodoistSyncRequest).toHaveBeenCalledWith(
|
|
{ text: 'Simple task' },
|
|
{},
|
|
'/quick/add',
|
|
);
|
|
expect(result).toEqual({ data: expectedResponse });
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
describe('Type Validation', () => {
|
|
it('should throw error for invalid task ID type', async () => {
|
|
const handler = new GetHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: null,
|
|
});
|
|
|
|
await expect(handler.handleOperation(mockCtx, 0)).rejects.toThrow();
|
|
});
|
|
|
|
it('should throw error for invalid project ID type', async () => {
|
|
const handler = new ProjectGetHandler();
|
|
const mockCtx = createMockContext({
|
|
projectId: {},
|
|
});
|
|
|
|
await expect(handler.handleOperation(mockCtx, 0)).rejects.toThrow();
|
|
});
|
|
|
|
it('should throw error for invalid content type in task creation', async () => {
|
|
const handler = new CreateHandler();
|
|
const mockCtx = createMockContext({
|
|
content: 123,
|
|
project: '789',
|
|
options: {},
|
|
});
|
|
|
|
await expect(handler.handleOperation(mockCtx, 0)).rejects.toThrow();
|
|
});
|
|
});
|
|
|
|
describe('API Error Handling', () => {
|
|
it('should propagate API errors', async () => {
|
|
const handler = new GetHandler();
|
|
const mockCtx = createMockContext({
|
|
taskId: '123456',
|
|
});
|
|
|
|
const apiError = new Error('API Error');
|
|
mockTodoistApiRequest.mockRejectedValue(apiError);
|
|
|
|
await expect(handler.handleOperation(mockCtx, 0)).rejects.toThrow('API Error');
|
|
});
|
|
});
|
|
});
|
|
});
|