@doist/todoist-ai 2.2.2 → 4.0.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 (202) hide show
  1. package/README.md +6 -14
  2. package/dist/index.d.ts +619 -250
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +45 -29
  5. package/dist/main.js +2 -1
  6. package/dist/mcp-helpers.d.ts +25 -3
  7. package/dist/mcp-helpers.d.ts.map +1 -1
  8. package/dist/mcp-helpers.js +37 -19
  9. package/dist/mcp-server.d.ts.map +1 -1
  10. package/dist/mcp-server.js +44 -28
  11. package/dist/tools/__tests__/add-comments.test.d.ts +2 -0
  12. package/dist/tools/__tests__/add-comments.test.d.ts.map +1 -0
  13. package/dist/tools/__tests__/add-comments.test.js +241 -0
  14. package/dist/tools/__tests__/add-projects.test.d.ts +2 -0
  15. package/dist/tools/__tests__/add-projects.test.d.ts.map +1 -0
  16. package/dist/tools/__tests__/add-projects.test.js +152 -0
  17. package/dist/tools/__tests__/add-sections.test.d.ts +2 -0
  18. package/dist/tools/__tests__/add-sections.test.d.ts.map +1 -0
  19. package/dist/tools/__tests__/add-sections.test.js +181 -0
  20. package/dist/tools/__tests__/add-tasks.test.d.ts +2 -0
  21. package/dist/tools/__tests__/add-tasks.test.d.ts.map +1 -0
  22. package/dist/tools/__tests__/{tasks-add-multiple.test.js → add-tasks.test.js} +89 -79
  23. package/dist/tools/__tests__/complete-tasks.test.d.ts +2 -0
  24. package/dist/tools/__tests__/complete-tasks.test.d.ts.map +1 -0
  25. package/dist/tools/__tests__/complete-tasks.test.js +206 -0
  26. package/dist/tools/__tests__/delete-object.test.d.ts +2 -0
  27. package/dist/tools/__tests__/delete-object.test.d.ts.map +1 -0
  28. package/dist/tools/__tests__/{delete-one.test.js → delete-object.test.js} +42 -22
  29. package/dist/tools/__tests__/find-comments.test.d.ts +2 -0
  30. package/dist/tools/__tests__/find-comments.test.d.ts.map +1 -0
  31. package/dist/tools/__tests__/find-comments.test.js +242 -0
  32. package/dist/tools/__tests__/find-completed-tasks.test.d.ts +2 -0
  33. package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +1 -0
  34. package/dist/tools/__tests__/{tasks-list-completed.test.js → find-completed-tasks.test.js} +13 -36
  35. package/dist/tools/__tests__/find-projects.test.d.ts +2 -0
  36. package/dist/tools/__tests__/find-projects.test.d.ts.map +1 -0
  37. package/dist/tools/__tests__/{projects-list.test.js → find-projects.test.js} +55 -39
  38. package/dist/tools/__tests__/find-sections.test.d.ts +2 -0
  39. package/dist/tools/__tests__/find-sections.test.d.ts.map +1 -0
  40. package/dist/tools/__tests__/{sections-search.test.js → find-sections.test.js} +64 -50
  41. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +2 -0
  42. package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +1 -0
  43. package/dist/tools/__tests__/{tasks-list-by-date.test.js → find-tasks-by-date.test.js} +96 -14
  44. package/dist/tools/__tests__/find-tasks.test.d.ts +2 -0
  45. package/dist/tools/__tests__/find-tasks.test.d.ts.map +1 -0
  46. package/dist/tools/__tests__/find-tasks.test.js +334 -0
  47. package/dist/tools/__tests__/get-overview.test.d.ts +2 -0
  48. package/dist/tools/__tests__/get-overview.test.d.ts.map +1 -0
  49. package/dist/tools/__tests__/{overview.test.js → get-overview.test.js} +77 -13
  50. package/dist/tools/__tests__/update-comments.test.d.ts +2 -0
  51. package/dist/tools/__tests__/update-comments.test.d.ts.map +1 -0
  52. package/dist/tools/__tests__/update-comments.test.js +296 -0
  53. package/dist/tools/__tests__/update-projects.test.d.ts +2 -0
  54. package/dist/tools/__tests__/update-projects.test.d.ts.map +1 -0
  55. package/dist/tools/__tests__/update-projects.test.js +205 -0
  56. package/dist/tools/__tests__/update-sections.test.d.ts +2 -0
  57. package/dist/tools/__tests__/update-sections.test.d.ts.map +1 -0
  58. package/dist/tools/__tests__/update-sections.test.js +156 -0
  59. package/dist/tools/__tests__/update-tasks.test.d.ts +2 -0
  60. package/dist/tools/__tests__/update-tasks.test.d.ts.map +1 -0
  61. package/dist/tools/__tests__/update-tasks.test.js +645 -0
  62. package/dist/tools/add-comments.d.ts +51 -0
  63. package/dist/tools/add-comments.d.ts.map +1 -0
  64. package/dist/tools/add-comments.js +79 -0
  65. package/dist/tools/add-projects.d.ts +50 -0
  66. package/dist/tools/add-projects.d.ts.map +1 -0
  67. package/dist/tools/add-projects.js +59 -0
  68. package/dist/tools/add-sections.d.ts +46 -0
  69. package/dist/tools/add-sections.d.ts.map +1 -0
  70. package/dist/tools/add-sections.js +61 -0
  71. package/dist/tools/add-tasks.d.ts +82 -0
  72. package/dist/tools/add-tasks.d.ts.map +1 -0
  73. package/dist/tools/add-tasks.js +96 -0
  74. package/dist/tools/complete-tasks.d.ts +40 -0
  75. package/dist/tools/complete-tasks.d.ts.map +1 -0
  76. package/dist/tools/complete-tasks.js +68 -0
  77. package/dist/tools/delete-object.d.ts +38 -0
  78. package/dist/tools/delete-object.d.ts.map +1 -0
  79. package/dist/tools/delete-object.js +79 -0
  80. package/dist/tools/find-comments.d.ts +46 -0
  81. package/dist/tools/find-comments.d.ts.map +1 -0
  82. package/dist/tools/find-comments.js +143 -0
  83. package/dist/tools/find-completed-tasks.d.ts +74 -0
  84. package/dist/tools/find-completed-tasks.d.ts.map +1 -0
  85. package/dist/tools/find-completed-tasks.js +112 -0
  86. package/dist/tools/find-projects.d.ts +53 -0
  87. package/dist/tools/find-projects.d.ts.map +1 -0
  88. package/dist/tools/find-projects.js +101 -0
  89. package/dist/tools/find-sections.d.ts +42 -0
  90. package/dist/tools/find-sections.d.ts.map +1 -0
  91. package/dist/tools/find-sections.js +96 -0
  92. package/dist/tools/find-tasks-by-date.d.ts +59 -0
  93. package/dist/tools/find-tasks-by-date.d.ts.map +1 -0
  94. package/dist/tools/find-tasks-by-date.js +121 -0
  95. package/dist/tools/find-tasks.d.ts +65 -0
  96. package/dist/tools/find-tasks.d.ts.map +1 -0
  97. package/dist/tools/find-tasks.js +182 -0
  98. package/dist/tools/get-overview.d.ts +67 -0
  99. package/dist/tools/get-overview.d.ts.map +1 -0
  100. package/dist/tools/{overview.js → get-overview.js} +66 -19
  101. package/dist/tools/update-comments.d.ts +50 -0
  102. package/dist/tools/update-comments.d.ts.map +1 -0
  103. package/dist/tools/update-comments.js +82 -0
  104. package/dist/tools/update-projects.d.ts +59 -0
  105. package/dist/tools/update-projects.d.ts.map +1 -0
  106. package/dist/tools/update-projects.js +84 -0
  107. package/dist/tools/update-sections.d.ts +47 -0
  108. package/dist/tools/update-sections.d.ts.map +1 -0
  109. package/dist/tools/update-sections.js +70 -0
  110. package/dist/tools/update-tasks.d.ts +94 -0
  111. package/dist/tools/update-tasks.d.ts.map +1 -0
  112. package/dist/tools/update-tasks.js +120 -0
  113. package/dist/utils/constants.d.ts +39 -0
  114. package/dist/utils/constants.d.ts.map +1 -0
  115. package/dist/utils/constants.js +41 -0
  116. package/dist/utils/response-builders.d.ts +88 -0
  117. package/dist/utils/response-builders.d.ts.map +1 -0
  118. package/dist/utils/response-builders.js +202 -0
  119. package/dist/{tools → utils}/test-helpers.d.ts +16 -0
  120. package/dist/utils/test-helpers.d.ts.map +1 -0
  121. package/dist/{tools → utils}/test-helpers.js +51 -0
  122. package/dist/utils/tool-names.d.ts +28 -0
  123. package/dist/utils/tool-names.d.ts.map +1 -0
  124. package/dist/utils/tool-names.js +31 -0
  125. package/package.json +1 -1
  126. package/dist/tools/__tests__/delete-one.test.d.ts +0 -2
  127. package/dist/tools/__tests__/delete-one.test.d.ts.map +0 -1
  128. package/dist/tools/__tests__/overview.test.d.ts +0 -2
  129. package/dist/tools/__tests__/overview.test.d.ts.map +0 -1
  130. package/dist/tools/__tests__/projects-list.test.d.ts +0 -2
  131. package/dist/tools/__tests__/projects-list.test.d.ts.map +0 -1
  132. package/dist/tools/__tests__/projects-manage.test.d.ts +0 -2
  133. package/dist/tools/__tests__/projects-manage.test.d.ts.map +0 -1
  134. package/dist/tools/__tests__/projects-manage.test.js +0 -106
  135. package/dist/tools/__tests__/sections-manage.test.d.ts +0 -2
  136. package/dist/tools/__tests__/sections-manage.test.d.ts.map +0 -1
  137. package/dist/tools/__tests__/sections-manage.test.js +0 -138
  138. package/dist/tools/__tests__/sections-search.test.d.ts +0 -2
  139. package/dist/tools/__tests__/sections-search.test.d.ts.map +0 -1
  140. package/dist/tools/__tests__/tasks-add-multiple.test.d.ts +0 -2
  141. package/dist/tools/__tests__/tasks-add-multiple.test.d.ts.map +0 -1
  142. package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts +0 -2
  143. package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts.map +0 -1
  144. package/dist/tools/__tests__/tasks-complete-multiple.test.js +0 -146
  145. package/dist/tools/__tests__/tasks-list-by-date.test.d.ts +0 -2
  146. package/dist/tools/__tests__/tasks-list-by-date.test.d.ts.map +0 -1
  147. package/dist/tools/__tests__/tasks-list-completed.test.d.ts +0 -2
  148. package/dist/tools/__tests__/tasks-list-completed.test.d.ts.map +0 -1
  149. package/dist/tools/__tests__/tasks-list-for-container.test.d.ts +0 -2
  150. package/dist/tools/__tests__/tasks-list-for-container.test.d.ts.map +0 -1
  151. package/dist/tools/__tests__/tasks-list-for-container.test.js +0 -232
  152. package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts +0 -2
  153. package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts.map +0 -1
  154. package/dist/tools/__tests__/tasks-organize-multiple.test.js +0 -245
  155. package/dist/tools/__tests__/tasks-search.test.d.ts +0 -2
  156. package/dist/tools/__tests__/tasks-search.test.d.ts.map +0 -1
  157. package/dist/tools/__tests__/tasks-search.test.js +0 -106
  158. package/dist/tools/__tests__/tasks-update-one.test.d.ts +0 -2
  159. package/dist/tools/__tests__/tasks-update-one.test.d.ts.map +0 -1
  160. package/dist/tools/__tests__/tasks-update-one.test.js +0 -251
  161. package/dist/tools/delete-one.d.ts +0 -17
  162. package/dist/tools/delete-one.d.ts.map +0 -1
  163. package/dist/tools/delete-one.js +0 -25
  164. package/dist/tools/overview.d.ts +0 -14
  165. package/dist/tools/overview.d.ts.map +0 -1
  166. package/dist/tools/projects-list.d.ts +0 -29
  167. package/dist/tools/projects-list.d.ts.map +0 -1
  168. package/dist/tools/projects-list.js +0 -39
  169. package/dist/tools/projects-manage.d.ts +0 -24
  170. package/dist/tools/projects-manage.d.ts.map +0 -1
  171. package/dist/tools/projects-manage.js +0 -26
  172. package/dist/tools/sections-manage.d.ts +0 -23
  173. package/dist/tools/sections-manage.d.ts.map +0 -1
  174. package/dist/tools/sections-manage.js +0 -37
  175. package/dist/tools/sections-search.d.ts +0 -18
  176. package/dist/tools/sections-search.d.ts.map +0 -1
  177. package/dist/tools/sections-search.js +0 -27
  178. package/dist/tools/tasks-add-multiple.d.ts +0 -55
  179. package/dist/tools/tasks-add-multiple.d.ts.map +0 -1
  180. package/dist/tools/tasks-add-multiple.js +0 -52
  181. package/dist/tools/tasks-complete-multiple.d.ts +0 -16
  182. package/dist/tools/tasks-complete-multiple.d.ts.map +0 -1
  183. package/dist/tools/tasks-complete-multiple.js +0 -23
  184. package/dist/tools/tasks-list-by-date.d.ts +0 -34
  185. package/dist/tools/tasks-list-by-date.d.ts.map +0 -1
  186. package/dist/tools/tasks-list-by-date.js +0 -53
  187. package/dist/tools/tasks-list-completed.d.ts +0 -44
  188. package/dist/tools/tasks-list-completed.d.ts.map +0 -1
  189. package/dist/tools/tasks-list-completed.js +0 -49
  190. package/dist/tools/tasks-list-for-container.d.ts +0 -34
  191. package/dist/tools/tasks-list-for-container.d.ts.map +0 -1
  192. package/dist/tools/tasks-list-for-container.js +0 -48
  193. package/dist/tools/tasks-organize-multiple.d.ts +0 -37
  194. package/dist/tools/tasks-organize-multiple.d.ts.map +0 -1
  195. package/dist/tools/tasks-organize-multiple.js +0 -34
  196. package/dist/tools/tasks-search.d.ts +0 -32
  197. package/dist/tools/tasks-search.d.ts.map +0 -1
  198. package/dist/tools/tasks-search.js +0 -30
  199. package/dist/tools/tasks-update-one.d.ts +0 -29
  200. package/dist/tools/tasks-update-one.d.ts.map +0 -1
  201. package/dist/tools/tasks-update-one.js +0 -63
  202. package/dist/tools/test-helpers.d.ts.map +0 -1
@@ -0,0 +1,206 @@
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(expect.objectContaining({
28
+ completed: ['task-1', 'task-2', 'task-3'],
29
+ failures: [],
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(expect.objectContaining({
44
+ completed: ['8485093748'],
45
+ failures: [],
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('overdue')");
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
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=delete-object.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delete-object.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/delete-object.test.ts"],"names":[],"mappings":""}
@@ -1,85 +1,105 @@
1
1
  import { jest } from '@jest/globals';
2
- import { deleteOne } from '../delete-one.js';
2
+ import { extractTextContent } from '../../utils/test-helpers.js';
3
+ import { ToolNames } from '../../utils/tool-names.js';
4
+ import { deleteObject } from '../delete-object.js';
3
5
  // Mock the Todoist API
4
6
  const mockTodoistApi = {
5
7
  deleteProject: jest.fn(),
6
8
  deleteSection: jest.fn(),
7
9
  deleteTask: jest.fn(),
8
10
  };
9
- describe('delete-one tool', () => {
11
+ const { FIND_PROJECTS, FIND_TASKS_BY_DATE, DELETE_OBJECT } = ToolNames;
12
+ describe(`${DELETE_OBJECT} tool`, () => {
10
13
  beforeEach(() => {
11
14
  jest.clearAllMocks();
12
15
  });
13
16
  describe('deleting projects', () => {
14
17
  it('should delete a project by ID', async () => {
15
18
  mockTodoistApi.deleteProject.mockResolvedValue(true);
16
- const result = await deleteOne.execute({ type: 'project', id: '6cfCcrrCFg2xP94Q' }, mockTodoistApi);
17
- // Verify API was called correctly
19
+ const result = await deleteObject.execute({ type: 'project', id: '6cfCcrrCFg2xP94Q' }, mockTodoistApi);
18
20
  expect(mockTodoistApi.deleteProject).toHaveBeenCalledWith('6cfCcrrCFg2xP94Q');
19
21
  expect(mockTodoistApi.deleteSection).not.toHaveBeenCalled();
20
22
  expect(mockTodoistApi.deleteTask).not.toHaveBeenCalled();
21
- // Verify success response
22
- expect(result).toEqual({ success: true });
23
+ const textContent = extractTextContent(result);
24
+ expect(textContent).toMatchSnapshot();
25
+ expect(textContent).toContain('Deleted project: id=6cfCcrrCFg2xP94Q');
26
+ expect(textContent).toContain(`Use ${FIND_PROJECTS} to see remaining projects`);
27
+ expect(result.structuredContent).toEqual({
28
+ deletedEntity: {
29
+ type: 'project',
30
+ id: '6cfCcrrCFg2xP94Q',
31
+ },
32
+ success: true,
33
+ });
23
34
  });
24
35
  it('should propagate project deletion errors', async () => {
25
36
  const apiError = new Error('API Error: Cannot delete project with tasks');
26
37
  mockTodoistApi.deleteProject.mockRejectedValue(apiError);
27
- await expect(deleteOne.execute({ type: 'project', id: 'project-with-tasks' }, mockTodoistApi)).rejects.toThrow('API Error: Cannot delete project with tasks');
38
+ await expect(deleteObject.execute({ type: 'project', id: 'project-with-tasks' }, mockTodoistApi)).rejects.toThrow('API Error: Cannot delete project with tasks');
28
39
  });
29
40
  });
30
41
  describe('deleting sections', () => {
31
42
  it('should delete a section by ID', async () => {
32
43
  mockTodoistApi.deleteSection.mockResolvedValue(true);
33
- const result = await deleteOne.execute({ type: 'section', id: 'section-123' }, mockTodoistApi);
34
- // Verify API was called correctly
44
+ const result = await deleteObject.execute({ type: 'section', id: 'section-123' }, mockTodoistApi);
35
45
  expect(mockTodoistApi.deleteSection).toHaveBeenCalledWith('section-123');
36
46
  expect(mockTodoistApi.deleteProject).not.toHaveBeenCalled();
37
47
  expect(mockTodoistApi.deleteTask).not.toHaveBeenCalled();
38
- // Verify success response
39
- expect(result).toEqual({ success: true });
48
+ const textContent = extractTextContent(result);
49
+ expect(textContent).toMatchSnapshot();
50
+ expect(textContent).toContain('Deleted section: id=section-123');
51
+ expect(textContent).toContain(`Use ${ToolNames.FIND_SECTIONS} to see remaining sections`);
52
+ expect(result.structuredContent).toEqual({
53
+ deletedEntity: { type: 'section', id: 'section-123' },
54
+ success: true,
55
+ });
40
56
  });
41
57
  it('should propagate section deletion errors', async () => {
42
58
  const apiError = new Error('API Error: Section not found');
43
59
  mockTodoistApi.deleteSection.mockRejectedValue(apiError);
44
- await expect(deleteOne.execute({ type: 'section', id: 'non-existent-section' }, mockTodoistApi)).rejects.toThrow('API Error: Section not found');
60
+ await expect(deleteObject.execute({ type: 'section', id: 'non-existent-section' }, mockTodoistApi)).rejects.toThrow('API Error: Section not found');
45
61
  });
46
62
  });
47
63
  describe('deleting tasks', () => {
48
64
  it('should delete a task by ID', async () => {
49
65
  mockTodoistApi.deleteTask.mockResolvedValue(true);
50
- const result = await deleteOne.execute({ type: 'task', id: '8485093748' }, mockTodoistApi);
51
- // Verify API was called correctly
66
+ const result = await deleteObject.execute({ type: 'task', id: '8485093748' }, mockTodoistApi);
52
67
  expect(mockTodoistApi.deleteTask).toHaveBeenCalledWith('8485093748');
53
68
  expect(mockTodoistApi.deleteProject).not.toHaveBeenCalled();
54
69
  expect(mockTodoistApi.deleteSection).not.toHaveBeenCalled();
55
- // Verify success response
56
- expect(result).toEqual({ success: true });
70
+ const textContent = extractTextContent(result);
71
+ expect(textContent).toMatchSnapshot();
72
+ expect(textContent).toContain('Deleted task: id=8485093748');
73
+ expect(textContent).toContain(`Use ${FIND_TASKS_BY_DATE} to see remaining tasks`);
74
+ expect(result.structuredContent).toEqual({
75
+ deletedEntity: { type: 'task', id: '8485093748' },
76
+ success: true,
77
+ });
57
78
  });
58
79
  it('should propagate task deletion errors', async () => {
59
80
  const apiError = new Error('API Error: Task not found');
60
81
  mockTodoistApi.deleteTask.mockRejectedValue(apiError);
61
- await expect(deleteOne.execute({ type: 'task', id: 'non-existent-task' }, mockTodoistApi)).rejects.toThrow('API Error: Task not found');
82
+ await expect(deleteObject.execute({ type: 'task', id: 'non-existent-task' }, mockTodoistApi)).rejects.toThrow('API Error: Task not found');
62
83
  });
63
84
  it('should handle permission errors', async () => {
64
85
  const apiError = new Error('API Error: Insufficient permissions to delete task');
65
86
  mockTodoistApi.deleteTask.mockRejectedValue(apiError);
66
- await expect(deleteOne.execute({ type: 'task', id: 'restricted-task' }, mockTodoistApi)).rejects.toThrow('API Error: Insufficient permissions to delete task');
87
+ await expect(deleteObject.execute({ type: 'task', id: 'restricted-task' }, mockTodoistApi)).rejects.toThrow('API Error: Insufficient permissions to delete task');
67
88
  });
68
89
  });
69
90
  describe('type validation', () => {
70
91
  it('should handle all supported entity types', async () => {
71
- // Test all three supported types work correctly
72
92
  mockTodoistApi.deleteProject.mockResolvedValue(true);
73
93
  mockTodoistApi.deleteSection.mockResolvedValue(true);
74
94
  mockTodoistApi.deleteTask.mockResolvedValue(true);
75
95
  // Delete project
76
- await deleteOne.execute({ type: 'project', id: 'proj-1' }, mockTodoistApi);
96
+ await deleteObject.execute({ type: 'project', id: 'proj-1' }, mockTodoistApi);
77
97
  expect(mockTodoistApi.deleteProject).toHaveBeenCalledWith('proj-1');
78
98
  // Delete section
79
- await deleteOne.execute({ type: 'section', id: 'sect-1' }, mockTodoistApi);
99
+ await deleteObject.execute({ type: 'section', id: 'sect-1' }, mockTodoistApi);
80
100
  expect(mockTodoistApi.deleteSection).toHaveBeenCalledWith('sect-1');
81
101
  // Delete task
82
- await deleteOne.execute({ type: 'task', id: 'task-1' }, mockTodoistApi);
102
+ await deleteObject.execute({ type: 'task', id: 'task-1' }, mockTodoistApi);
83
103
  expect(mockTodoistApi.deleteTask).toHaveBeenCalledWith('task-1');
84
104
  // Verify each API method was called exactly once
85
105
  expect(mockTodoistApi.deleteProject).toHaveBeenCalledTimes(1);
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=find-comments.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-comments.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-comments.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,242 @@
1
+ import { jest } from '@jest/globals';
2
+ import { extractStructuredContent, extractTextContent } from '../../utils/test-helpers.js';
3
+ import { ToolNames } from '../../utils/tool-names.js';
4
+ import { findComments } from '../find-comments.js';
5
+ // Mock the Todoist API
6
+ const mockTodoistApi = {
7
+ getComment: jest.fn(),
8
+ getComments: jest.fn(),
9
+ };
10
+ const { FIND_COMMENTS } = ToolNames;
11
+ const createMockComment = (overrides = {}) => ({
12
+ id: '12345',
13
+ content: 'Test comment content',
14
+ postedAt: '2024-01-01T12:00:00Z',
15
+ postedUid: 'user123',
16
+ taskId: 'task123',
17
+ projectId: undefined,
18
+ fileAttachment: null,
19
+ uidsToNotify: null,
20
+ reactions: null,
21
+ isDeleted: false,
22
+ ...overrides,
23
+ });
24
+ describe(`${FIND_COMMENTS} tool`, () => {
25
+ beforeEach(() => {
26
+ jest.clearAllMocks();
27
+ });
28
+ describe('finding comments by task', () => {
29
+ it('should find comments for a task', async () => {
30
+ const mockComments = [
31
+ createMockComment({ id: '1', content: 'First comment', taskId: 'task123' }),
32
+ createMockComment({ id: '2', content: 'Second comment', taskId: 'task123' }),
33
+ ];
34
+ mockTodoistApi.getComments.mockResolvedValue({
35
+ results: mockComments,
36
+ nextCursor: null,
37
+ });
38
+ const result = await findComments.execute({
39
+ taskId: 'task123',
40
+ }, mockTodoistApi);
41
+ expect(mockTodoistApi.getComments).toHaveBeenCalledWith({
42
+ taskId: 'task123',
43
+ cursor: null,
44
+ limit: 10,
45
+ });
46
+ // Verify result is a concise summary
47
+ expect(extractTextContent(result)).toMatchSnapshot();
48
+ // Verify structured content
49
+ const structuredContent = extractStructuredContent(result);
50
+ expect(structuredContent).toEqual(expect.objectContaining({
51
+ comments: expect.arrayContaining([
52
+ expect.objectContaining({ id: '1', content: 'First comment' }),
53
+ expect.objectContaining({ id: '2', content: 'Second comment' }),
54
+ ]),
55
+ searchType: 'task',
56
+ searchId: 'task123',
57
+ hasMore: false,
58
+ nextCursor: null,
59
+ totalCount: 2,
60
+ }));
61
+ });
62
+ it('should handle pagination', async () => {
63
+ const mockComments = [createMockComment({ id: '1', content: 'Comment 1' })];
64
+ mockTodoistApi.getComments.mockResolvedValue({
65
+ results: mockComments,
66
+ nextCursor: 'next_page_token',
67
+ });
68
+ const result = await findComments.execute({
69
+ taskId: 'task123',
70
+ limit: 1,
71
+ cursor: 'current_cursor',
72
+ }, mockTodoistApi);
73
+ expect(mockTodoistApi.getComments).toHaveBeenCalledWith({
74
+ taskId: 'task123',
75
+ cursor: 'current_cursor',
76
+ limit: 1,
77
+ });
78
+ // Verify result includes pagination info
79
+ expect(extractTextContent(result)).toMatchSnapshot();
80
+ // Verify structured content includes pagination
81
+ const structuredContent = extractStructuredContent(result);
82
+ expect(structuredContent).toEqual(expect.objectContaining({
83
+ comments: expect.arrayContaining([
84
+ expect.objectContaining({ id: '1', content: 'Comment 1' }),
85
+ ]),
86
+ searchType: 'task',
87
+ searchId: 'task123',
88
+ hasMore: true,
89
+ nextCursor: 'next_page_token',
90
+ totalCount: 1,
91
+ }));
92
+ });
93
+ });
94
+ describe('finding comments by project', () => {
95
+ it('should find comments for a project', async () => {
96
+ const mockComments = [
97
+ createMockComment({
98
+ id: '1',
99
+ content: 'Project comment',
100
+ taskId: undefined,
101
+ projectId: 'project456',
102
+ }),
103
+ ];
104
+ mockTodoistApi.getComments.mockResolvedValue({
105
+ results: mockComments,
106
+ nextCursor: null,
107
+ });
108
+ const result = await findComments.execute({
109
+ projectId: 'project456',
110
+ }, mockTodoistApi);
111
+ expect(mockTodoistApi.getComments).toHaveBeenCalledWith({
112
+ projectId: 'project456',
113
+ cursor: null,
114
+ limit: 10,
115
+ });
116
+ // Verify result is a concise summary
117
+ expect(extractTextContent(result)).toMatchSnapshot();
118
+ // Verify structured content
119
+ const structuredContent = extractStructuredContent(result);
120
+ expect(structuredContent).toEqual(expect.objectContaining({
121
+ comments: expect.arrayContaining([
122
+ expect.objectContaining({
123
+ id: '1',
124
+ content: 'Project comment',
125
+ projectId: 'project456',
126
+ }),
127
+ ]),
128
+ searchType: 'project',
129
+ searchId: 'project456',
130
+ hasMore: false,
131
+ nextCursor: null,
132
+ totalCount: 1,
133
+ }));
134
+ });
135
+ });
136
+ describe('finding single comment', () => {
137
+ it('should find comment by ID', async () => {
138
+ const mockComment = createMockComment({
139
+ id: 'comment789',
140
+ content: 'Single comment content',
141
+ taskId: 'task123',
142
+ });
143
+ mockTodoistApi.getComment.mockResolvedValue(mockComment);
144
+ const result = await findComments.execute({
145
+ commentId: 'comment789',
146
+ }, mockTodoistApi);
147
+ expect(mockTodoistApi.getComment).toHaveBeenCalledWith('comment789');
148
+ // Verify result is a concise summary
149
+ expect(extractTextContent(result)).toMatchSnapshot();
150
+ // Verify structured content
151
+ const structuredContent = extractStructuredContent(result);
152
+ expect(structuredContent).toEqual(expect.objectContaining({
153
+ comments: expect.arrayContaining([
154
+ expect.objectContaining({
155
+ id: 'comment789',
156
+ content: 'Single comment content',
157
+ taskId: 'task123',
158
+ fileAttachment: null,
159
+ }),
160
+ ]),
161
+ searchType: 'single',
162
+ searchId: 'comment789',
163
+ hasMore: false,
164
+ nextCursor: null,
165
+ totalCount: 1,
166
+ }));
167
+ });
168
+ it('should handle comment with attachment', async () => {
169
+ const mockComment = createMockComment({
170
+ id: 'comment789',
171
+ content: 'Comment with file',
172
+ fileAttachment: {
173
+ resourceType: 'file',
174
+ fileName: 'document.pdf',
175
+ fileUrl: 'https://example.com/document.pdf',
176
+ fileType: 'application/pdf',
177
+ },
178
+ });
179
+ mockTodoistApi.getComment.mockResolvedValue(mockComment);
180
+ const result = await findComments.execute({
181
+ commentId: 'comment789',
182
+ }, mockTodoistApi);
183
+ // Verify result includes attachment info
184
+ expect(extractTextContent(result)).toMatchSnapshot();
185
+ // Verify structured content includes attachment
186
+ const structuredContent = extractStructuredContent(result);
187
+ expect(structuredContent).toEqual(expect.objectContaining({
188
+ comments: expect.arrayContaining([
189
+ expect.objectContaining({
190
+ id: 'comment789',
191
+ content: 'Comment with file',
192
+ fileAttachment: expect.objectContaining({
193
+ resourceType: 'file',
194
+ fileName: 'document.pdf',
195
+ fileUrl: 'https://example.com/document.pdf',
196
+ fileType: 'application/pdf',
197
+ }),
198
+ }),
199
+ ]),
200
+ searchType: 'single',
201
+ searchId: 'comment789',
202
+ hasMore: false,
203
+ nextCursor: null,
204
+ totalCount: 1,
205
+ }));
206
+ });
207
+ });
208
+ describe('validation', () => {
209
+ it('should throw error when no search parameter provided', async () => {
210
+ await expect(findComments.execute({}, mockTodoistApi)).rejects.toThrow('Must provide exactly one of: taskId, projectId, or commentId.');
211
+ });
212
+ it('should throw error when multiple search parameters provided', async () => {
213
+ await expect(findComments.execute({
214
+ taskId: 'task123',
215
+ projectId: 'project456',
216
+ }, mockTodoistApi)).rejects.toThrow('Cannot provide multiple search parameters.');
217
+ });
218
+ });
219
+ describe('empty results', () => {
220
+ it('should handle no comments found', async () => {
221
+ mockTodoistApi.getComments.mockResolvedValue({
222
+ results: [],
223
+ nextCursor: null,
224
+ });
225
+ const result = await findComments.execute({
226
+ taskId: 'task123',
227
+ }, mockTodoistApi);
228
+ // Verify result handles empty case
229
+ expect(extractTextContent(result)).toMatchSnapshot();
230
+ // Verify structured content
231
+ const structuredContent = extractStructuredContent(result);
232
+ expect(structuredContent).toEqual(expect.objectContaining({
233
+ comments: [],
234
+ searchType: 'task',
235
+ searchId: 'task123',
236
+ hasMore: false,
237
+ nextCursor: null,
238
+ totalCount: 0,
239
+ }));
240
+ });
241
+ });
242
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=find-completed-tasks.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"find-completed-tasks.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-completed-tasks.test.ts"],"names":[],"mappings":""}