@doist/todoist-ai 4.15.1 → 4.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/filter-helpers.d.ts +1 -1
- package/dist/index.d.ts +175 -175
- package/dist/index.js +61 -81
- package/dist/main.js +15 -23
- package/dist/mcp-helpers.d.ts +4 -4
- package/dist/mcp-server-6tm7Rhyz.js +2840 -0
- package/dist/todoist-tool.d.ts +2 -2
- package/dist/tool-helpers.d.ts +1 -1
- package/dist/tools/add-comments.d.ts +1 -1
- package/dist/tools/add-comments.d.ts.map +1 -1
- package/dist/tools/add-projects.d.ts +4 -4
- package/dist/tools/add-projects.d.ts.map +1 -1
- package/dist/tools/add-sections.d.ts +1 -1
- package/dist/tools/add-sections.d.ts.map +1 -1
- package/dist/tools/add-tasks.d.ts +4 -4
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/complete-tasks.d.ts +1 -1
- package/dist/tools/complete-tasks.d.ts.map +1 -1
- package/dist/tools/delete-object.d.ts +3 -3
- package/dist/tools/delete-object.d.ts.map +1 -1
- package/dist/tools/fetch.d.ts +1 -1
- package/dist/tools/find-activity.d.ts +5 -5
- package/dist/tools/find-activity.d.ts.map +1 -1
- package/dist/tools/find-comments.d.ts +2 -2
- package/dist/tools/find-comments.d.ts.map +1 -1
- package/dist/tools/find-completed-tasks.d.ts +3 -3
- package/dist/tools/find-completed-tasks.d.ts.map +1 -1
- package/dist/tools/find-project-collaborators.d.ts +2 -2
- package/dist/tools/find-projects.d.ts +1 -1
- package/dist/tools/find-projects.d.ts.map +1 -1
- package/dist/tools/find-sections.d.ts +1 -1
- package/dist/tools/find-sections.d.ts.map +1 -1
- package/dist/tools/find-tasks-by-date.d.ts +1 -1
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
- package/dist/tools/find-tasks.d.ts +3 -3
- package/dist/tools/find-tasks.d.ts.map +1 -1
- package/dist/tools/get-overview.d.ts +1 -1
- package/dist/tools/manage-assignments.d.ts +1 -1
- package/dist/tools/search.d.ts +1 -1
- package/dist/tools/update-comments.d.ts +4 -4
- package/dist/tools/update-comments.d.ts.map +1 -1
- package/dist/tools/update-projects.d.ts +1 -1
- package/dist/tools/update-projects.d.ts.map +1 -1
- package/dist/tools/update-sections.d.ts +4 -4
- package/dist/tools/update-sections.d.ts.map +1 -1
- package/dist/tools/update-tasks.d.ts +7 -7
- package/dist/tools/update-tasks.d.ts.map +1 -1
- package/dist/tools/user-info.d.ts +1 -1
- package/dist/utils/assignment-validator.d.ts +2 -2
- package/dist/utils/response-builders.d.ts +1 -3
- package/dist/utils/response-builders.d.ts.map +1 -1
- package/dist/utils/test-helpers.d.ts +1 -1
- package/dist/utils/user-resolver.d.ts +1 -1
- package/package.json +11 -9
- package/dist/filter-helpers.js +0 -79
- package/dist/mcp-helpers.js +0 -71
- package/dist/mcp-server.js +0 -142
- package/dist/todoist-tool.js +0 -1
- package/dist/tool-helpers.js +0 -125
- package/dist/tool-helpers.test.d.ts +0 -2
- package/dist/tool-helpers.test.d.ts.map +0 -1
- package/dist/tool-helpers.test.js +0 -223
- package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-comments.test.js +0 -241
- package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-projects.test.js +0 -174
- package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-sections.test.js +0 -185
- package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/add-tasks.test.js +0 -533
- package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
- package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
- package/dist/tools/__tests__/assignment-integration.test.js +0 -428
- package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/complete-tasks.test.js +0 -206
- package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
- package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
- package/dist/tools/__tests__/delete-object.test.js +0 -110
- package/dist/tools/__tests__/fetch.test.d.ts +0 -2
- package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
- package/dist/tools/__tests__/fetch.test.js +0 -279
- package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
- package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-activity.test.js +0 -229
- package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-comments.test.js +0 -236
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-completed-tasks.test.js +0 -324
- package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-projects.test.js +0 -154
- package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-sections.test.js +0 -245
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
- package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/find-tasks.test.js +0 -771
- package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
- package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
- package/dist/tools/__tests__/get-overview.test.js +0 -225
- package/dist/tools/__tests__/search.test.d.ts +0 -2
- package/dist/tools/__tests__/search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/search.test.js +0 -206
- package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
- package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-comments.test.js +0 -294
- package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-projects.test.js +0 -217
- package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-sections.test.js +0 -169
- package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
- package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
- package/dist/tools/__tests__/update-tasks.test.js +0 -788
- package/dist/tools/__tests__/user-info.test.d.ts +0 -2
- package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
- package/dist/tools/__tests__/user-info.test.js +0 -139
- package/dist/tools/add-comments.js +0 -79
- package/dist/tools/add-projects.js +0 -63
- package/dist/tools/add-sections.js +0 -61
- package/dist/tools/add-tasks.js +0 -160
- package/dist/tools/complete-tasks.js +0 -68
- package/dist/tools/delete-object.js +0 -79
- package/dist/tools/fetch.js +0 -102
- package/dist/tools/find-activity.js +0 -221
- package/dist/tools/find-comments.js +0 -143
- package/dist/tools/find-completed-tasks.js +0 -161
- package/dist/tools/find-project-collaborators.js +0 -151
- package/dist/tools/find-projects.js +0 -101
- package/dist/tools/find-sections.js +0 -96
- package/dist/tools/find-tasks-by-date.js +0 -198
- package/dist/tools/find-tasks.js +0 -329
- package/dist/tools/get-overview.js +0 -249
- package/dist/tools/manage-assignments.js +0 -337
- package/dist/tools/search.js +0 -65
- package/dist/tools/update-comments.js +0 -82
- package/dist/tools/update-projects.js +0 -84
- package/dist/tools/update-sections.js +0 -70
- package/dist/tools/update-tasks.js +0 -170
- package/dist/tools/user-info.js +0 -142
- package/dist/utils/assignment-validator.js +0 -253
- package/dist/utils/constants.js +0 -45
- package/dist/utils/duration-parser.js +0 -96
- package/dist/utils/duration-parser.test.d.ts +0 -2
- package/dist/utils/duration-parser.test.d.ts.map +0 -1
- package/dist/utils/duration-parser.test.js +0 -147
- package/dist/utils/labels.js +0 -18
- package/dist/utils/priorities.js +0 -20
- package/dist/utils/response-builders.js +0 -210
- package/dist/utils/sanitize-data.js +0 -37
- package/dist/utils/sanitize-data.test.d.ts +0 -2
- package/dist/utils/sanitize-data.test.d.ts.map +0 -1
- package/dist/utils/sanitize-data.test.js +0 -93
- package/dist/utils/test-helpers.js +0 -237
- package/dist/utils/tool-names.js +0 -40
- package/dist/utils/user-resolver.js +0 -179
|
@@ -1,788 +0,0 @@
|
|
|
1
|
-
import { jest } from '@jest/globals';
|
|
2
|
-
import { createMockTask, extractStructuredContent, extractTextContent, TEST_IDS, } from '../../utils/test-helpers.js';
|
|
3
|
-
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
-
import { updateTasks } from '../update-tasks.js';
|
|
5
|
-
// Mock the Todoist API
|
|
6
|
-
const mockTodoistApi = {
|
|
7
|
-
updateTask: jest.fn(),
|
|
8
|
-
moveTask: jest.fn(),
|
|
9
|
-
};
|
|
10
|
-
const { UPDATE_TASKS } = ToolNames;
|
|
11
|
-
describe(`${UPDATE_TASKS} tool`, () => {
|
|
12
|
-
beforeEach(() => {
|
|
13
|
-
jest.clearAllMocks();
|
|
14
|
-
});
|
|
15
|
-
describe('updating task properties', () => {
|
|
16
|
-
it('should update task content and description', async () => {
|
|
17
|
-
// Mock API response extracted from recordings (Task type)
|
|
18
|
-
const mockApiResponse = createMockTask({
|
|
19
|
-
id: '8485093748',
|
|
20
|
-
content: 'Updated task content',
|
|
21
|
-
description: 'Updated task description',
|
|
22
|
-
url: 'https://todoist.com/showTask?id=8485093748',
|
|
23
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
24
|
-
});
|
|
25
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
26
|
-
const result = await updateTasks.execute({
|
|
27
|
-
tasks: [
|
|
28
|
-
{
|
|
29
|
-
id: '8485093748',
|
|
30
|
-
content: 'Updated task content',
|
|
31
|
-
description: 'Updated task description',
|
|
32
|
-
},
|
|
33
|
-
],
|
|
34
|
-
}, mockTodoistApi);
|
|
35
|
-
// Verify API was called correctly
|
|
36
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093748', {
|
|
37
|
-
content: 'Updated task content',
|
|
38
|
-
description: 'Updated task description',
|
|
39
|
-
});
|
|
40
|
-
// Verify result matches expected structure with text and structured content
|
|
41
|
-
const textContent = extractTextContent(result);
|
|
42
|
-
expect(textContent).toContain('Updated 1 task');
|
|
43
|
-
const structuredContent = extractStructuredContent(result);
|
|
44
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
45
|
-
tasks: expect.arrayContaining([expect.objectContaining({ id: '8485093748' })]),
|
|
46
|
-
}));
|
|
47
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
48
|
-
});
|
|
49
|
-
it('should update all tasks when multiple tasks are provided', async () => {
|
|
50
|
-
const mockApiResponse = createMockTask({
|
|
51
|
-
id: '8485093748',
|
|
52
|
-
content: 'Updated task content',
|
|
53
|
-
description: 'Updated task description',
|
|
54
|
-
url: 'https://todoist.com/showTask?id=8485093748',
|
|
55
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
56
|
-
});
|
|
57
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
58
|
-
const result = await updateTasks.execute({
|
|
59
|
-
tasks: [
|
|
60
|
-
{
|
|
61
|
-
id: '8485093748',
|
|
62
|
-
content: 'Updated task content',
|
|
63
|
-
description: 'Updated task description',
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
id: '8485093749',
|
|
67
|
-
content: 'Updated task content',
|
|
68
|
-
description: 'Updated task description',
|
|
69
|
-
},
|
|
70
|
-
],
|
|
71
|
-
}, mockTodoistApi);
|
|
72
|
-
// Verify API was called correctly
|
|
73
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093748', {
|
|
74
|
-
content: 'Updated task content',
|
|
75
|
-
description: 'Updated task description',
|
|
76
|
-
});
|
|
77
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093749', {
|
|
78
|
-
content: 'Updated task content',
|
|
79
|
-
description: 'Updated task description',
|
|
80
|
-
});
|
|
81
|
-
// Verify result matches expected structure with text and structured content
|
|
82
|
-
const textContent = extractTextContent(result);
|
|
83
|
-
expect(textContent).toContain('Updated 2 tasks');
|
|
84
|
-
const structuredContent = extractStructuredContent(result);
|
|
85
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
86
|
-
totalCount: 2,
|
|
87
|
-
tasks: expect.any(Array),
|
|
88
|
-
}));
|
|
89
|
-
expect(structuredContent.tasks).toHaveLength(2);
|
|
90
|
-
});
|
|
91
|
-
it('should update task priority and due date', async () => {
|
|
92
|
-
const mockApiResponse = createMockTask({
|
|
93
|
-
id: '8485093749',
|
|
94
|
-
content: 'Original task content',
|
|
95
|
-
labels: ['urgent'],
|
|
96
|
-
priority: 3,
|
|
97
|
-
url: 'https://todoist.com/showTask?id=8485093749',
|
|
98
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
99
|
-
due: {
|
|
100
|
-
date: '2025-08-20',
|
|
101
|
-
isRecurring: false,
|
|
102
|
-
lang: 'en',
|
|
103
|
-
string: 'Aug 20',
|
|
104
|
-
timezone: null,
|
|
105
|
-
},
|
|
106
|
-
});
|
|
107
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
108
|
-
const result = await updateTasks.execute({
|
|
109
|
-
tasks: [
|
|
110
|
-
{
|
|
111
|
-
id: '8485093749',
|
|
112
|
-
priority: 'p3',
|
|
113
|
-
dueString: 'Aug 20',
|
|
114
|
-
},
|
|
115
|
-
],
|
|
116
|
-
}, mockTodoistApi);
|
|
117
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093749', {
|
|
118
|
-
priority: 2,
|
|
119
|
-
dueString: 'Aug 20',
|
|
120
|
-
});
|
|
121
|
-
// Verify result structure
|
|
122
|
-
const textContent = extractTextContent(result);
|
|
123
|
-
expect(textContent).toContain('Updated 1 task');
|
|
124
|
-
const structuredContent = extractStructuredContent(result);
|
|
125
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
126
|
-
});
|
|
127
|
-
it('should move task to different project', async () => {
|
|
128
|
-
const mockApiResponse = createMockTask({
|
|
129
|
-
id: '8485093750',
|
|
130
|
-
content: 'Task to move',
|
|
131
|
-
projectId: 'new-project-id',
|
|
132
|
-
url: 'https://todoist.com/showTask?id=8485093750',
|
|
133
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
134
|
-
});
|
|
135
|
-
mockTodoistApi.moveTask.mockResolvedValue(mockApiResponse);
|
|
136
|
-
const result = await updateTasks.execute({
|
|
137
|
-
tasks: [
|
|
138
|
-
{
|
|
139
|
-
id: '8485093750',
|
|
140
|
-
projectId: 'new-project-id',
|
|
141
|
-
},
|
|
142
|
-
],
|
|
143
|
-
}, mockTodoistApi);
|
|
144
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledWith('8485093750', {
|
|
145
|
-
projectId: 'new-project-id',
|
|
146
|
-
});
|
|
147
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
148
|
-
// Verify result structure
|
|
149
|
-
const textContent = extractTextContent(result);
|
|
150
|
-
expect(textContent).toContain('Updated 1 task');
|
|
151
|
-
const structuredContent = extractStructuredContent(result);
|
|
152
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
153
|
-
});
|
|
154
|
-
it('should update task parent (create subtask relationship)', async () => {
|
|
155
|
-
const mockApiResponse = createMockTask({
|
|
156
|
-
id: '8485093751',
|
|
157
|
-
content: 'Subtask content',
|
|
158
|
-
parentId: 'parent-task-123',
|
|
159
|
-
url: 'https://todoist.com/showTask?id=8485093751',
|
|
160
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
161
|
-
});
|
|
162
|
-
mockTodoistApi.moveTask.mockResolvedValue(mockApiResponse);
|
|
163
|
-
const result = await updateTasks.execute({
|
|
164
|
-
tasks: [
|
|
165
|
-
{
|
|
166
|
-
id: '8485093751',
|
|
167
|
-
parentId: 'parent-task-123',
|
|
168
|
-
},
|
|
169
|
-
],
|
|
170
|
-
}, mockTodoistApi);
|
|
171
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledWith('8485093751', {
|
|
172
|
-
parentId: 'parent-task-123',
|
|
173
|
-
});
|
|
174
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
175
|
-
// Verify result structure
|
|
176
|
-
const textContent = extractTextContent(result);
|
|
177
|
-
expect(textContent).toContain('Updated 1 task');
|
|
178
|
-
const structuredContent = extractStructuredContent(result);
|
|
179
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
180
|
-
});
|
|
181
|
-
it('should move task and update properties at once', async () => {
|
|
182
|
-
const movedTask = createMockTask({
|
|
183
|
-
id: '8485093752',
|
|
184
|
-
content: 'Task to move',
|
|
185
|
-
projectId: 'different-project-id',
|
|
186
|
-
});
|
|
187
|
-
const updatedTask = createMockTask({
|
|
188
|
-
id: '8485093752',
|
|
189
|
-
content: 'Completely updated task',
|
|
190
|
-
description: 'New description with details',
|
|
191
|
-
priority: 4,
|
|
192
|
-
projectId: 'different-project-id',
|
|
193
|
-
url: 'https://todoist.com/showTask?id=8485093752',
|
|
194
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
195
|
-
due: {
|
|
196
|
-
date: '2025-08-25',
|
|
197
|
-
isRecurring: true,
|
|
198
|
-
lang: 'en',
|
|
199
|
-
string: 'every Friday',
|
|
200
|
-
timezone: null,
|
|
201
|
-
},
|
|
202
|
-
});
|
|
203
|
-
mockTodoistApi.moveTask.mockResolvedValue(movedTask);
|
|
204
|
-
mockTodoistApi.updateTask.mockResolvedValue(updatedTask);
|
|
205
|
-
const result = await updateTasks.execute({
|
|
206
|
-
tasks: [
|
|
207
|
-
{
|
|
208
|
-
id: '8485093752',
|
|
209
|
-
content: 'Completely updated task',
|
|
210
|
-
description: 'New description with details',
|
|
211
|
-
priority: 'p4',
|
|
212
|
-
dueString: 'every Friday',
|
|
213
|
-
projectId: 'different-project-id',
|
|
214
|
-
},
|
|
215
|
-
],
|
|
216
|
-
}, mockTodoistApi);
|
|
217
|
-
// Should call moveTask first for the projectId
|
|
218
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledWith('8485093752', {
|
|
219
|
-
projectId: 'different-project-id',
|
|
220
|
-
});
|
|
221
|
-
// Then call updateTask for the other properties
|
|
222
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093752', {
|
|
223
|
-
content: 'Completely updated task',
|
|
224
|
-
description: 'New description with details',
|
|
225
|
-
priority: 1,
|
|
226
|
-
dueString: 'every Friday',
|
|
227
|
-
});
|
|
228
|
-
// Verify result structure
|
|
229
|
-
const textContent = extractTextContent(result);
|
|
230
|
-
expect(textContent).toContain('Updated 1 task');
|
|
231
|
-
const structuredContent = extractStructuredContent(result);
|
|
232
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
233
|
-
tasks: expect.arrayContaining([expect.objectContaining({ id: '8485093752' })]),
|
|
234
|
-
}));
|
|
235
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
236
|
-
});
|
|
237
|
-
it('should update task duration', async () => {
|
|
238
|
-
const mockApiResponse = createMockTask({
|
|
239
|
-
id: '8485093753',
|
|
240
|
-
content: 'Task with updated duration',
|
|
241
|
-
duration: { amount: 150, unit: 'minute' },
|
|
242
|
-
url: 'https://todoist.com/showTask?id=8485093753',
|
|
243
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
244
|
-
});
|
|
245
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
246
|
-
const result = await updateTasks.execute({
|
|
247
|
-
tasks: [
|
|
248
|
-
{
|
|
249
|
-
id: '8485093753',
|
|
250
|
-
duration: '2h30m',
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
}, mockTodoistApi);
|
|
254
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093753', {
|
|
255
|
-
duration: 150,
|
|
256
|
-
durationUnit: 'minute',
|
|
257
|
-
});
|
|
258
|
-
// Verify result structure
|
|
259
|
-
const textContent = extractTextContent(result);
|
|
260
|
-
expect(textContent).toContain('Updated 1 task');
|
|
261
|
-
const structuredContent = extractStructuredContent(result);
|
|
262
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
263
|
-
tasks: expect.arrayContaining([expect.objectContaining({ id: '8485093753' })]),
|
|
264
|
-
}));
|
|
265
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
266
|
-
});
|
|
267
|
-
it('should handle various duration formats', async () => {
|
|
268
|
-
const mockApiResponse = createMockTask({
|
|
269
|
-
id: '8485093754',
|
|
270
|
-
content: 'Test task',
|
|
271
|
-
duration: { amount: 120, unit: 'minute' },
|
|
272
|
-
});
|
|
273
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
274
|
-
// Test different duration formats
|
|
275
|
-
const testCases = [
|
|
276
|
-
{ input: '2h', expectedMinutes: 120 },
|
|
277
|
-
{ input: '90m', expectedMinutes: 90 },
|
|
278
|
-
{ input: '1.5h', expectedMinutes: 90 },
|
|
279
|
-
{ input: ' 2h 30m ', expectedMinutes: 150 },
|
|
280
|
-
{ input: '2H30M', expectedMinutes: 150 },
|
|
281
|
-
];
|
|
282
|
-
for (const testCase of testCases) {
|
|
283
|
-
mockTodoistApi.updateTask.mockClear();
|
|
284
|
-
await updateTasks.execute({
|
|
285
|
-
tasks: [
|
|
286
|
-
{
|
|
287
|
-
id: '8485093754',
|
|
288
|
-
duration: testCase.input,
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
}, mockTodoistApi);
|
|
292
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093754', expect.objectContaining({
|
|
293
|
-
duration: testCase.expectedMinutes,
|
|
294
|
-
durationUnit: 'minute',
|
|
295
|
-
}));
|
|
296
|
-
}
|
|
297
|
-
});
|
|
298
|
-
it('should update task with duration and move at once', async () => {
|
|
299
|
-
const movedTask = createMockTask({
|
|
300
|
-
id: '8485093755',
|
|
301
|
-
content: 'Task to move and update',
|
|
302
|
-
projectId: 'new-project-id',
|
|
303
|
-
});
|
|
304
|
-
const updatedTask = createMockTask({
|
|
305
|
-
id: '8485093755',
|
|
306
|
-
content: 'Updated task with duration',
|
|
307
|
-
duration: { amount: 120, unit: 'minute' },
|
|
308
|
-
projectId: 'new-project-id',
|
|
309
|
-
});
|
|
310
|
-
mockTodoistApi.moveTask.mockResolvedValue(movedTask);
|
|
311
|
-
mockTodoistApi.updateTask.mockResolvedValue(updatedTask);
|
|
312
|
-
const result = await updateTasks.execute({
|
|
313
|
-
tasks: [
|
|
314
|
-
{
|
|
315
|
-
id: '8485093755',
|
|
316
|
-
content: 'Updated task with duration',
|
|
317
|
-
duration: '2h',
|
|
318
|
-
projectId: 'new-project-id',
|
|
319
|
-
},
|
|
320
|
-
],
|
|
321
|
-
}, mockTodoistApi);
|
|
322
|
-
// Should call moveTask first
|
|
323
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledWith('8485093755', {
|
|
324
|
-
projectId: 'new-project-id',
|
|
325
|
-
});
|
|
326
|
-
// Then call updateTask with duration
|
|
327
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093755', {
|
|
328
|
-
content: 'Updated task with duration',
|
|
329
|
-
duration: 120,
|
|
330
|
-
durationUnit: 'minute',
|
|
331
|
-
});
|
|
332
|
-
// Verify result structure
|
|
333
|
-
const textContent = extractTextContent(result);
|
|
334
|
-
expect(textContent).toContain('Updated 1 task');
|
|
335
|
-
const structuredContent = extractStructuredContent(result);
|
|
336
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
337
|
-
tasks: expect.arrayContaining([expect.objectContaining({ id: '8485093755' })]),
|
|
338
|
-
}));
|
|
339
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
340
|
-
});
|
|
341
|
-
});
|
|
342
|
-
describe('updating deadlines', () => {
|
|
343
|
-
it('should update task deadline', async () => {
|
|
344
|
-
const mockApiResponse = createMockTask({
|
|
345
|
-
id: '8485093760',
|
|
346
|
-
content: 'Task with deadline',
|
|
347
|
-
deadline: {
|
|
348
|
-
date: '2025-12-31',
|
|
349
|
-
lang: 'en',
|
|
350
|
-
},
|
|
351
|
-
url: 'https://todoist.com/showTask?id=8485093760',
|
|
352
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
353
|
-
});
|
|
354
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
355
|
-
const result = await updateTasks.execute({
|
|
356
|
-
tasks: [
|
|
357
|
-
{
|
|
358
|
-
id: '8485093760',
|
|
359
|
-
deadlineDate: '2025-12-31',
|
|
360
|
-
},
|
|
361
|
-
],
|
|
362
|
-
}, mockTodoistApi);
|
|
363
|
-
// Verify API was called with deadline
|
|
364
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093760', {
|
|
365
|
-
deadlineDate: '2025-12-31',
|
|
366
|
-
});
|
|
367
|
-
// Verify result structure
|
|
368
|
-
const textContent = extractTextContent(result);
|
|
369
|
-
expect(textContent).toContain('Updated 1 task');
|
|
370
|
-
const structuredContent = extractStructuredContent(result);
|
|
371
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
372
|
-
tasks: expect.arrayContaining([
|
|
373
|
-
expect.objectContaining({
|
|
374
|
-
id: '8485093760',
|
|
375
|
-
deadlineDate: '2025-12-31',
|
|
376
|
-
}),
|
|
377
|
-
]),
|
|
378
|
-
}));
|
|
379
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
380
|
-
});
|
|
381
|
-
it('should remove task deadline with "remove" string', async () => {
|
|
382
|
-
const mockApiResponse = createMockTask({
|
|
383
|
-
id: '8485093761',
|
|
384
|
-
content: 'Task without deadline',
|
|
385
|
-
deadline: null,
|
|
386
|
-
url: 'https://todoist.com/showTask?id=8485093761',
|
|
387
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
388
|
-
});
|
|
389
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
390
|
-
const result = await updateTasks.execute({
|
|
391
|
-
tasks: [
|
|
392
|
-
{
|
|
393
|
-
id: '8485093761',
|
|
394
|
-
deadlineDate: 'remove',
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
}, mockTodoistApi);
|
|
398
|
-
// Verify API was called to remove deadline (converts "remove" to null)
|
|
399
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093761', {
|
|
400
|
-
deadlineDate: null,
|
|
401
|
-
});
|
|
402
|
-
// Verify result structure
|
|
403
|
-
const textContent = extractTextContent(result);
|
|
404
|
-
expect(textContent).toContain('Updated 1 task');
|
|
405
|
-
const structuredContent = extractStructuredContent(result);
|
|
406
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
describe('updating labels', () => {
|
|
410
|
-
it('should update task labels', async () => {
|
|
411
|
-
const mockApiResponse = createMockTask({
|
|
412
|
-
id: '8485093750',
|
|
413
|
-
content: 'Task with updated labels',
|
|
414
|
-
labels: ['work', 'important'],
|
|
415
|
-
url: 'https://todoist.com/showTask?id=8485093750',
|
|
416
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
417
|
-
});
|
|
418
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
419
|
-
const result = await updateTasks.execute({
|
|
420
|
-
tasks: [
|
|
421
|
-
{
|
|
422
|
-
id: '8485093750',
|
|
423
|
-
labels: ['work', 'important'],
|
|
424
|
-
},
|
|
425
|
-
],
|
|
426
|
-
}, mockTodoistApi);
|
|
427
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093750', {
|
|
428
|
-
labels: ['work', 'important'],
|
|
429
|
-
});
|
|
430
|
-
// Verify structured content includes updated labels
|
|
431
|
-
const structuredContent = extractStructuredContent(result);
|
|
432
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
433
|
-
expect(structuredContent.tasks[0]).toEqual(expect.objectContaining({
|
|
434
|
-
labels: ['work', 'important'],
|
|
435
|
-
}));
|
|
436
|
-
});
|
|
437
|
-
it('should clear task labels with empty array', async () => {
|
|
438
|
-
const mockApiResponse = createMockTask({
|
|
439
|
-
id: '8485093751',
|
|
440
|
-
content: 'Task with cleared labels',
|
|
441
|
-
labels: [],
|
|
442
|
-
url: 'https://todoist.com/showTask?id=8485093751',
|
|
443
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
444
|
-
});
|
|
445
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
446
|
-
await updateTasks.execute({
|
|
447
|
-
tasks: [
|
|
448
|
-
{
|
|
449
|
-
id: '8485093751',
|
|
450
|
-
labels: [],
|
|
451
|
-
},
|
|
452
|
-
],
|
|
453
|
-
}, mockTodoistApi);
|
|
454
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093751', {
|
|
455
|
-
labels: [],
|
|
456
|
-
});
|
|
457
|
-
});
|
|
458
|
-
it('should update task with labels along with other fields', async () => {
|
|
459
|
-
const mockApiResponse = createMockTask({
|
|
460
|
-
id: '8485093752',
|
|
461
|
-
content: 'Updated content',
|
|
462
|
-
labels: ['personal', 'todo'],
|
|
463
|
-
priority: 3,
|
|
464
|
-
url: 'https://todoist.com/showTask?id=8485093752',
|
|
465
|
-
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
466
|
-
});
|
|
467
|
-
mockTodoistApi.updateTask.mockResolvedValue(mockApiResponse);
|
|
468
|
-
await updateTasks.execute({
|
|
469
|
-
tasks: [
|
|
470
|
-
{
|
|
471
|
-
id: '8485093752',
|
|
472
|
-
content: 'Updated content',
|
|
473
|
-
labels: ['personal', 'todo'],
|
|
474
|
-
priority: 'p2',
|
|
475
|
-
},
|
|
476
|
-
],
|
|
477
|
-
}, mockTodoistApi);
|
|
478
|
-
expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('8485093752', {
|
|
479
|
-
content: 'Updated content',
|
|
480
|
-
labels: ['personal', 'todo'],
|
|
481
|
-
priority: 3,
|
|
482
|
-
});
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
describe('error handling', () => {
|
|
486
|
-
it('should throw error for invalid duration format', async () => {
|
|
487
|
-
await expect(updateTasks.execute({
|
|
488
|
-
tasks: [
|
|
489
|
-
{
|
|
490
|
-
id: '8485093756',
|
|
491
|
-
duration: 'invalid',
|
|
492
|
-
},
|
|
493
|
-
],
|
|
494
|
-
}, mockTodoistApi)).rejects.toThrow('Task 8485093756: Invalid duration format "invalid"');
|
|
495
|
-
});
|
|
496
|
-
it('should throw error for duration exceeding 24 hours', async () => {
|
|
497
|
-
await expect(updateTasks.execute({
|
|
498
|
-
tasks: [
|
|
499
|
-
{
|
|
500
|
-
id: '8485093757',
|
|
501
|
-
duration: '25h',
|
|
502
|
-
},
|
|
503
|
-
],
|
|
504
|
-
}, mockTodoistApi)).rejects.toThrow('Task 8485093757: Invalid duration format "25h": Duration cannot exceed 24 hours (1440 minutes)');
|
|
505
|
-
});
|
|
506
|
-
it('should throw error when multiple move parameters are provided', async () => {
|
|
507
|
-
await expect(updateTasks.execute({
|
|
508
|
-
tasks: [
|
|
509
|
-
{
|
|
510
|
-
id: '8485093748',
|
|
511
|
-
projectId: 'new-project',
|
|
512
|
-
sectionId: 'new-section',
|
|
513
|
-
},
|
|
514
|
-
],
|
|
515
|
-
}, mockTodoistApi)).rejects.toThrow('Only one of projectId, sectionId, or parentId can be specified at a time. ' +
|
|
516
|
-
'The Todoist API requires exactly one destination for move operations.');
|
|
517
|
-
});
|
|
518
|
-
it('should throw error when all three move parameters are provided', async () => {
|
|
519
|
-
await expect(updateTasks.execute({
|
|
520
|
-
tasks: [
|
|
521
|
-
{
|
|
522
|
-
id: '8485093748',
|
|
523
|
-
projectId: 'p1',
|
|
524
|
-
sectionId: 's1',
|
|
525
|
-
parentId: 't1',
|
|
526
|
-
},
|
|
527
|
-
],
|
|
528
|
-
}, mockTodoistApi)).rejects.toThrow('Only one of projectId, sectionId, or parentId can be specified at a time');
|
|
529
|
-
});
|
|
530
|
-
it.each([
|
|
531
|
-
{
|
|
532
|
-
error: 'API Error: Task not found',
|
|
533
|
-
params: { id: 'non-existent-task', content: 'Updated content' },
|
|
534
|
-
},
|
|
535
|
-
{
|
|
536
|
-
error: 'API Error: Invalid priority value',
|
|
537
|
-
params: { id: '8485093748', content: 'Test task' },
|
|
538
|
-
},
|
|
539
|
-
])('should propagate $error', async ({ error, params }) => {
|
|
540
|
-
mockTodoistApi.updateTask.mockRejectedValue(new Error(error));
|
|
541
|
-
await expect(updateTasks.execute({
|
|
542
|
-
tasks: [params],
|
|
543
|
-
}, mockTodoistApi)).rejects.toThrow(error);
|
|
544
|
-
});
|
|
545
|
-
});
|
|
546
|
-
describe('task organisation', () => {
|
|
547
|
-
describe('organizing multiple tasks', () => {
|
|
548
|
-
it('should move multiple tasks to the same destination', async () => {
|
|
549
|
-
const sectionId = '6cfPqr9xgvmgW6J0';
|
|
550
|
-
const mockResponses = [
|
|
551
|
-
createMockTask({ id: '6cPuJm79x4QhMwR4', content: 'First task', sectionId }),
|
|
552
|
-
createMockTask({ id: '6cPHJj2MV4HMj92W', content: 'Second task', sectionId }),
|
|
553
|
-
];
|
|
554
|
-
// Each task should be moved individually to avoid bulk operation issues
|
|
555
|
-
mockTodoistApi.moveTask
|
|
556
|
-
.mockResolvedValueOnce(mockResponses[0])
|
|
557
|
-
.mockResolvedValueOnce(mockResponses[1]);
|
|
558
|
-
const result = await updateTasks.execute({
|
|
559
|
-
tasks: [
|
|
560
|
-
{ id: '6cPHJm59x4WhMwR4', sectionId },
|
|
561
|
-
{ id: '6cPHJj2MV4HMj92W', sectionId },
|
|
562
|
-
],
|
|
563
|
-
}, mockTodoistApi);
|
|
564
|
-
// Should call moveTask twice, once for each task individually
|
|
565
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledTimes(2);
|
|
566
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(1, '6cPHJm59x4WhMwR4', {
|
|
567
|
-
sectionId,
|
|
568
|
-
});
|
|
569
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(2, '6cPHJj2MV4HMj92W', {
|
|
570
|
-
sectionId,
|
|
571
|
-
});
|
|
572
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
573
|
-
// Verify result structure
|
|
574
|
-
const textContent = extractTextContent(result);
|
|
575
|
-
expect(textContent).toContain('Updated 2 tasks');
|
|
576
|
-
const structuredContent = extractStructuredContent(result);
|
|
577
|
-
expect(structuredContent.tasks).toHaveLength(2);
|
|
578
|
-
expect(structuredContent.totalCount).toBe(2);
|
|
579
|
-
});
|
|
580
|
-
it('should move multiple tasks with different destinations', async () => {
|
|
581
|
-
const { TASK_1, TASK_2, TASK_3 } = TEST_IDS;
|
|
582
|
-
const mockResponses = [
|
|
583
|
-
createMockTask({ id: TASK_1, content: 'Task 1', projectId: 'new-project-id' }),
|
|
584
|
-
createMockTask({ id: TASK_2, content: 'Task 2', sectionId: 'new-section-id' }),
|
|
585
|
-
createMockTask({ id: TASK_3, content: 'Task 3', parentId: 'parent-task-123' }),
|
|
586
|
-
];
|
|
587
|
-
// Each task should be moved individually
|
|
588
|
-
mockTodoistApi.moveTask
|
|
589
|
-
.mockResolvedValueOnce(mockResponses[0])
|
|
590
|
-
.mockResolvedValueOnce(mockResponses[1])
|
|
591
|
-
.mockResolvedValueOnce(mockResponses[2]);
|
|
592
|
-
const result = await updateTasks.execute({
|
|
593
|
-
tasks: [
|
|
594
|
-
{ id: '8485093748', projectId: 'new-project-id' },
|
|
595
|
-
{ id: '8485093749', sectionId: 'new-section-id' },
|
|
596
|
-
{ id: '8485093750', parentId: 'parent-task-123' },
|
|
597
|
-
],
|
|
598
|
-
}, mockTodoistApi);
|
|
599
|
-
// Verify API was called correctly - 3 individual move calls
|
|
600
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledTimes(3);
|
|
601
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(1, '8485093748', {
|
|
602
|
-
projectId: 'new-project-id',
|
|
603
|
-
});
|
|
604
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(2, '8485093749', {
|
|
605
|
-
sectionId: 'new-section-id',
|
|
606
|
-
});
|
|
607
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(3, '8485093750', {
|
|
608
|
-
parentId: 'parent-task-123',
|
|
609
|
-
});
|
|
610
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
611
|
-
// Verify results are returned in the correct order
|
|
612
|
-
const textContent = extractTextContent(result);
|
|
613
|
-
expect(textContent).toContain('Updated 3 tasks');
|
|
614
|
-
const structuredContent = extractStructuredContent(result);
|
|
615
|
-
expect(structuredContent.tasks).toHaveLength(3);
|
|
616
|
-
expect(structuredContent.totalCount).toBe(3);
|
|
617
|
-
});
|
|
618
|
-
it('should handle single task organization', async () => {
|
|
619
|
-
const mockTaskResponse = createMockTask({
|
|
620
|
-
id: '8485093751',
|
|
621
|
-
content: 'Single task update',
|
|
622
|
-
sectionId: 'target-section',
|
|
623
|
-
url: 'https://todoist.com/showTask?id=8485093751',
|
|
624
|
-
addedAt: '2025-08-13T22:09:59.123456Z',
|
|
625
|
-
});
|
|
626
|
-
mockTodoistApi.moveTask.mockResolvedValue(mockTaskResponse);
|
|
627
|
-
const result = await updateTasks.execute({ tasks: [{ id: '8485093751', sectionId: 'target-section' }] }, mockTodoistApi);
|
|
628
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledTimes(1);
|
|
629
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledWith('8485093751', {
|
|
630
|
-
sectionId: 'target-section',
|
|
631
|
-
});
|
|
632
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
633
|
-
// Verify result structure
|
|
634
|
-
const textContent = extractTextContent(result);
|
|
635
|
-
expect(textContent).toContain('Updated 1 task');
|
|
636
|
-
const structuredContent = extractStructuredContent(result);
|
|
637
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
638
|
-
tasks: expect.arrayContaining([
|
|
639
|
-
expect.objectContaining({ id: '8485093751' }),
|
|
640
|
-
]),
|
|
641
|
-
}));
|
|
642
|
-
expect(structuredContent.tasks).toHaveLength(1);
|
|
643
|
-
});
|
|
644
|
-
it('should handle complex reorganization scenario', async () => {
|
|
645
|
-
// Simulate moving tasks to different destinations (one move param per task)
|
|
646
|
-
const mockResponses = [
|
|
647
|
-
createMockTask({
|
|
648
|
-
id: 'task-1',
|
|
649
|
-
content: 'Task moved to new project',
|
|
650
|
-
projectId: 'project-new',
|
|
651
|
-
url: 'https://todoist.com/showTask?id=task-1',
|
|
652
|
-
addedAt: '2025-08-13T22:10:00.123456Z',
|
|
653
|
-
}),
|
|
654
|
-
createMockTask({
|
|
655
|
-
id: 'task-2',
|
|
656
|
-
content: 'Task made into subtask',
|
|
657
|
-
parentId: 'task-1',
|
|
658
|
-
url: 'https://todoist.com/showTask?id=task-2',
|
|
659
|
-
addedAt: '2025-08-13T22:10:01.123456Z',
|
|
660
|
-
}),
|
|
661
|
-
createMockTask({
|
|
662
|
-
id: 'task-3',
|
|
663
|
-
content: 'Task moved to section',
|
|
664
|
-
sectionId: 'section-new',
|
|
665
|
-
url: 'https://todoist.com/showTask?id=task-3',
|
|
666
|
-
addedAt: '2025-08-13T22:10:02.123456Z',
|
|
667
|
-
}),
|
|
668
|
-
];
|
|
669
|
-
// Each task should be moved individually
|
|
670
|
-
mockTodoistApi.moveTask
|
|
671
|
-
.mockResolvedValueOnce(mockResponses[0])
|
|
672
|
-
.mockResolvedValueOnce(mockResponses[1])
|
|
673
|
-
.mockResolvedValueOnce(mockResponses[2]);
|
|
674
|
-
const result = await updateTasks.execute({
|
|
675
|
-
tasks: [
|
|
676
|
-
{ id: 'task-1', projectId: 'project-new' },
|
|
677
|
-
{ id: 'task-2', parentId: 'task-1' },
|
|
678
|
-
{ id: 'task-3', sectionId: 'section-new' },
|
|
679
|
-
],
|
|
680
|
-
}, mockTodoistApi);
|
|
681
|
-
// Verify API was called correctly - 3 individual move calls
|
|
682
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledTimes(3);
|
|
683
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(1, 'task-1', {
|
|
684
|
-
projectId: 'project-new',
|
|
685
|
-
});
|
|
686
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(2, 'task-2', {
|
|
687
|
-
parentId: 'task-1',
|
|
688
|
-
});
|
|
689
|
-
expect(mockTodoistApi.moveTask).toHaveBeenNthCalledWith(3, 'task-3', {
|
|
690
|
-
sectionId: 'section-new',
|
|
691
|
-
});
|
|
692
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
693
|
-
// Verify result structure
|
|
694
|
-
const textContent = extractTextContent(result);
|
|
695
|
-
expect(textContent).toContain('Updated 3 tasks');
|
|
696
|
-
const structuredContent = extractStructuredContent(result);
|
|
697
|
-
expect(structuredContent.tasks).toHaveLength(3);
|
|
698
|
-
expect(structuredContent.totalCount).toBe(3);
|
|
699
|
-
});
|
|
700
|
-
});
|
|
701
|
-
describe('partial updates', () => {
|
|
702
|
-
it('should handle move operations with single parameters', async () => {
|
|
703
|
-
const mockResponse = createMockTask({
|
|
704
|
-
id: '8485093752',
|
|
705
|
-
content: 'Minimal update task',
|
|
706
|
-
projectId: 'new-project-only',
|
|
707
|
-
url: 'https://todoist.com/showTask?id=8485093752',
|
|
708
|
-
addedAt: '2025-08-13T22:10:07.123456Z',
|
|
709
|
-
});
|
|
710
|
-
mockTodoistApi.moveTask.mockResolvedValue(mockResponse);
|
|
711
|
-
const result = await updateTasks.execute({
|
|
712
|
-
tasks: [
|
|
713
|
-
{
|
|
714
|
-
id: '8485093752',
|
|
715
|
-
projectId: 'new-project-only',
|
|
716
|
-
// Only updating projectId (move operation)
|
|
717
|
-
},
|
|
718
|
-
],
|
|
719
|
-
}, mockTodoistApi);
|
|
720
|
-
expect(mockTodoistApi.moveTask).toHaveBeenCalledWith('8485093752', {
|
|
721
|
-
projectId: 'new-project-only',
|
|
722
|
-
});
|
|
723
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
724
|
-
// Verify result structure
|
|
725
|
-
const textContent = extractTextContent(result);
|
|
726
|
-
expect(textContent).toContain('Updated 1 task');
|
|
727
|
-
const structuredContent = extractStructuredContent(result);
|
|
728
|
-
expect(structuredContent).toEqual(expect.objectContaining({
|
|
729
|
-
tasks: expect.arrayContaining([
|
|
730
|
-
expect.objectContaining({ id: '8485093752' }),
|
|
731
|
-
]),
|
|
732
|
-
}));
|
|
733
|
-
});
|
|
734
|
-
it('should handle empty updates (only id provided)', async () => {
|
|
735
|
-
const result = await updateTasks.execute({ tasks: [{ id: '8485093753' }] }, mockTodoistApi);
|
|
736
|
-
// No API calls should be made since no move parameters are provided
|
|
737
|
-
expect(mockTodoistApi.moveTask).not.toHaveBeenCalled();
|
|
738
|
-
expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
|
|
739
|
-
// Returns empty results since no moves were processed
|
|
740
|
-
const textContent = extractTextContent(result);
|
|
741
|
-
expect(textContent).toContain('Updated 0 tasks');
|
|
742
|
-
const structuredContent = extractStructuredContent(result);
|
|
743
|
-
expect(structuredContent.tasks).toBeUndefined(); // Empty arrays are removed
|
|
744
|
-
expect(structuredContent.totalCount).toBe(0);
|
|
745
|
-
});
|
|
746
|
-
});
|
|
747
|
-
describe('error handling', () => {
|
|
748
|
-
it('should throw error when task has multiple move parameters', async () => {
|
|
749
|
-
await expect(updateTasks.execute({
|
|
750
|
-
tasks: [
|
|
751
|
-
{
|
|
752
|
-
id: 'task-1',
|
|
753
|
-
projectId: 'new-project',
|
|
754
|
-
sectionId: 'new-section',
|
|
755
|
-
},
|
|
756
|
-
],
|
|
757
|
-
}, mockTodoistApi)).rejects.toThrow('Task task-1: Only one of projectId, sectionId, or parentId can be specified at a time');
|
|
758
|
-
});
|
|
759
|
-
it('should propagate API errors for individual task moves', async () => {
|
|
760
|
-
const apiError = new Error('API Error: Task not found');
|
|
761
|
-
mockTodoistApi.moveTask.mockRejectedValue(apiError);
|
|
762
|
-
await expect(updateTasks.execute({ tasks: [{ id: 'non-existent-task', projectId: 'some-project' }] }, mockTodoistApi)).rejects.toThrow('API Error: Task not found');
|
|
763
|
-
});
|
|
764
|
-
it('should handle validation errors', async () => {
|
|
765
|
-
const validationError = new Error('API Error: Invalid section ID');
|
|
766
|
-
mockTodoistApi.moveTask.mockRejectedValue(validationError);
|
|
767
|
-
await expect(updateTasks.execute({ tasks: [{ id: 'task-1', sectionId: 'invalid-section-format' }] }, mockTodoistApi)).rejects.toThrow('API Error: Invalid section ID');
|
|
768
|
-
});
|
|
769
|
-
it('should handle permission errors', async () => {
|
|
770
|
-
const permissionError = new Error('API Error: Insufficient permissions to move task');
|
|
771
|
-
mockTodoistApi.moveTask.mockRejectedValue(permissionError);
|
|
772
|
-
await expect(updateTasks.execute({ tasks: [{ id: 'restricted-task', projectId: 'restricted-project' }] }, mockTodoistApi)).rejects.toThrow('API Error: Insufficient permissions to move task');
|
|
773
|
-
});
|
|
774
|
-
it('should handle circular parent dependency errors', async () => {
|
|
775
|
-
const circularError = new Error('API Error: Circular dependency detected');
|
|
776
|
-
mockTodoistApi.moveTask.mockRejectedValue(circularError);
|
|
777
|
-
await expect(updateTasks.execute({
|
|
778
|
-
tasks: [
|
|
779
|
-
{
|
|
780
|
-
id: 'task-parent',
|
|
781
|
-
parentId: 'task-child', // This would create a circular dependency
|
|
782
|
-
},
|
|
783
|
-
],
|
|
784
|
-
}, mockTodoistApi)).rejects.toThrow('API Error: Circular dependency detected');
|
|
785
|
-
});
|
|
786
|
-
});
|
|
787
|
-
});
|
|
788
|
-
});
|