@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.
Files changed (167) hide show
  1. package/dist/filter-helpers.d.ts +1 -1
  2. package/dist/index.d.ts +175 -175
  3. package/dist/index.js +61 -81
  4. package/dist/main.js +15 -23
  5. package/dist/mcp-helpers.d.ts +4 -4
  6. package/dist/mcp-server-6tm7Rhyz.js +2840 -0
  7. package/dist/todoist-tool.d.ts +2 -2
  8. package/dist/tool-helpers.d.ts +1 -1
  9. package/dist/tools/add-comments.d.ts +1 -1
  10. package/dist/tools/add-comments.d.ts.map +1 -1
  11. package/dist/tools/add-projects.d.ts +4 -4
  12. package/dist/tools/add-projects.d.ts.map +1 -1
  13. package/dist/tools/add-sections.d.ts +1 -1
  14. package/dist/tools/add-sections.d.ts.map +1 -1
  15. package/dist/tools/add-tasks.d.ts +4 -4
  16. package/dist/tools/add-tasks.d.ts.map +1 -1
  17. package/dist/tools/complete-tasks.d.ts +1 -1
  18. package/dist/tools/complete-tasks.d.ts.map +1 -1
  19. package/dist/tools/delete-object.d.ts +3 -3
  20. package/dist/tools/delete-object.d.ts.map +1 -1
  21. package/dist/tools/fetch.d.ts +1 -1
  22. package/dist/tools/find-activity.d.ts +5 -5
  23. package/dist/tools/find-activity.d.ts.map +1 -1
  24. package/dist/tools/find-comments.d.ts +2 -2
  25. package/dist/tools/find-comments.d.ts.map +1 -1
  26. package/dist/tools/find-completed-tasks.d.ts +3 -3
  27. package/dist/tools/find-completed-tasks.d.ts.map +1 -1
  28. package/dist/tools/find-project-collaborators.d.ts +2 -2
  29. package/dist/tools/find-projects.d.ts +1 -1
  30. package/dist/tools/find-projects.d.ts.map +1 -1
  31. package/dist/tools/find-sections.d.ts +1 -1
  32. package/dist/tools/find-sections.d.ts.map +1 -1
  33. package/dist/tools/find-tasks-by-date.d.ts +1 -1
  34. package/dist/tools/find-tasks-by-date.d.ts.map +1 -1
  35. package/dist/tools/find-tasks.d.ts +3 -3
  36. package/dist/tools/find-tasks.d.ts.map +1 -1
  37. package/dist/tools/get-overview.d.ts +1 -1
  38. package/dist/tools/manage-assignments.d.ts +1 -1
  39. package/dist/tools/search.d.ts +1 -1
  40. package/dist/tools/update-comments.d.ts +4 -4
  41. package/dist/tools/update-comments.d.ts.map +1 -1
  42. package/dist/tools/update-projects.d.ts +1 -1
  43. package/dist/tools/update-projects.d.ts.map +1 -1
  44. package/dist/tools/update-sections.d.ts +4 -4
  45. package/dist/tools/update-sections.d.ts.map +1 -1
  46. package/dist/tools/update-tasks.d.ts +7 -7
  47. package/dist/tools/update-tasks.d.ts.map +1 -1
  48. package/dist/tools/user-info.d.ts +1 -1
  49. package/dist/utils/assignment-validator.d.ts +2 -2
  50. package/dist/utils/response-builders.d.ts +1 -3
  51. package/dist/utils/response-builders.d.ts.map +1 -1
  52. package/dist/utils/test-helpers.d.ts +1 -1
  53. package/dist/utils/user-resolver.d.ts +1 -1
  54. package/package.json +11 -9
  55. package/dist/filter-helpers.js +0 -79
  56. package/dist/mcp-helpers.js +0 -71
  57. package/dist/mcp-server.js +0 -142
  58. package/dist/todoist-tool.js +0 -1
  59. package/dist/tool-helpers.js +0 -125
  60. package/dist/tool-helpers.test.d.ts +0 -2
  61. package/dist/tool-helpers.test.d.ts.map +0 -1
  62. package/dist/tool-helpers.test.js +0 -223
  63. package/dist/tools/__tests__/add-comments.test.d.ts +0 -2
  64. package/dist/tools/__tests__/add-comments.test.d.ts.map +0 -1
  65. package/dist/tools/__tests__/add-comments.test.js +0 -241
  66. package/dist/tools/__tests__/add-projects.test.d.ts +0 -2
  67. package/dist/tools/__tests__/add-projects.test.d.ts.map +0 -1
  68. package/dist/tools/__tests__/add-projects.test.js +0 -174
  69. package/dist/tools/__tests__/add-sections.test.d.ts +0 -2
  70. package/dist/tools/__tests__/add-sections.test.d.ts.map +0 -1
  71. package/dist/tools/__tests__/add-sections.test.js +0 -185
  72. package/dist/tools/__tests__/add-tasks.test.d.ts +0 -2
  73. package/dist/tools/__tests__/add-tasks.test.d.ts.map +0 -1
  74. package/dist/tools/__tests__/add-tasks.test.js +0 -533
  75. package/dist/tools/__tests__/assignment-integration.test.d.ts +0 -2
  76. package/dist/tools/__tests__/assignment-integration.test.d.ts.map +0 -1
  77. package/dist/tools/__tests__/assignment-integration.test.js +0 -428
  78. package/dist/tools/__tests__/complete-tasks.test.d.ts +0 -2
  79. package/dist/tools/__tests__/complete-tasks.test.d.ts.map +0 -1
  80. package/dist/tools/__tests__/complete-tasks.test.js +0 -206
  81. package/dist/tools/__tests__/delete-object.test.d.ts +0 -2
  82. package/dist/tools/__tests__/delete-object.test.d.ts.map +0 -1
  83. package/dist/tools/__tests__/delete-object.test.js +0 -110
  84. package/dist/tools/__tests__/fetch.test.d.ts +0 -2
  85. package/dist/tools/__tests__/fetch.test.d.ts.map +0 -1
  86. package/dist/tools/__tests__/fetch.test.js +0 -279
  87. package/dist/tools/__tests__/find-activity.test.d.ts +0 -2
  88. package/dist/tools/__tests__/find-activity.test.d.ts.map +0 -1
  89. package/dist/tools/__tests__/find-activity.test.js +0 -229
  90. package/dist/tools/__tests__/find-comments.test.d.ts +0 -2
  91. package/dist/tools/__tests__/find-comments.test.d.ts.map +0 -1
  92. package/dist/tools/__tests__/find-comments.test.js +0 -236
  93. package/dist/tools/__tests__/find-completed-tasks.test.d.ts +0 -2
  94. package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +0 -1
  95. package/dist/tools/__tests__/find-completed-tasks.test.js +0 -324
  96. package/dist/tools/__tests__/find-projects.test.d.ts +0 -2
  97. package/dist/tools/__tests__/find-projects.test.d.ts.map +0 -1
  98. package/dist/tools/__tests__/find-projects.test.js +0 -154
  99. package/dist/tools/__tests__/find-sections.test.d.ts +0 -2
  100. package/dist/tools/__tests__/find-sections.test.d.ts.map +0 -1
  101. package/dist/tools/__tests__/find-sections.test.js +0 -245
  102. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +0 -2
  103. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +0 -1
  104. package/dist/tools/__tests__/find-tasks-by-date.test.js +0 -528
  105. package/dist/tools/__tests__/find-tasks.test.d.ts +0 -2
  106. package/dist/tools/__tests__/find-tasks.test.d.ts.map +0 -1
  107. package/dist/tools/__tests__/find-tasks.test.js +0 -771
  108. package/dist/tools/__tests__/get-overview.test.d.ts +0 -2
  109. package/dist/tools/__tests__/get-overview.test.d.ts.map +0 -1
  110. package/dist/tools/__tests__/get-overview.test.js +0 -225
  111. package/dist/tools/__tests__/search.test.d.ts +0 -2
  112. package/dist/tools/__tests__/search.test.d.ts.map +0 -1
  113. package/dist/tools/__tests__/search.test.js +0 -206
  114. package/dist/tools/__tests__/update-comments.test.d.ts +0 -2
  115. package/dist/tools/__tests__/update-comments.test.d.ts.map +0 -1
  116. package/dist/tools/__tests__/update-comments.test.js +0 -294
  117. package/dist/tools/__tests__/update-projects.test.d.ts +0 -2
  118. package/dist/tools/__tests__/update-projects.test.d.ts.map +0 -1
  119. package/dist/tools/__tests__/update-projects.test.js +0 -217
  120. package/dist/tools/__tests__/update-sections.test.d.ts +0 -2
  121. package/dist/tools/__tests__/update-sections.test.d.ts.map +0 -1
  122. package/dist/tools/__tests__/update-sections.test.js +0 -169
  123. package/dist/tools/__tests__/update-tasks.test.d.ts +0 -2
  124. package/dist/tools/__tests__/update-tasks.test.d.ts.map +0 -1
  125. package/dist/tools/__tests__/update-tasks.test.js +0 -788
  126. package/dist/tools/__tests__/user-info.test.d.ts +0 -2
  127. package/dist/tools/__tests__/user-info.test.d.ts.map +0 -1
  128. package/dist/tools/__tests__/user-info.test.js +0 -139
  129. package/dist/tools/add-comments.js +0 -79
  130. package/dist/tools/add-projects.js +0 -63
  131. package/dist/tools/add-sections.js +0 -61
  132. package/dist/tools/add-tasks.js +0 -160
  133. package/dist/tools/complete-tasks.js +0 -68
  134. package/dist/tools/delete-object.js +0 -79
  135. package/dist/tools/fetch.js +0 -102
  136. package/dist/tools/find-activity.js +0 -221
  137. package/dist/tools/find-comments.js +0 -143
  138. package/dist/tools/find-completed-tasks.js +0 -161
  139. package/dist/tools/find-project-collaborators.js +0 -151
  140. package/dist/tools/find-projects.js +0 -101
  141. package/dist/tools/find-sections.js +0 -96
  142. package/dist/tools/find-tasks-by-date.js +0 -198
  143. package/dist/tools/find-tasks.js +0 -329
  144. package/dist/tools/get-overview.js +0 -249
  145. package/dist/tools/manage-assignments.js +0 -337
  146. package/dist/tools/search.js +0 -65
  147. package/dist/tools/update-comments.js +0 -82
  148. package/dist/tools/update-projects.js +0 -84
  149. package/dist/tools/update-sections.js +0 -70
  150. package/dist/tools/update-tasks.js +0 -170
  151. package/dist/tools/user-info.js +0 -142
  152. package/dist/utils/assignment-validator.js +0 -253
  153. package/dist/utils/constants.js +0 -45
  154. package/dist/utils/duration-parser.js +0 -96
  155. package/dist/utils/duration-parser.test.d.ts +0 -2
  156. package/dist/utils/duration-parser.test.d.ts.map +0 -1
  157. package/dist/utils/duration-parser.test.js +0 -147
  158. package/dist/utils/labels.js +0 -18
  159. package/dist/utils/priorities.js +0 -20
  160. package/dist/utils/response-builders.js +0 -210
  161. package/dist/utils/sanitize-data.js +0 -37
  162. package/dist/utils/sanitize-data.test.d.ts +0 -2
  163. package/dist/utils/sanitize-data.test.d.ts.map +0 -1
  164. package/dist/utils/sanitize-data.test.js +0 -93
  165. package/dist/utils/test-helpers.js +0 -237
  166. package/dist/utils/tool-names.js +0 -40
  167. package/dist/utils/user-resolver.js +0 -179
@@ -1,428 +0,0 @@
1
- import { createMockProject, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
2
- import { addTasks } from '../add-tasks.js';
3
- import { findProjectCollaborators } from '../find-project-collaborators.js';
4
- import { manageAssignments } from '../manage-assignments.js';
5
- import { updateTasks } from '../update-tasks.js';
6
- // Mock the assignment validator
7
- jest.mock('../../utils/assignment-validator.js', () => ({
8
- assignmentValidator: {
9
- validateTaskCreationAssignment: jest.fn(),
10
- validateTaskUpdateAssignment: jest.fn(),
11
- validateBulkAssignment: jest.fn(),
12
- },
13
- }));
14
- // Mock the user resolver
15
- jest.mock('../../utils/user-resolver.js', () => ({
16
- userResolver: {
17
- resolveUser: jest.fn(),
18
- getProjectCollaborators: jest.fn(),
19
- },
20
- }));
21
- describe('Assignment Integration Tests', () => {
22
- let mockTodoistApi;
23
- const mockValidUser = {
24
- userId: 'user-123',
25
- name: 'John Doe',
26
- email: 'john@example.com',
27
- };
28
- const mockTask = {
29
- id: 'task-123',
30
- content: 'Test task',
31
- projectId: 'project-123',
32
- sectionId: null,
33
- parentId: null,
34
- priority: 1,
35
- labels: [],
36
- description: '',
37
- url: 'https://todoist.com/showTask?id=task-123',
38
- noteCount: 0,
39
- addedByUid: 'creator-123',
40
- addedAt: new Date().toISOString(),
41
- deadline: null,
42
- responsibleUid: null,
43
- assignedByUid: null,
44
- isCollapsed: false,
45
- isDeleted: false,
46
- duration: null,
47
- checked: false,
48
- updatedAt: new Date().toISOString(),
49
- due: null,
50
- dayOrder: 0,
51
- userId: 'creator-123',
52
- completedAt: null,
53
- childOrder: 1,
54
- };
55
- const mockProject = createMockProject({
56
- id: 'project-123',
57
- name: 'Test Project',
58
- color: 'blue',
59
- isShared: true,
60
- canAssignTasks: true,
61
- url: 'https://todoist.com/showProject?id=project-123',
62
- });
63
- beforeEach(() => {
64
- jest.clearAllMocks();
65
- mockTodoistApi = {
66
- addTask: jest.fn(),
67
- updateTask: jest.fn(),
68
- getTask: jest.fn(),
69
- getProjects: jest.fn(),
70
- getProject: jest.fn(),
71
- };
72
- // Mock assignment validator responses
73
- const mockAssignmentValidator = require('../../utils/assignment-validator.js').assignmentValidator;
74
- mockAssignmentValidator.validateTaskCreationAssignment.mockResolvedValue({
75
- isValid: true,
76
- resolvedUser: mockValidUser,
77
- });
78
- mockAssignmentValidator.validateTaskUpdateAssignment.mockResolvedValue({
79
- isValid: true,
80
- resolvedUser: mockValidUser,
81
- });
82
- mockAssignmentValidator.validateBulkAssignment.mockResolvedValue([
83
- { isValid: true, resolvedUser: mockValidUser },
84
- { isValid: true, resolvedUser: mockValidUser },
85
- { isValid: true, resolvedUser: mockValidUser },
86
- ]);
87
- // Mock user resolver
88
- const mockUserResolver = require('../../utils/user-resolver.js').userResolver;
89
- mockUserResolver.resolveUser.mockResolvedValue(mockValidUser);
90
- mockUserResolver.getProjectCollaborators.mockResolvedValue([
91
- { id: 'user-123', name: 'John Doe', email: 'john@example.com' },
92
- { id: 'user-456', name: 'Jane Smith', email: 'jane@example.com' },
93
- ]);
94
- // Mock API responses
95
- mockTodoistApi.getProjects.mockResolvedValue({
96
- results: [mockProject],
97
- nextCursor: null,
98
- });
99
- mockTodoistApi.getProject.mockResolvedValue(mockProject);
100
- mockTodoistApi.addTask.mockResolvedValue({ ...mockTask, responsibleUid: 'user-123' });
101
- mockTodoistApi.updateTask.mockResolvedValue({ ...mockTask, responsibleUid: 'user-123' });
102
- mockTodoistApi.getTask.mockResolvedValue(mockTask);
103
- });
104
- describe('Task Creation with Assignment', () => {
105
- it('should assign task during creation', async () => {
106
- const result = await addTasks.execute({
107
- tasks: [
108
- {
109
- content: 'New assigned task',
110
- projectId: 'project-123',
111
- responsibleUser: 'john@example.com',
112
- },
113
- ],
114
- }, mockTodoistApi);
115
- expect(mockTodoistApi.addTask).toHaveBeenCalledWith(expect.objectContaining({
116
- content: 'New assigned task',
117
- projectId: 'project-123',
118
- assigneeId: 'user-123', // Should be resolved user ID
119
- }));
120
- expect(extractTextContent(result)).toContain('Added 1 task');
121
- });
122
- it('should validate assignment before creating task', async () => {
123
- const mockAssignmentValidator = require('../../utils/assignment-validator.js').assignmentValidator;
124
- mockAssignmentValidator.validateTaskCreationAssignment.mockResolvedValueOnce({
125
- isValid: false,
126
- error: {
127
- message: 'User not found in project collaborators',
128
- suggestions: ['Use find-project-collaborators to see valid assignees'],
129
- },
130
- });
131
- await expect(addTasks.execute({
132
- tasks: [
133
- {
134
- content: 'Invalid assignment task',
135
- projectId: 'project-123',
136
- responsibleUser: 'nonexistent@example.com',
137
- },
138
- ],
139
- }, mockTodoistApi)).rejects.toThrow('Task "Invalid assignment task": User not found in project collaborators. Use find-project-collaborators to see valid assignees');
140
- expect(mockTodoistApi.addTask).not.toHaveBeenCalled();
141
- });
142
- it('should handle assignment for subtasks', async () => {
143
- mockTodoistApi.getTask.mockResolvedValueOnce({
144
- ...mockTask,
145
- id: 'parent-123',
146
- projectId: 'project-123',
147
- });
148
- await addTasks.execute({
149
- tasks: [
150
- {
151
- content: 'Subtask with assignment',
152
- parentId: 'parent-123',
153
- responsibleUser: 'john@example.com',
154
- },
155
- ],
156
- }, mockTodoistApi);
157
- expect(mockTodoistApi.addTask).toHaveBeenCalledWith(expect.objectContaining({
158
- content: 'Subtask with assignment',
159
- parentId: 'parent-123',
160
- assigneeId: 'user-123',
161
- }));
162
- });
163
- });
164
- describe('Task Update with Assignment', () => {
165
- it('should update task assignment', async () => {
166
- const result = await updateTasks.execute({
167
- tasks: [
168
- {
169
- id: 'task-123',
170
- responsibleUser: 'jane@example.com',
171
- },
172
- ],
173
- }, mockTodoistApi);
174
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-123', expect.objectContaining({
175
- assigneeId: 'user-123',
176
- }));
177
- expect(extractTextContent(result)).toContain('Updated 1 task');
178
- });
179
- it('should unassign task when responsibleUser is "unassign"', async () => {
180
- await updateTasks.execute({
181
- tasks: [
182
- {
183
- id: 'task-123',
184
- responsibleUser: 'unassign',
185
- },
186
- ],
187
- }, mockTodoistApi);
188
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-123', expect.objectContaining({
189
- assigneeId: null,
190
- }));
191
- });
192
- it('should unassign task when responsibleUser is null (backward compatibility)', async () => {
193
- await updateTasks.execute({
194
- tasks: [
195
- {
196
- id: 'task-123',
197
- responsibleUser: null,
198
- },
199
- ],
200
- }, mockTodoistApi);
201
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-123', expect.objectContaining({
202
- assigneeId: null,
203
- }));
204
- });
205
- it('should validate assignment changes', async () => {
206
- const mockAssignmentValidator = require('../../utils/assignment-validator.js').assignmentValidator;
207
- mockAssignmentValidator.validateTaskUpdateAssignment.mockResolvedValueOnce({
208
- isValid: false,
209
- error: {
210
- message: 'User cannot be assigned to this project',
211
- },
212
- });
213
- await expect(updateTasks.execute({
214
- tasks: [
215
- {
216
- id: 'task-123',
217
- responsibleUser: 'invalid@example.com',
218
- },
219
- ],
220
- }, mockTodoistApi)).rejects.toThrow('Task task-123: User cannot be assigned to this project');
221
- expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
222
- });
223
- });
224
- describe('Bulk Assignment Operations', () => {
225
- beforeEach(() => {
226
- mockTodoistApi.getTask
227
- .mockResolvedValueOnce({ ...mockTask, id: 'task-1' })
228
- .mockResolvedValueOnce({ ...mockTask, id: 'task-2' })
229
- .mockResolvedValueOnce({ ...mockTask, id: 'task-3' });
230
- });
231
- it('should perform bulk assignment', async () => {
232
- const result = await manageAssignments.execute({
233
- operation: 'assign',
234
- taskIds: ['task-1', 'task-2', 'task-3'],
235
- responsibleUser: 'john@example.com',
236
- dryRun: false,
237
- }, mockTodoistApi);
238
- expect(mockTodoistApi.updateTask).toHaveBeenCalledTimes(3);
239
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-1', {
240
- assigneeId: 'user-123',
241
- });
242
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-2', {
243
- assigneeId: 'user-123',
244
- });
245
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-3', {
246
- assigneeId: 'user-123',
247
- });
248
- expect(extractTextContent(result)).toContain('3 tasks were successfully assigned');
249
- });
250
- it('should perform bulk unassignment', async () => {
251
- const result = await manageAssignments.execute({
252
- operation: 'unassign',
253
- taskIds: ['task-1', 'task-2'],
254
- dryRun: false,
255
- }, mockTodoistApi);
256
- expect(mockTodoistApi.updateTask).toHaveBeenCalledTimes(2);
257
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-1', {
258
- assigneeId: null,
259
- });
260
- expect(mockTodoistApi.updateTask).toHaveBeenCalledWith('task-2', {
261
- assigneeId: null,
262
- });
263
- expect(extractTextContent(result)).toContain('2 tasks were successfully unassigned');
264
- });
265
- it('should handle dry-run mode', async () => {
266
- // Mock validation for 2 tasks
267
- const mockAssignmentValidator = require('../../utils/assignment-validator.js').assignmentValidator;
268
- mockAssignmentValidator.validateBulkAssignment.mockResolvedValueOnce([
269
- { isValid: true, resolvedUser: mockValidUser },
270
- { isValid: true, resolvedUser: mockValidUser },
271
- ]);
272
- const result = await manageAssignments.execute({
273
- operation: 'assign',
274
- taskIds: ['task-1', 'task-2'],
275
- responsibleUser: 'john@example.com',
276
- dryRun: true,
277
- }, mockTodoistApi);
278
- expect(mockTodoistApi.updateTask).not.toHaveBeenCalled();
279
- expect(extractTextContent(result)).toContain('Dry Run: Bulk assign operation');
280
- expect(extractTextContent(result)).toContain('2 tasks would be successfully assigned');
281
- });
282
- it('should handle mixed success and failure results', async () => {
283
- // Mock validation for 3 tasks - 2 valid, 1 invalid
284
- const mockAssignmentValidator = require('../../utils/assignment-validator.js').assignmentValidator;
285
- mockAssignmentValidator.validateBulkAssignment.mockResolvedValueOnce([
286
- { isValid: true, resolvedUser: mockValidUser },
287
- { isValid: false, error: { message: 'API Error' } },
288
- { isValid: true, resolvedUser: mockValidUser },
289
- ]);
290
- mockTodoistApi.updateTask
291
- .mockResolvedValueOnce({ ...mockTask, id: 'task-1' })
292
- .mockResolvedValueOnce({ ...mockTask, id: 'task-3' });
293
- const result = await manageAssignments.execute({
294
- operation: 'assign',
295
- taskIds: ['task-1', 'task-2', 'task-3'],
296
- responsibleUser: 'john@example.com',
297
- dryRun: false,
298
- }, mockTodoistApi);
299
- expect(extractTextContent(result)).toContain('2 tasks were successfully assigned');
300
- expect(extractTextContent(result)).toContain('1 task failed');
301
- expect(extractTextContent(result)).toContain('API Error');
302
- });
303
- });
304
- describe('Project Collaborators Discovery', () => {
305
- it('should find project collaborators', async () => {
306
- const result = await findProjectCollaborators.execute({
307
- projectId: 'project-123',
308
- }, mockTodoistApi);
309
- expect(extractTextContent(result)).toContain('Project collaborators');
310
- expect(extractTextContent(result)).toContain('John Doe (john@example.com)');
311
- expect(extractTextContent(result)).toContain('Jane Smith (jane@example.com)');
312
- expect(extractStructuredContent(result).collaborators).toHaveLength(2);
313
- });
314
- it('should filter collaborators by search term', async () => {
315
- const result = await findProjectCollaborators.execute({
316
- projectId: 'project-123',
317
- searchTerm: 'John',
318
- }, mockTodoistApi);
319
- expect(extractTextContent(result)).toContain('matching "John"');
320
- });
321
- it('should handle non-shared projects', async () => {
322
- mockTodoistApi.getProject.mockResolvedValueOnce({ ...mockProject, isShared: false });
323
- const result = await findProjectCollaborators.execute({
324
- projectId: 'project-123',
325
- }, mockTodoistApi);
326
- expect(extractTextContent(result)).toContain('is not shared and has no collaborators');
327
- expect(extractStructuredContent(result).collaborators).toBeUndefined(); // Empty arrays are removed
328
- });
329
- it('should handle project not found', async () => {
330
- mockTodoistApi.getProject.mockRejectedValueOnce(new Error('Project not found'));
331
- await expect(findProjectCollaborators.execute({
332
- projectId: 'nonexistent-project',
333
- }, mockTodoistApi)).rejects.toThrow('Failed to access project "nonexistent-project"');
334
- });
335
- });
336
- describe('Error Handling and Edge Cases', () => {
337
- it('should handle assignment validation errors gracefully', async () => {
338
- const mockAssignmentValidator = require('../../utils/assignment-validator.js').assignmentValidator;
339
- mockAssignmentValidator.validateTaskCreationAssignment.mockResolvedValueOnce({
340
- isValid: false,
341
- error: {
342
- message: 'Project not shared',
343
- suggestions: ['Share the project to enable assignments'],
344
- },
345
- });
346
- await expect(addTasks.execute({
347
- tasks: [
348
- {
349
- content: 'Task in unshared project',
350
- projectId: 'project-123',
351
- responsibleUser: 'john@example.com',
352
- },
353
- ],
354
- }, mockTodoistApi)).rejects.toThrow('Task "Task in unshared project": Project not shared. Share the project to enable assignments');
355
- });
356
- it('should handle inbox assignment restriction', async () => {
357
- await expect(addTasks.execute({
358
- tasks: [
359
- {
360
- content: 'Inbox task with assignment',
361
- responsibleUser: 'john@example.com',
362
- },
363
- ],
364
- }, mockTodoistApi)).rejects.toThrow('Task "Inbox task with assignment": Cannot assign tasks without specifying project context. Please specify a projectId, sectionId, or parentId.');
365
- });
366
- it('should handle parent task not found', async () => {
367
- mockTodoistApi.getTask.mockRejectedValueOnce(new Error('Task not found'));
368
- await expect(addTasks.execute({
369
- tasks: [
370
- {
371
- content: 'Subtask with bad parent',
372
- parentId: 'nonexistent-parent',
373
- responsibleUser: 'john@example.com',
374
- },
375
- ],
376
- }, mockTodoistApi)).rejects.toThrow('Task "Subtask with bad parent": Parent task "nonexistent-parent" not found');
377
- });
378
- it('should require responsibleUser for assign operations', async () => {
379
- await expect(manageAssignments.execute({
380
- operation: 'assign',
381
- taskIds: ['task-1'],
382
- dryRun: false,
383
- }, mockTodoistApi)).rejects.toThrow('assign operation requires responsibleUser parameter');
384
- });
385
- it('should require responsibleUser for reassign operations', async () => {
386
- await expect(manageAssignments.execute({
387
- operation: 'reassign',
388
- taskIds: ['task-1'],
389
- dryRun: false,
390
- }, mockTodoistApi)).rejects.toThrow('reassign operation requires responsibleUser parameter');
391
- });
392
- });
393
- describe('End-to-End Assignment Workflows', () => {
394
- it('should support complete assignment lifecycle', async () => {
395
- // 1. Create assigned task
396
- const createResult = await addTasks.execute({
397
- tasks: [
398
- {
399
- content: 'Task for lifecycle test',
400
- projectId: 'project-123',
401
- responsibleUser: 'john@example.com',
402
- },
403
- ],
404
- }, mockTodoistApi);
405
- expect(extractTextContent(createResult)).toContain('Added 1 task');
406
- // 2. Update assignment
407
- const updateResult = await updateTasks.execute({
408
- tasks: [
409
- {
410
- id: 'task-123',
411
- responsibleUser: 'jane@example.com',
412
- },
413
- ],
414
- }, mockTodoistApi);
415
- expect(extractTextContent(updateResult)).toContain('Updated 1 task');
416
- // 3. Unassign task
417
- const unassignResult = await updateTasks.execute({
418
- tasks: [
419
- {
420
- id: 'task-123',
421
- responsibleUser: null,
422
- },
423
- ],
424
- }, mockTodoistApi);
425
- expect(extractTextContent(unassignResult)).toContain('Updated 1 task');
426
- });
427
- });
428
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=complete-tasks.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"complete-tasks.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/complete-tasks.test.ts"],"names":[],"mappings":""}
@@ -1,206 +0,0 @@
1
- import { jest } from '@jest/globals';
2
- import { extractTextContent } from '../../utils/test-helpers.js';
3
- import { ToolNames } from '../../utils/tool-names.js';
4
- import { completeTasks } from '../complete-tasks.js';
5
- // Mock the Todoist API
6
- const mockTodoistApi = {
7
- closeTask: jest.fn(),
8
- };
9
- const { COMPLETE_TASKS, GET_OVERVIEW } = ToolNames;
10
- describe(`${COMPLETE_TASKS} tool`, () => {
11
- beforeEach(() => {
12
- jest.clearAllMocks();
13
- });
14
- describe('completing multiple tasks', () => {
15
- it('should complete all tasks successfully', async () => {
16
- mockTodoistApi.closeTask.mockResolvedValue(true);
17
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
18
- // Verify API was called for each task
19
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
20
- expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(1, 'task-1');
21
- expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(2, 'task-2');
22
- expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(3, 'task-3');
23
- // Verify result is a concise summary
24
- expect(extractTextContent(result)).toMatchSnapshot();
25
- // Verify structured content
26
- const { structuredContent } = result;
27
- expect(structuredContent).toEqual({
28
- completed: ['task-1', 'task-2', 'task-3'],
29
- // failures array is removed when empty
30
- totalRequested: 3,
31
- successCount: 3,
32
- failureCount: 0,
33
- });
34
- });
35
- it('should complete single task', async () => {
36
- mockTodoistApi.closeTask.mockResolvedValue(true);
37
- const result = await completeTasks.execute({ ids: ['8485093748'] }, mockTodoistApi);
38
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(1);
39
- expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('8485093748');
40
- expect(extractTextContent(result)).toMatchSnapshot();
41
- // Verify structured content
42
- const { structuredContent } = result;
43
- expect(structuredContent).toEqual({
44
- completed: ['8485093748'],
45
- // failures array is removed when empty
46
- totalRequested: 1,
47
- successCount: 1,
48
- failureCount: 0,
49
- });
50
- });
51
- it('should handle partial failures gracefully', async () => {
52
- // Mock first and third tasks to succeed, second to fail
53
- mockTodoistApi.closeTask
54
- .mockResolvedValueOnce(true) // task-1 succeeds
55
- .mockRejectedValueOnce(new Error('Task not found')) // task-2 fails
56
- .mockResolvedValueOnce(true); // task-3 succeeds
57
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
58
- // Verify API was called for all tasks despite failure
59
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
60
- expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(1, 'task-1');
61
- expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(2, 'task-2');
62
- expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(3, 'task-3');
63
- // Verify only successful completions are reported
64
- expect(extractTextContent(result)).toMatchSnapshot();
65
- // Verify structured content with partial failures
66
- const { structuredContent } = result;
67
- expect(structuredContent).toEqual(expect.objectContaining({
68
- completed: ['task-1', 'task-3'],
69
- failures: [
70
- expect.objectContaining({
71
- item: 'task-2',
72
- error: 'Task not found',
73
- }),
74
- ],
75
- totalRequested: 3,
76
- successCount: 2,
77
- failureCount: 1,
78
- }));
79
- });
80
- it('should handle all tasks failing', async () => {
81
- const apiError = new Error('API Error: Network timeout');
82
- mockTodoistApi.closeTask.mockRejectedValue(apiError);
83
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2'] }, mockTodoistApi);
84
- // Verify API was attempted for all tasks
85
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(2);
86
- // Verify no tasks were completed but still returns success
87
- expect(extractTextContent(result)).toMatchSnapshot();
88
- });
89
- it('should continue processing remaining tasks after failures', async () => {
90
- // Mock various failure scenarios
91
- mockTodoistApi.closeTask
92
- .mockRejectedValueOnce(new Error('Task already completed'))
93
- .mockRejectedValueOnce(new Error('Task not found'))
94
- .mockResolvedValueOnce(true) // task-3 succeeds
95
- .mockRejectedValueOnce(new Error('Permission denied'))
96
- .mockResolvedValueOnce(true); // task-5 succeeds
97
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3', 'task-4', 'task-5'] }, mockTodoistApi);
98
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(5);
99
- // Only tasks 3 and 5 should be in completed list
100
- expect(extractTextContent(result)).toMatchSnapshot();
101
- });
102
- it('should handle different types of API errors', async () => {
103
- mockTodoistApi.closeTask
104
- .mockRejectedValueOnce(new Error('Task not found'))
105
- .mockRejectedValueOnce(new Error('Task already completed'))
106
- .mockRejectedValueOnce(new Error('Permission denied'))
107
- .mockRejectedValueOnce(new Error('Rate limit exceeded'));
108
- const result = await completeTasks.execute({ ids: ['not-found', 'already-done', 'no-permission', 'rate-limited'] }, mockTodoistApi);
109
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(4);
110
- // All should fail, but the tool should handle it gracefully
111
- expect(extractTextContent(result)).toMatchSnapshot();
112
- });
113
- });
114
- describe('mixed success and failure scenarios', () => {
115
- it('should handle realistic mixed scenario', async () => {
116
- // Simulate a realistic scenario with some tasks completing and others failing
117
- mockTodoistApi.closeTask
118
- .mockResolvedValueOnce(true) // regular task completion
119
- .mockResolvedValueOnce(true) // another successful completion
120
- .mockRejectedValueOnce(new Error('Task already completed')) // duplicate completion
121
- .mockResolvedValueOnce(true) // successful completion
122
- .mockRejectedValueOnce(new Error('Task not found')); // deleted task
123
- const result = await completeTasks.execute({
124
- ids: [
125
- '8485093748', // regular task
126
- '8485093749', // regular task
127
- '8485093750', // already completed
128
- '8485093751', // regular task
129
- '8485093752', // deleted task
130
- ],
131
- }, mockTodoistApi);
132
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(5);
133
- expect(extractTextContent(result)).toMatchSnapshot();
134
- });
135
- });
136
- describe('next steps logic validation', () => {
137
- it('should suggest overdue tasks when all tasks complete successfully', async () => {
138
- mockTodoistApi.closeTask.mockResolvedValue(true);
139
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2'] }, mockTodoistApi);
140
- const textContent = extractTextContent(result);
141
- expect(textContent).toMatchSnapshot();
142
- expect(textContent).toContain("Use find-tasks-by-date('today')");
143
- });
144
- it('should suggest reviewing failures when mixed results', async () => {
145
- mockTodoistApi.closeTask
146
- .mockResolvedValueOnce(true)
147
- .mockRejectedValueOnce(new Error('Task not found'));
148
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2'] }, mockTodoistApi);
149
- const textContent = extractTextContent(result);
150
- expect(textContent).toMatchSnapshot();
151
- expect(textContent).toContain('Review failed completions and retry if needed');
152
- });
153
- it('should suggest checking IDs when all tasks fail', async () => {
154
- mockTodoistApi.closeTask.mockRejectedValue(new Error('Task not found'));
155
- const result = await completeTasks.execute({ ids: ['bad-id-1', 'bad-id-2'] }, mockTodoistApi);
156
- const textContent = extractTextContent(result);
157
- expect(textContent).toMatchSnapshot();
158
- expect(textContent).toContain('Check task IDs and permissions, then retry');
159
- expect(textContent).not.toContain(`Use ${GET_OVERVIEW} tool`); // Should only show retry message
160
- });
161
- });
162
- describe('error message truncation', () => {
163
- it('should truncate failure messages after 3 errors', async () => {
164
- mockTodoistApi.closeTask
165
- .mockRejectedValueOnce(new Error('Error 1'))
166
- .mockRejectedValueOnce(new Error('Error 2'))
167
- .mockRejectedValueOnce(new Error('Error 3'))
168
- .mockRejectedValueOnce(new Error('Error 4'))
169
- .mockRejectedValueOnce(new Error('Error 5'));
170
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3', 'task-4', 'task-5'] }, mockTodoistApi);
171
- const textContent = extractTextContent(result);
172
- expect(textContent).toMatchSnapshot();
173
- expect(textContent).toContain('+2 more'); // 5 total failures, showing first 3, so +2 more
174
- expect(textContent).not.toContain('Error 4'); // Should not show 4th error
175
- expect(textContent).not.toContain('Error 5'); // Should not show 5th error
176
- });
177
- it('should not show truncation message for exactly 3 errors', async () => {
178
- mockTodoistApi.closeTask
179
- .mockRejectedValueOnce(new Error('Error 1'))
180
- .mockRejectedValueOnce(new Error('Error 2'))
181
- .mockRejectedValueOnce(new Error('Error 3'));
182
- const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
183
- const textContent = extractTextContent(result);
184
- expect(textContent).toMatchSnapshot();
185
- expect(textContent).not.toContain('more'); // Should not show truncation
186
- });
187
- });
188
- describe('edge cases', () => {
189
- it('should handle empty task completion (minimum one task required by schema)', async () => {
190
- // Note: This test documents that the schema requires at least one task,
191
- // so this scenario shouldn't occur in practice due to validation
192
- mockTodoistApi.closeTask.mockResolvedValue(true);
193
- const result = await completeTasks.execute({ ids: ['single-task'] }, mockTodoistApi);
194
- expect(extractTextContent(result)).toMatchSnapshot();
195
- });
196
- it('should handle tasks with special ID formats', async () => {
197
- mockTodoistApi.closeTask.mockResolvedValue(true);
198
- const result = await completeTasks.execute({ ids: ['proj_123_task_456', 'task-with-dashes', '1234567890'] }, mockTodoistApi);
199
- expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
200
- expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('proj_123_task_456');
201
- expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('task-with-dashes');
202
- expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('1234567890');
203
- expect(extractTextContent(result)).toMatchSnapshot();
204
- });
205
- });
206
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=delete-object.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"delete-object.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/delete-object.test.ts"],"names":[],"mappings":""}