@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,528 +0,0 @@
1
- import { jest } from '@jest/globals';
2
- import { getTasksByFilter } from '../../tool-helpers.js';
3
- import { createMappedTask, createMockUser, extractStructuredContent, extractTextContent, TEST_ERRORS, TEST_IDS, } from '../../utils/test-helpers.js';
4
- import { ToolNames } from '../../utils/tool-names.js';
5
- import { resolveUserNameToId } from '../../utils/user-resolver.js';
6
- import { findTasksByDate } from '../find-tasks-by-date.js';
7
- // Mock only getTasksByFilter, use actual implementations for everything else
8
- jest.mock('../../tool-helpers', () => {
9
- const actual = jest.requireActual('../../tool-helpers');
10
- return {
11
- ...actual,
12
- getTasksByFilter: jest.fn(),
13
- };
14
- });
15
- // Mock user resolver
16
- jest.mock('../../utils/user-resolver', () => ({
17
- resolveUserNameToId: jest.fn(),
18
- }));
19
- const mockGetTasksByFilter = getTasksByFilter;
20
- const mockResolveUserNameToId = resolveUserNameToId;
21
- // Mock the Todoist API (not directly used by find-tasks-by-date, but needed for type)
22
- const mockTodoistApi = {
23
- getUser: jest.fn(),
24
- };
25
- // Mock the Todoist User
26
- const mockTodoistUser = createMockUser();
27
- // Mock date-fns functions to make tests deterministic
28
- jest.mock('date-fns', () => ({
29
- addDays: jest.fn((date, amount) => {
30
- const d = new Date(date);
31
- d.setDate(d.getDate() + amount);
32
- return d;
33
- }),
34
- formatISO: jest.fn((date, options) => {
35
- if (typeof date === 'string') {
36
- return date; // Return string dates as-is
37
- }
38
- if (options?.representation === 'date') {
39
- return date.toISOString().split('T')[0];
40
- }
41
- return date.toISOString();
42
- }),
43
- }));
44
- const { FIND_TASKS_BY_DATE, UPDATE_TASKS } = ToolNames;
45
- describe(`${FIND_TASKS_BY_DATE} tool`, () => {
46
- beforeEach(() => {
47
- jest.clearAllMocks();
48
- mockTodoistApi.getUser.mockResolvedValue(mockTodoistUser);
49
- // Mock current date to make tests deterministic
50
- jest.spyOn(Date, 'now').mockReturnValue(new Date('2025-08-15T10:00:00Z').getTime());
51
- });
52
- afterEach(() => {
53
- jest.restoreAllMocks();
54
- });
55
- describe('listing tasks by date range', () => {
56
- it('only returns tasks for the startDate when daysCount is 1', async () => {
57
- const mockTasks = [
58
- createMappedTask({ content: 'Task for specific date', dueDate: '2025-08-20' }),
59
- ];
60
- const mockResponse = { tasks: mockTasks, nextCursor: null };
61
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
62
- const result = await findTasksByDate.execute({ startDate: '2025-08-20', limit: 50, daysCount: 1 }, mockTodoistApi);
63
- // Verify the query uses daysCount=1 by checking the end date calculation
64
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
65
- client: mockTodoistApi,
66
- query: '(due after: 2025-08-20 | due: 2025-08-20) & due before: 2025-08-21 & !assigned to: others',
67
- cursor: undefined,
68
- limit: 50,
69
- });
70
- const textContent = extractTextContent(result);
71
- expect(textContent).toMatchSnapshot();
72
- });
73
- it('should get tasks for today when startDate is "today" (includes overdue)', async () => {
74
- const mockTasks = [createMappedTask({ content: 'Today task', dueDate: '2025-08-15' })];
75
- const mockResponse = { tasks: mockTasks, nextCursor: null };
76
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
77
- const result = await findTasksByDate.execute({ startDate: 'today', limit: 50, daysCount: 7 }, mockTodoistApi);
78
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
79
- client: mockTodoistApi,
80
- query: '(today | overdue) & !assigned to: others',
81
- cursor: undefined,
82
- limit: 50,
83
- });
84
- // Verify result is a concise summary
85
- expect(extractTextContent(result)).toMatchSnapshot();
86
- });
87
- it.each([
88
- {
89
- name: 'specific date',
90
- params: { startDate: '2025-08-20', limit: 50, daysCount: 7 },
91
- tasks: [createMappedTask({ content: 'Specific date task', dueDate: '2025-08-20' })],
92
- cursor: null,
93
- },
94
- {
95
- name: 'multiple days with pagination',
96
- params: {
97
- startDate: '2025-08-20',
98
- daysCount: 3,
99
- limit: 20,
100
- cursor: 'current-cursor',
101
- },
102
- tasks: [
103
- createMappedTask({
104
- id: TEST_IDS.TASK_2,
105
- content: 'Multi-day task 1',
106
- dueDate: '2025-08-20',
107
- }),
108
- createMappedTask({
109
- id: TEST_IDS.TASK_3,
110
- content: 'Multi-day task 2',
111
- dueDate: '2025-08-21',
112
- }),
113
- ],
114
- cursor: 'next-page-cursor',
115
- },
116
- ])('should handle $name', async ({ params, tasks, cursor }) => {
117
- const mockResponse = { tasks, nextCursor: cursor };
118
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
119
- const result = await findTasksByDate.execute(params, mockTodoistApi);
120
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
121
- client: mockTodoistApi,
122
- query: expect.stringContaining('2025-08-20'),
123
- cursor: params.cursor || undefined,
124
- limit: params.limit,
125
- });
126
- // Verify result is a concise summary
127
- expect(extractTextContent(result)).toMatchSnapshot();
128
- });
129
- });
130
- describe('pagination and limits', () => {
131
- it.each([
132
- {
133
- name: 'pagination parameters',
134
- params: {
135
- startDate: 'today',
136
- limit: 25,
137
- daysCount: 7,
138
- cursor: 'pagination-cursor',
139
- },
140
- expectedCursor: 'pagination-cursor',
141
- expectedLimit: 25,
142
- },
143
- {
144
- name: 'default values',
145
- params: { startDate: '2025-08-15', limit: 50, daysCount: 7 },
146
- expectedCursor: undefined,
147
- expectedLimit: 50,
148
- },
149
- ])('should handle $name', async ({ params, expectedCursor, expectedLimit }) => {
150
- const mockResponse = { tasks: [], nextCursor: null };
151
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
152
- await findTasksByDate.execute(params, mockTodoistApi);
153
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
154
- client: mockTodoistApi,
155
- query: expect.any(String),
156
- cursor: expectedCursor,
157
- limit: expectedLimit,
158
- });
159
- });
160
- });
161
- describe('edge cases', () => {
162
- it.each([
163
- { name: 'empty results', daysCount: 7, shouldReturnResult: true },
164
- { name: 'maximum daysCount', daysCount: 30, shouldReturnResult: false },
165
- { name: 'minimum daysCount', daysCount: 1, shouldReturnResult: false },
166
- ])('should handle $name', async ({ daysCount, shouldReturnResult }) => {
167
- const mockResponse = { tasks: [], nextCursor: null };
168
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
169
- const startDate = daysCount === 7 ? 'today' : '2025-08-15';
170
- const result = await findTasksByDate.execute({ startDate, limit: 50, daysCount }, mockTodoistApi);
171
- expect(mockGetTasksByFilter).toHaveBeenCalledTimes(1);
172
- if (shouldReturnResult) {
173
- // Verify result is a concise summary
174
- expect(extractTextContent(result)).toMatchSnapshot();
175
- }
176
- });
177
- });
178
- describe('next steps logic', () => {
179
- it('should suggest appropriate actions when hasOverdue is true', async () => {
180
- const mockTasks = [
181
- createMappedTask({
182
- id: TEST_IDS.TASK_1,
183
- content: 'Overdue task from list',
184
- dueDate: '2025-08-10', // Past date - creates hasOverdue context
185
- }),
186
- ];
187
- const mockResponse = { tasks: mockTasks, nextCursor: null };
188
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
189
- const result = await findTasksByDate.execute({
190
- startDate: '2025-08-15',
191
- limit: 10,
192
- daysCount: 1,
193
- }, mockTodoistApi);
194
- const textContent = extractTextContent(result);
195
- expect(textContent).toMatchSnapshot();
196
- expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
197
- });
198
- it('should suggest today-focused actions when startDate is today', async () => {
199
- const mockTasks = [
200
- createMappedTask({
201
- id: TEST_IDS.TASK_1,
202
- content: "Today's task",
203
- dueDate: '2025-08-15', // Today's date based on our mock
204
- }),
205
- ];
206
- const mockResponse = { tasks: mockTasks, nextCursor: null };
207
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
208
- const result = await findTasksByDate.execute({ startDate: 'today', limit: 10, daysCount: 1 }, mockTodoistApi);
209
- const textContent = extractTextContent(result);
210
- expect(textContent).toMatchSnapshot();
211
- expect(textContent).toContain(`Use ${UPDATE_TASKS} to modify priorities or due dates`);
212
- });
213
- it('should provide helpful suggestions for empty today results', async () => {
214
- const mockResponse = { tasks: [], nextCursor: null };
215
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
216
- const result = await findTasksByDate.execute({ startDate: 'today', limit: 10, daysCount: 1 }, mockTodoistApi);
217
- const textContent = extractTextContent(result);
218
- expect(textContent).toMatchSnapshot();
219
- expect(textContent).toContain('Great job! No tasks for today or overdue');
220
- });
221
- it('should provide helpful suggestions for empty date range results', async () => {
222
- const mockResponse = { tasks: [], nextCursor: null };
223
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
224
- const result = await findTasksByDate.execute({
225
- startDate: '2025-08-20',
226
- limit: 10,
227
- daysCount: 1,
228
- }, mockTodoistApi);
229
- const textContent = extractTextContent(result);
230
- expect(textContent).toMatchSnapshot();
231
- expect(textContent).toContain("Expand date range with larger 'daysCount'");
232
- expect(textContent).toContain("Check today's tasks with startDate='today'");
233
- });
234
- });
235
- describe('label filtering', () => {
236
- it.each([
237
- {
238
- name: 'single label with OR operator',
239
- params: {
240
- startDate: 'today',
241
- daysCount: 1,
242
- limit: 50,
243
- labels: ['work'],
244
- },
245
- expectedQueryPattern: '(today | overdue) & ((@work)) & !assigned to: others', // Will be combined with date query
246
- },
247
- {
248
- name: 'multiple labels with AND operator',
249
- params: {
250
- startDate: 'today',
251
- daysCount: 1,
252
- limit: 50,
253
- labels: ['work', 'urgent'],
254
- labelsOperator: 'and',
255
- },
256
- expectedQueryPattern: '(today | overdue) & ((@work & @urgent)) & !assigned to: others',
257
- },
258
- {
259
- name: 'multiple labels with OR operator',
260
- params: {
261
- startDate: '2025-08-20',
262
- daysCount: 3,
263
- limit: 50,
264
- labels: ['personal', 'shopping'],
265
- labelsOperator: 'or',
266
- },
267
- expectedQueryPattern: '((@personal | @shopping))',
268
- },
269
- ])('should filter tasks by labels: $name', async ({ params, expectedQueryPattern }) => {
270
- const mockTasks = [
271
- createMappedTask({
272
- id: TEST_IDS.TASK_1,
273
- content: 'Task with work label',
274
- labels: ['work'],
275
- dueDate: '2025-08-20',
276
- }),
277
- ];
278
- const mockResponse = { tasks: mockTasks, nextCursor: null };
279
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
280
- const result = await findTasksByDate.execute(params, mockTodoistApi);
281
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
282
- client: mockTodoistApi,
283
- query: expect.stringContaining('(@'),
284
- cursor: undefined,
285
- limit: 50,
286
- });
287
- // For today specifically, check the exact pattern
288
- if (params.startDate === 'today') {
289
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
290
- client: mockTodoistApi,
291
- query: expectedQueryPattern,
292
- cursor: undefined,
293
- limit: 50,
294
- });
295
- }
296
- const structuredContent = extractStructuredContent(result);
297
- expect(structuredContent.appliedFilters).toEqual(expect.objectContaining({
298
- labels: params.labels,
299
- ...(params.labelsOperator ? { labelsOperator: params.labelsOperator } : {}),
300
- }));
301
- });
302
- it('should handle empty labels array', async () => {
303
- const params = {
304
- startDate: 'today',
305
- daysCount: 1,
306
- limit: 50,
307
- };
308
- const mockResponse = { tasks: [], nextCursor: null };
309
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
310
- await findTasksByDate.execute(params, mockTodoistApi);
311
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
312
- client: mockTodoistApi,
313
- query: expect.not.stringContaining('@'),
314
- cursor: undefined,
315
- limit: 50,
316
- });
317
- });
318
- it('should combine date filters with label filters', async () => {
319
- const params = {
320
- startDate: '2025-08-15',
321
- daysCount: 1,
322
- limit: 25,
323
- labels: ['important'],
324
- };
325
- const mockTasks = [
326
- createMappedTask({
327
- content: 'Important task for specific date',
328
- labels: ['important'],
329
- dueDate: '2025-08-15',
330
- }),
331
- ];
332
- const mockResponse = { tasks: mockTasks, nextCursor: null };
333
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
334
- const result = await findTasksByDate.execute(params, mockTodoistApi);
335
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
336
- client: mockTodoistApi,
337
- query: expect.stringContaining('due after:') &&
338
- expect.stringContaining('(@important)'),
339
- cursor: undefined,
340
- limit: 25,
341
- });
342
- const textContent = extractTextContent(result);
343
- expect(textContent).toMatchSnapshot();
344
- });
345
- });
346
- describe('responsible user filtering', () => {
347
- it('should filter results to show only unassigned tasks or tasks assigned to current user', async () => {
348
- // Backend filtering: API should only return unassigned + assigned to me
349
- const mockTasks = [
350
- createMappedTask({
351
- id: TEST_IDS.TASK_1,
352
- content: 'My task',
353
- dueDate: '2025-08-15',
354
- responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
355
- }),
356
- createMappedTask({
357
- id: TEST_IDS.TASK_2,
358
- content: 'Unassigned task',
359
- dueDate: '2025-08-15',
360
- responsibleUid: null, // Unassigned
361
- }),
362
- ];
363
- const mockResponse = { tasks: mockTasks, nextCursor: null };
364
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
365
- const result = await findTasksByDate.execute({
366
- startDate: 'today',
367
- daysCount: 1,
368
- limit: 50,
369
- responsibleUserFiltering: 'unassignedOrMe',
370
- }, mockTodoistApi);
371
- // Verify the query includes the assignment filter
372
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
373
- client: mockTodoistApi,
374
- query: '(today | overdue) & !assigned to: others',
375
- cursor: undefined,
376
- limit: 50,
377
- });
378
- const structuredContent = extractStructuredContent(result);
379
- // Should only return tasks 1 and 2, not task 3
380
- expect(structuredContent.tasks).toHaveLength(2);
381
- expect(structuredContent.tasks.map((t) => t.id)).toEqual([
382
- TEST_IDS.TASK_1,
383
- TEST_IDS.TASK_2,
384
- ]);
385
- });
386
- it('should filter overdue results to show only unassigned tasks or tasks assigned to current user', async () => {
387
- // Backend filtering: API should only return unassigned + assigned to me
388
- const mockTasks = [
389
- createMappedTask({
390
- id: TEST_IDS.TASK_1,
391
- content: 'My overdue task',
392
- dueDate: '2025-08-10',
393
- responsibleUid: TEST_IDS.USER_ID, // Assigned to current user
394
- }),
395
- createMappedTask({
396
- id: TEST_IDS.TASK_2,
397
- content: 'Unassigned overdue task',
398
- dueDate: '2025-08-10',
399
- responsibleUid: null, // Unassigned
400
- }),
401
- ];
402
- const mockResponse = { tasks: mockTasks, nextCursor: null };
403
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
404
- const result = await findTasksByDate.execute({
405
- overdueOption: 'overdue-only',
406
- daysCount: 1,
407
- limit: 50,
408
- responsibleUserFiltering: 'unassignedOrMe',
409
- }, mockTodoistApi);
410
- // Verify the query includes the assignment filter
411
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
412
- client: mockTodoistApi,
413
- query: 'overdue & !assigned to: others',
414
- cursor: undefined,
415
- limit: 50,
416
- });
417
- const structuredContent = extractStructuredContent(result);
418
- // Should only return tasks 1 and 2, not task 3
419
- expect(structuredContent.tasks).toHaveLength(2);
420
- expect(structuredContent.tasks.map((t) => t.id)).toEqual([
421
- TEST_IDS.TASK_1,
422
- TEST_IDS.TASK_2,
423
- ]);
424
- });
425
- });
426
- describe('responsibleUser parameter', () => {
427
- it('should filter tasks by specific user email', async () => {
428
- mockResolveUserNameToId.mockResolvedValue({
429
- userId: 'user-123',
430
- displayName: 'John Doe',
431
- email: 'john@example.com',
432
- });
433
- const mockTasks = [
434
- createMappedTask({
435
- id: TEST_IDS.TASK_1,
436
- content: 'Task assigned to John',
437
- dueDate: '2025-08-15',
438
- responsibleUid: 'user-123',
439
- }),
440
- ];
441
- const mockResponse = { tasks: mockTasks, nextCursor: null };
442
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
443
- const result = await findTasksByDate.execute({
444
- startDate: 'today',
445
- daysCount: 1,
446
- limit: 50,
447
- responsibleUser: 'john@example.com',
448
- }, mockTodoistApi);
449
- expect(mockResolveUserNameToId).toHaveBeenCalledWith(mockTodoistApi, 'john@example.com');
450
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
451
- client: mockTodoistApi,
452
- query: '(today | overdue) & assigned to: john@example.com',
453
- cursor: undefined,
454
- limit: 50,
455
- });
456
- const textContent = extractTextContent(result);
457
- expect(textContent).toContain('assigned to john@example.com');
458
- expect(textContent).toMatchSnapshot();
459
- });
460
- it('should throw error when user cannot be resolved', async () => {
461
- mockResolveUserNameToId.mockResolvedValue(null);
462
- await expect(findTasksByDate.execute({
463
- startDate: 'today',
464
- daysCount: 1,
465
- limit: 50,
466
- responsibleUser: 'nonexistent@example.com',
467
- }, mockTodoistApi)).rejects.toThrow('Could not find user: "nonexistent@example.com". Make sure the user is a collaborator on a shared project.');
468
- });
469
- it('should combine responsibleUser with labels and date filters', async () => {
470
- mockResolveUserNameToId.mockResolvedValue({
471
- userId: 'user-789',
472
- displayName: 'Bob Wilson',
473
- email: 'bob@example.com',
474
- });
475
- const mockTasks = [
476
- createMappedTask({
477
- id: TEST_IDS.TASK_1,
478
- content: 'Important task for Bob',
479
- dueDate: '2025-08-20',
480
- responsibleUid: 'user-789',
481
- labels: ['urgent'],
482
- }),
483
- ];
484
- const mockResponse = { tasks: mockTasks, nextCursor: null };
485
- mockGetTasksByFilter.mockResolvedValue(mockResponse);
486
- await findTasksByDate.execute({
487
- startDate: '2025-08-20',
488
- daysCount: 1,
489
- limit: 50,
490
- responsibleUser: 'bob@example.com',
491
- labels: ['urgent'],
492
- }, mockTodoistApi);
493
- expect(mockGetTasksByFilter).toHaveBeenCalledWith({
494
- client: mockTodoistApi,
495
- query: expect.stringContaining('2025-08-20'),
496
- cursor: undefined,
497
- limit: 50,
498
- });
499
- const call = mockGetTasksByFilter.mock.calls[0]?.[0];
500
- expect(call?.query).toContain('(@urgent)');
501
- expect(call?.query).toContain('assigned to: bob@example.com');
502
- });
503
- });
504
- describe('error handling', () => {
505
- it.each([
506
- {
507
- error: TEST_ERRORS.INVALID_FILTER,
508
- params: { startDate: 'today', limit: 50, daysCount: 7 },
509
- },
510
- {
511
- error: TEST_ERRORS.API_RATE_LIMIT,
512
- params: { startDate: 'today', limit: 50, daysCount: 7 },
513
- },
514
- {
515
- error: TEST_ERRORS.INVALID_CURSOR,
516
- params: {
517
- startDate: '2025-08-15',
518
- limit: 50,
519
- daysCount: 7,
520
- cursor: 'invalid-cursor',
521
- },
522
- },
523
- ])('should propagate $error', async ({ error, params }) => {
524
- mockGetTasksByFilter.mockRejectedValue(new Error(error));
525
- await expect(findTasksByDate.execute(params, mockTodoistApi)).rejects.toThrow(error);
526
- });
527
- });
528
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=find-tasks.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"find-tasks.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-tasks.test.ts"],"names":[],"mappings":""}