@doist/todoist-ai 4.16.0 → 4.17.0

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.
Files changed (178) hide show
  1. package/dist/filter-helpers.d.ts +1 -1
  2. package/dist/index.d.ts +1044 -196
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +61 -81
  5. package/dist/main.js +15 -23
  6. package/dist/mcp-helpers.d.ts +5 -5
  7. package/dist/mcp-helpers.d.ts.map +1 -1
  8. package/dist/mcp-server-BADReNAy.js +3092 -0
  9. package/dist/todoist-tool.d.ts +9 -3
  10. package/dist/todoist-tool.d.ts.map +1 -1
  11. package/dist/tool-helpers.d.ts +1 -1
  12. package/dist/tools/add-comments.d.ts +69 -3
  13. package/dist/tools/add-comments.d.ts.map +1 -1
  14. package/dist/tools/add-projects.d.ts +34 -3
  15. package/dist/tools/add-projects.d.ts.map +1 -1
  16. package/dist/tools/add-sections.d.ts +14 -1
  17. package/dist/tools/add-sections.d.ts.map +1 -1
  18. package/dist/tools/add-tasks.d.ts +65 -10
  19. package/dist/tools/add-tasks.d.ts.map +1 -1
  20. package/dist/tools/complete-tasks.d.ts +20 -1
  21. package/dist/tools/complete-tasks.d.ts.map +1 -1
  22. package/dist/tools/delete-object.d.ts +16 -3
  23. package/dist/tools/delete-object.d.ts.map +1 -1
  24. package/dist/tools/fetch.d.ts +8 -1
  25. package/dist/tools/fetch.d.ts.map +1 -1
  26. package/dist/tools/find-activity.d.ts +44 -7
  27. package/dist/tools/find-activity.d.ts.map +1 -1
  28. package/dist/tools/find-comments.d.ts +69 -3
  29. package/dist/tools/find-comments.d.ts.map +1 -1
  30. package/dist/tools/find-completed-tasks.d.ts +63 -5
  31. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  32. package/dist/tools/find-project-collaborators.d.ts +33 -2
  33. package/dist/tools/find-project-collaborators.d.ts.map +1 -1
  34. package/dist/tools/find-projects.d.ts +35 -1
  35. package/dist/tools/find-projects.d.ts.map +1 -1
  36. package/dist/tools/find-sections.d.ts +15 -1
  37. package/dist/tools/find-sections.d.ts.map +1 -1
  38. package/dist/tools/find-tasks-by-date.d.ts +61 -3
  39. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  40. package/dist/tools/find-tasks.d.ts +63 -5
  41. package/dist/tools/find-tasks.d.ts.map +1 -1
  42. package/dist/tools/get-overview.d.ts +24 -1
  43. package/dist/tools/get-overview.d.ts.map +1 -1
  44. package/dist/tools/manage-assignments.d.ts +39 -2
  45. package/dist/tools/manage-assignments.d.ts.map +1 -1
  46. package/dist/tools/search.d.ts +17 -1
  47. package/dist/tools/search.d.ts.map +1 -1
  48. package/dist/tools/update-comments.d.ts +76 -3
  49. package/dist/tools/update-comments.d.ts.map +1 -1
  50. package/dist/tools/update-projects.d.ts +43 -1
  51. package/dist/tools/update-projects.d.ts.map +1 -1
  52. package/dist/tools/update-sections.d.ts +17 -3
  53. package/dist/tools/update-sections.d.ts.map +1 -1
  54. package/dist/tools/update-tasks.d.ts +79 -13
  55. package/dist/tools/update-tasks.d.ts.map +1 -1
  56. package/dist/tools/user-info.d.ts +19 -1
  57. package/dist/tools/user-info.d.ts.map +1 -1
  58. package/dist/utils/assignment-validator.d.ts +2 -2
  59. package/dist/utils/output-schemas.d.ts +233 -0
  60. package/dist/utils/output-schemas.d.ts.map +1 -0
  61. package/dist/utils/response-builders.d.ts +1 -3
  62. package/dist/utils/response-builders.d.ts.map +1 -1
  63. package/dist/utils/test-helpers.d.ts +1 -1
  64. package/dist/utils/user-resolver.d.ts +1 -1
  65. package/package.json +10 -8
  66. package/dist/filter-helpers.js +0 -79
  67. package/dist/mcp-helpers.js +0 -71
  68. package/dist/mcp-server.js +0 -142
  69. package/dist/todoist-tool.js +0 -1
  70. package/dist/tool-helpers.js +0 -125
  71. package/dist/tool-helpers.test.d.ts +0 -2
  72. package/dist/tool-helpers.test.d.ts.map +0 -1
  73. package/dist/tool-helpers.test.js +0 -223
  74. package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
  75. package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
  76. package/dist/tools/__tests__/add-comments.test.js +0 -241
  77. package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
  78. package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
  79. package/dist/tools/__tests__/add-projects.test.js +0 -174
  80. package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
  81. package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
  82. package/dist/tools/__tests__/add-sections.test.js +0 -185
  83. package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
  84. package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
  85. package/dist/tools/__tests__/add-tasks.test.js +0 -606
  86. package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
  87. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
  88. package/dist/tools/__tests__/assignment-integration.test.js +0 -428
  89. package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
  90. package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
  91. package/dist/tools/__tests__/complete-tasks.test.js +0 -206
  92. package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
  93. package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
  94. package/dist/tools/__tests__/delete-object.test.js +0 -110
  95. package/dist/tools/__tests__/fetch.test.d.ts +0 -2
  96. package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
  97. package/dist/tools/__tests__/fetch.test.js +0 -279
  98. package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
  99. package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
  100. package/dist/tools/__tests__/find-activity.test.js +0 -229
  101. package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
  102. package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
  103. package/dist/tools/__tests__/find-comments.test.js +0 -236
  104. package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
  105. package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
  106. package/dist/tools/__tests__/find-completed-tasks.test.js +0 -423
  107. package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
  108. package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
  109. package/dist/tools/__tests__/find-projects.test.js +0 -154
  110. package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
  111. package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
  112. package/dist/tools/__tests__/find-sections.test.js +0 -313
  113. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
  114. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
  115. package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
  116. package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
  117. package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
  118. package/dist/tools/__tests__/find-tasks.test.js +0 -771
  119. package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
  120. package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
  121. package/dist/tools/__tests__/get-overview.test.js +0 -225
  122. package/dist/tools/__tests__/search.test.d.ts +0 -2
  123. package/dist/tools/__tests__/search.test.d.ts.map +0 -1
  124. package/dist/tools/__tests__/search.test.js +0 -206
  125. package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
  126. package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
  127. package/dist/tools/__tests__/update-comments.test.js +0 -294
  128. package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
  129. package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
  130. package/dist/tools/__tests__/update-projects.test.js +0 -217
  131. package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
  132. package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
  133. package/dist/tools/__tests__/update-sections.test.js +0 -169
  134. package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
  135. package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
  136. package/dist/tools/__tests__/update-tasks.test.js +0 -788
  137. package/dist/tools/__tests__/user-info.test.d.ts +0 -2
  138. package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
  139. package/dist/tools/__tests__/user-info.test.js +0 -139
  140. package/dist/tools/add-comments.js +0 -89
  141. package/dist/tools/add-projects.js +0 -63
  142. package/dist/tools/add-sections.js +0 -74
  143. package/dist/tools/add-tasks.js +0 -169
  144. package/dist/tools/complete-tasks.js +0 -68
  145. package/dist/tools/delete-object.js +0 -79
  146. package/dist/tools/fetch.js +0 -102
  147. package/dist/tools/find-activity.js +0 -221
  148. package/dist/tools/find-comments.js +0 -148
  149. package/dist/tools/find-completed-tasks.js +0 -168
  150. package/dist/tools/find-project-collaborators.js +0 -151
  151. package/dist/tools/find-projects.js +0 -101
  152. package/dist/tools/find-sections.js +0 -101
  153. package/dist/tools/find-tasks-by-date.js +0 -198
  154. package/dist/tools/find-tasks.js +0 -329
  155. package/dist/tools/get-overview.js +0 -249
  156. package/dist/tools/manage-assignments.js +0 -337
  157. package/dist/tools/search.js +0 -65
  158. package/dist/tools/update-comments.js +0 -82
  159. package/dist/tools/update-projects.js +0 -84
  160. package/dist/tools/update-sections.js +0 -70
  161. package/dist/tools/update-tasks.js +0 -179
  162. package/dist/tools/user-info.js +0 -142
  163. package/dist/utils/assignment-validator.js +0 -253
  164. package/dist/utils/constants.js +0 -45
  165. package/dist/utils/duration-parser.js +0 -96
  166. package/dist/utils/duration-parser.test.d.ts +0 -2
  167. package/dist/utils/duration-parser.test.d.ts.map +0 -1
  168. package/dist/utils/duration-parser.test.js +0 -147
  169. package/dist/utils/labels.js +0 -18
  170. package/dist/utils/priorities.js +0 -20
  171. package/dist/utils/response-builders.js +0 -210
  172. package/dist/utils/sanitize-data.js +0 -37
  173. package/dist/utils/sanitize-data.test.d.ts +0 -2
  174. package/dist/utils/sanitize-data.test.d.ts.map +0 -1
  175. package/dist/utils/sanitize-data.test.js +0 -93
  176. package/dist/utils/test-helpers.js +0 -237
  177. package/dist/utils/tool-names.js +0 -40
  178. 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
- });