@doist/todoist-ai 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/index.d.ts +18 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -48
- package/dist/main.js +6 -11
- package/dist/mcp-helpers.d.ts +2 -2
- package/dist/mcp-helpers.d.ts.map +1 -1
- package/dist/mcp-helpers.js +1 -4
- package/dist/mcp-server.d.ts +1 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +34 -36
- package/dist/todoist-tool.js +1 -2
- package/dist/tool-helpers.d.ts +11 -1
- package/dist/tool-helpers.d.ts.map +1 -1
- package/dist/tool-helpers.js +41 -22
- package/dist/tool-helpers.test.js +35 -14
- package/dist/tools/__tests__/delete-one.test.d.ts +2 -0
- package/dist/tools/__tests__/delete-one.test.d.ts.map +1 -0
- package/dist/tools/__tests__/delete-one.test.js +90 -0
- package/dist/tools/__tests__/overview.test.d.ts +2 -0
- package/dist/tools/__tests__/overview.test.d.ts.map +1 -0
- package/dist/tools/__tests__/overview.test.js +163 -0
- package/dist/tools/__tests__/projects-list.test.d.ts +2 -0
- package/dist/tools/__tests__/projects-list.test.d.ts.map +1 -0
- package/dist/tools/__tests__/projects-list.test.js +140 -0
- package/dist/tools/__tests__/projects-manage.test.d.ts +2 -0
- package/dist/tools/__tests__/projects-manage.test.d.ts.map +1 -0
- package/dist/tools/__tests__/projects-manage.test.js +106 -0
- package/dist/tools/__tests__/sections-manage.test.d.ts +2 -0
- package/dist/tools/__tests__/sections-manage.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sections-manage.test.js +138 -0
- package/dist/tools/__tests__/sections-search.test.d.ts +2 -0
- package/dist/tools/__tests__/sections-search.test.d.ts.map +1 -0
- package/dist/tools/__tests__/sections-search.test.js +235 -0
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-add-multiple.test.js +160 -0
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-complete-multiple.test.js +146 -0
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-list-by-date.test.js +192 -0
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-list-completed.test.js +154 -0
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-list-for-container.test.js +232 -0
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-organize-multiple.test.js +245 -0
- package/dist/tools/__tests__/tasks-search.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-search.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-search.test.js +106 -0
- package/dist/tools/__tests__/tasks-update-one.test.d.ts +2 -0
- package/dist/tools/__tests__/tasks-update-one.test.d.ts.map +1 -0
- package/dist/tools/__tests__/tasks-update-one.test.js +161 -0
- package/dist/tools/delete-one.js +4 -7
- package/dist/tools/overview.js +8 -11
- package/dist/tools/projects-list.js +7 -10
- package/dist/tools/projects-manage.js +6 -9
- package/dist/tools/sections-manage.js +7 -10
- package/dist/tools/sections-search.js +4 -7
- package/dist/tools/tasks-add-multiple.js +13 -16
- package/dist/tools/tasks-complete-multiple.js +3 -6
- package/dist/tools/tasks-list-by-date.js +12 -15
- package/dist/tools/tasks-list-completed.d.ts +1 -1
- package/dist/tools/tasks-list-completed.js +13 -16
- package/dist/tools/tasks-list-for-container.js +8 -11
- package/dist/tools/tasks-organize-multiple.d.ts.map +1 -1
- package/dist/tools/tasks-organize-multiple.js +20 -14
- package/dist/tools/tasks-search.js +7 -10
- package/dist/tools/tasks-update-one.d.ts +2 -2
- package/dist/tools/tasks-update-one.d.ts.map +1 -1
- package/dist/tools/tasks-update-one.js +22 -15
- package/dist/tools/test-helpers.d.ts +79 -0
- package/dist/tools/test-helpers.d.ts.map +1 -0
- package/dist/tools/test-helpers.js +139 -0
- package/package.json +6 -2
- package/scripts/test-executable.cjs +69 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sections-search.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/sections-search.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { sectionsSearch } from '../sections-search.js';
|
|
3
|
+
import { TEST_ERRORS, TEST_IDS, createMockSection } from '../test-helpers.js';
|
|
4
|
+
// Mock the Todoist API
|
|
5
|
+
const mockTodoistApi = {
|
|
6
|
+
getSections: jest.fn(),
|
|
7
|
+
};
|
|
8
|
+
describe('sections-search tool', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
describe('listing all sections in a project', () => {
|
|
13
|
+
it('should list all sections when no search parameter is provided', async () => {
|
|
14
|
+
const mockSections = [
|
|
15
|
+
createMockSection({
|
|
16
|
+
id: TEST_IDS.SECTION_1,
|
|
17
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
18
|
+
name: 'To Do',
|
|
19
|
+
}),
|
|
20
|
+
createMockSection({
|
|
21
|
+
id: TEST_IDS.SECTION_2,
|
|
22
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
23
|
+
sectionOrder: 2,
|
|
24
|
+
name: 'In Progress',
|
|
25
|
+
}),
|
|
26
|
+
createMockSection({
|
|
27
|
+
id: 'section-789',
|
|
28
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
29
|
+
sectionOrder: 3,
|
|
30
|
+
name: 'Done',
|
|
31
|
+
}),
|
|
32
|
+
createMockSection({
|
|
33
|
+
id: 'section-999',
|
|
34
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
35
|
+
sectionOrder: 4,
|
|
36
|
+
name: 'Backlog Items',
|
|
37
|
+
}),
|
|
38
|
+
];
|
|
39
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
40
|
+
results: mockSections,
|
|
41
|
+
nextCursor: null,
|
|
42
|
+
});
|
|
43
|
+
const result = await sectionsSearch.execute({ projectId: TEST_IDS.PROJECT_TEST }, mockTodoistApi);
|
|
44
|
+
// Verify API was called correctly
|
|
45
|
+
expect(mockTodoistApi.getSections).toHaveBeenCalledWith({
|
|
46
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
47
|
+
});
|
|
48
|
+
// Verify result is properly mapped (simplified format)
|
|
49
|
+
expect(result).toEqual([
|
|
50
|
+
{ id: TEST_IDS.SECTION_1, name: 'To Do' },
|
|
51
|
+
{ id: TEST_IDS.SECTION_2, name: 'In Progress' },
|
|
52
|
+
{ id: 'section-789', name: 'Done' },
|
|
53
|
+
{ id: 'section-999', name: 'Backlog Items' },
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
it('should handle project with no sections', async () => {
|
|
57
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
58
|
+
results: [],
|
|
59
|
+
nextCursor: null,
|
|
60
|
+
});
|
|
61
|
+
const result = await sectionsSearch.execute({ projectId: 'empty-project-id' }, mockTodoistApi);
|
|
62
|
+
expect(mockTodoistApi.getSections).toHaveBeenCalledWith({
|
|
63
|
+
projectId: 'empty-project-id',
|
|
64
|
+
});
|
|
65
|
+
expect(result).toEqual([]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('searching sections by name', () => {
|
|
69
|
+
it('should filter sections by search term (case insensitive)', async () => {
|
|
70
|
+
const mockSections = [
|
|
71
|
+
createMockSection({
|
|
72
|
+
id: TEST_IDS.SECTION_1,
|
|
73
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
74
|
+
name: 'To Do',
|
|
75
|
+
}),
|
|
76
|
+
createMockSection({
|
|
77
|
+
id: TEST_IDS.SECTION_2,
|
|
78
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
79
|
+
sectionOrder: 2,
|
|
80
|
+
name: 'In Progress',
|
|
81
|
+
}),
|
|
82
|
+
createMockSection({
|
|
83
|
+
id: 'section-789',
|
|
84
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
85
|
+
sectionOrder: 3,
|
|
86
|
+
name: 'Done',
|
|
87
|
+
}),
|
|
88
|
+
createMockSection({
|
|
89
|
+
id: 'section-999',
|
|
90
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
91
|
+
sectionOrder: 4,
|
|
92
|
+
name: 'Progress Review',
|
|
93
|
+
}),
|
|
94
|
+
];
|
|
95
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
96
|
+
results: mockSections,
|
|
97
|
+
nextCursor: null,
|
|
98
|
+
});
|
|
99
|
+
const result = await sectionsSearch.execute({
|
|
100
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
101
|
+
search: 'progress',
|
|
102
|
+
}, mockTodoistApi);
|
|
103
|
+
expect(mockTodoistApi.getSections).toHaveBeenCalledWith({
|
|
104
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
105
|
+
});
|
|
106
|
+
// Should return both "In Progress" and "Progress Review" (case insensitive partial match)
|
|
107
|
+
expect(result).toEqual([
|
|
108
|
+
{ id: TEST_IDS.SECTION_2, name: 'In Progress' },
|
|
109
|
+
{ id: 'section-999', name: 'Progress Review' },
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
it('should handle search with no matches', async () => {
|
|
113
|
+
const mockSections = [
|
|
114
|
+
createMockSection({
|
|
115
|
+
id: TEST_IDS.SECTION_1,
|
|
116
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
117
|
+
name: 'To Do',
|
|
118
|
+
}),
|
|
119
|
+
createMockSection({
|
|
120
|
+
id: TEST_IDS.SECTION_2,
|
|
121
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
122
|
+
sectionOrder: 2,
|
|
123
|
+
name: 'In Progress',
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
127
|
+
results: mockSections,
|
|
128
|
+
nextCursor: null,
|
|
129
|
+
});
|
|
130
|
+
const result = await sectionsSearch.execute({
|
|
131
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
132
|
+
search: 'nonexistent',
|
|
133
|
+
}, mockTodoistApi);
|
|
134
|
+
expect(result).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
it('should handle case sensitive search correctly', async () => {
|
|
137
|
+
const mockSections = [
|
|
138
|
+
createMockSection({
|
|
139
|
+
id: TEST_IDS.SECTION_1,
|
|
140
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
141
|
+
name: 'Important Tasks',
|
|
142
|
+
}),
|
|
143
|
+
createMockSection({
|
|
144
|
+
id: TEST_IDS.SECTION_2,
|
|
145
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
146
|
+
sectionOrder: 2,
|
|
147
|
+
name: 'Regular Work',
|
|
148
|
+
}),
|
|
149
|
+
];
|
|
150
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
151
|
+
results: mockSections,
|
|
152
|
+
nextCursor: null,
|
|
153
|
+
});
|
|
154
|
+
const result = await sectionsSearch.execute({
|
|
155
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
156
|
+
search: 'IMPORTANT',
|
|
157
|
+
}, mockTodoistApi);
|
|
158
|
+
// Should match despite different case
|
|
159
|
+
expect(result).toHaveLength(1);
|
|
160
|
+
expect(result[0]).toEqual({ id: TEST_IDS.SECTION_1, name: 'Important Tasks' });
|
|
161
|
+
});
|
|
162
|
+
it('should handle partial matches correctly', async () => {
|
|
163
|
+
const mockSections = [
|
|
164
|
+
createMockSection({
|
|
165
|
+
id: TEST_IDS.SECTION_1,
|
|
166
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
167
|
+
name: 'Development Tasks',
|
|
168
|
+
}),
|
|
169
|
+
createMockSection({
|
|
170
|
+
id: TEST_IDS.SECTION_2,
|
|
171
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
172
|
+
sectionOrder: 2,
|
|
173
|
+
name: 'Testing Tasks',
|
|
174
|
+
}),
|
|
175
|
+
createMockSection({
|
|
176
|
+
id: 'section-789',
|
|
177
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
178
|
+
sectionOrder: 3,
|
|
179
|
+
name: 'Deployment',
|
|
180
|
+
}),
|
|
181
|
+
];
|
|
182
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
183
|
+
results: mockSections,
|
|
184
|
+
nextCursor: null,
|
|
185
|
+
});
|
|
186
|
+
const result = await sectionsSearch.execute({
|
|
187
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
188
|
+
search: 'task',
|
|
189
|
+
}, mockTodoistApi);
|
|
190
|
+
// Should match both sections with "task" in the name
|
|
191
|
+
expect(result).toEqual([
|
|
192
|
+
{ id: TEST_IDS.SECTION_1, name: 'Development Tasks' },
|
|
193
|
+
{ id: TEST_IDS.SECTION_2, name: 'Testing Tasks' },
|
|
194
|
+
]);
|
|
195
|
+
});
|
|
196
|
+
it('should handle exact matches', async () => {
|
|
197
|
+
const mockSections = [
|
|
198
|
+
createMockSection({
|
|
199
|
+
id: TEST_IDS.SECTION_1,
|
|
200
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
201
|
+
name: 'Done',
|
|
202
|
+
}),
|
|
203
|
+
createMockSection({
|
|
204
|
+
id: TEST_IDS.SECTION_2,
|
|
205
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
206
|
+
sectionOrder: 2,
|
|
207
|
+
name: 'Done Soon',
|
|
208
|
+
}),
|
|
209
|
+
];
|
|
210
|
+
mockTodoistApi.getSections.mockResolvedValue({
|
|
211
|
+
results: mockSections,
|
|
212
|
+
nextCursor: null,
|
|
213
|
+
});
|
|
214
|
+
const result = await sectionsSearch.execute({
|
|
215
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
216
|
+
search: 'done',
|
|
217
|
+
}, mockTodoistApi);
|
|
218
|
+
// Should match both sections containing "done"
|
|
219
|
+
expect(result).toEqual([
|
|
220
|
+
{ id: TEST_IDS.SECTION_1, name: 'Done' },
|
|
221
|
+
{ id: TEST_IDS.SECTION_2, name: 'Done Soon' },
|
|
222
|
+
]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
describe('error handling', () => {
|
|
226
|
+
it.each([
|
|
227
|
+
{ error: 'API Error: Project not found', projectId: 'non-existent-project' },
|
|
228
|
+
{ error: TEST_ERRORS.API_UNAUTHORIZED, projectId: 'restricted-project' },
|
|
229
|
+
{ error: 'API Error: Invalid project ID format', projectId: 'invalid-id-format' },
|
|
230
|
+
])('should propagate $error', async ({ error, projectId }) => {
|
|
231
|
+
mockTodoistApi.getSections.mockRejectedValue(new Error(error));
|
|
232
|
+
await expect(sectionsSearch.execute({ projectId }, mockTodoistApi)).rejects.toThrow(error);
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks-add-multiple.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/tasks-add-multiple.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { tasksAddMultiple } from '../tasks-add-multiple.js';
|
|
3
|
+
import { createMockTask } from '../test-helpers.js';
|
|
4
|
+
// Mock the Todoist API
|
|
5
|
+
const mockTodoistApi = {
|
|
6
|
+
addTask: jest.fn(),
|
|
7
|
+
};
|
|
8
|
+
describe('tasks-add-multiple tool', () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
describe('adding multiple tasks', () => {
|
|
13
|
+
it('should add multiple tasks and return mapped results', async () => {
|
|
14
|
+
// Mock API responses extracted from recordings (Task type)
|
|
15
|
+
const mockApiResponse1 = createMockTask({
|
|
16
|
+
id: '8485093748',
|
|
17
|
+
content: 'First task content',
|
|
18
|
+
url: 'https://todoist.com/showTask?id=8485093748',
|
|
19
|
+
addedAt: '2025-08-13T22:09:56.123456Z',
|
|
20
|
+
});
|
|
21
|
+
const mockApiResponse2 = createMockTask({
|
|
22
|
+
id: '8485093749',
|
|
23
|
+
content: 'Second task content',
|
|
24
|
+
description: 'Task description',
|
|
25
|
+
labels: ['work', 'urgent'],
|
|
26
|
+
childOrder: 2,
|
|
27
|
+
priority: 2,
|
|
28
|
+
url: 'https://todoist.com/showTask?id=8485093749',
|
|
29
|
+
addedAt: '2025-08-13T22:09:57.123456Z',
|
|
30
|
+
due: {
|
|
31
|
+
date: '2025-08-15',
|
|
32
|
+
isRecurring: false,
|
|
33
|
+
lang: 'en',
|
|
34
|
+
string: 'Aug 15',
|
|
35
|
+
timezone: null,
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
mockTodoistApi.addTask
|
|
39
|
+
.mockResolvedValueOnce(mockApiResponse1)
|
|
40
|
+
.mockResolvedValueOnce(mockApiResponse2);
|
|
41
|
+
const result = await tasksAddMultiple.execute({
|
|
42
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
43
|
+
tasks: [
|
|
44
|
+
{ content: 'First task content' },
|
|
45
|
+
{
|
|
46
|
+
content: 'Second task content',
|
|
47
|
+
description: 'Task description',
|
|
48
|
+
priority: 2,
|
|
49
|
+
dueString: 'Aug 15',
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
}, mockTodoistApi);
|
|
53
|
+
// Verify API was called correctly for each task
|
|
54
|
+
expect(mockTodoistApi.addTask).toHaveBeenCalledTimes(2);
|
|
55
|
+
expect(mockTodoistApi.addTask).toHaveBeenNthCalledWith(1, {
|
|
56
|
+
content: 'First task content',
|
|
57
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
58
|
+
sectionId: undefined,
|
|
59
|
+
parentId: undefined,
|
|
60
|
+
});
|
|
61
|
+
expect(mockTodoistApi.addTask).toHaveBeenNthCalledWith(2, {
|
|
62
|
+
content: 'Second task content',
|
|
63
|
+
description: 'Task description',
|
|
64
|
+
priority: 2,
|
|
65
|
+
dueString: 'Aug 15',
|
|
66
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
67
|
+
sectionId: undefined,
|
|
68
|
+
parentId: undefined,
|
|
69
|
+
});
|
|
70
|
+
// Verify result is properly mapped
|
|
71
|
+
expect(result).toEqual([
|
|
72
|
+
expect.objectContaining({
|
|
73
|
+
id: '8485093748',
|
|
74
|
+
content: 'First task content',
|
|
75
|
+
description: '',
|
|
76
|
+
labels: [],
|
|
77
|
+
}),
|
|
78
|
+
expect.objectContaining({
|
|
79
|
+
id: '8485093749',
|
|
80
|
+
content: 'Second task content',
|
|
81
|
+
description: 'Task description',
|
|
82
|
+
dueDate: '2025-08-15',
|
|
83
|
+
priority: 2,
|
|
84
|
+
labels: ['work', 'urgent'],
|
|
85
|
+
}),
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
it('should handle tasks with section and parent IDs', async () => {
|
|
89
|
+
const mockApiResponse = createMockTask({
|
|
90
|
+
id: '8485093750',
|
|
91
|
+
content: 'Subtask content',
|
|
92
|
+
description: 'Subtask description',
|
|
93
|
+
priority: 3,
|
|
94
|
+
sectionId: 'section-123',
|
|
95
|
+
parentId: 'parent-task-456',
|
|
96
|
+
url: 'https://todoist.com/showTask?id=8485093750',
|
|
97
|
+
addedAt: '2025-08-13T22:09:58.123456Z',
|
|
98
|
+
});
|
|
99
|
+
mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
|
|
100
|
+
const result = await tasksAddMultiple.execute({
|
|
101
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
102
|
+
sectionId: 'section-123',
|
|
103
|
+
parentId: 'parent-task-456',
|
|
104
|
+
tasks: [
|
|
105
|
+
{
|
|
106
|
+
content: 'Subtask content',
|
|
107
|
+
description: 'Subtask description',
|
|
108
|
+
priority: 3,
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
}, mockTodoistApi);
|
|
112
|
+
expect(mockTodoistApi.addTask).toHaveBeenCalledWith({
|
|
113
|
+
content: 'Subtask content',
|
|
114
|
+
description: 'Subtask description',
|
|
115
|
+
priority: 3,
|
|
116
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
117
|
+
sectionId: 'section-123',
|
|
118
|
+
parentId: 'parent-task-456',
|
|
119
|
+
});
|
|
120
|
+
expect(result).toEqual([
|
|
121
|
+
expect.objectContaining({
|
|
122
|
+
id: '8485093750',
|
|
123
|
+
content: 'Subtask content',
|
|
124
|
+
description: 'Subtask description',
|
|
125
|
+
priority: 3,
|
|
126
|
+
sectionId: 'section-123',
|
|
127
|
+
parentId: 'parent-task-456',
|
|
128
|
+
labels: [],
|
|
129
|
+
}),
|
|
130
|
+
]);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('error handling', () => {
|
|
134
|
+
it('should propagate API errors', async () => {
|
|
135
|
+
const apiError = new Error('API Error: Task content is required');
|
|
136
|
+
mockTodoistApi.addTask.mockRejectedValue(apiError);
|
|
137
|
+
await expect(tasksAddMultiple.execute({ tasks: [{ content: '' }] }, mockTodoistApi)).rejects.toThrow(apiError.message);
|
|
138
|
+
});
|
|
139
|
+
it('should handle partial failures when adding multiple tasks', async () => {
|
|
140
|
+
const mockApiResponse = createMockTask({
|
|
141
|
+
id: '8485093751',
|
|
142
|
+
content: 'First task content',
|
|
143
|
+
url: 'https://todoist.com/showTask?id=8485093751',
|
|
144
|
+
addedAt: '2025-08-13T22:09:59.123456Z',
|
|
145
|
+
});
|
|
146
|
+
const apiError = new Error('API Error: Second task failed');
|
|
147
|
+
mockTodoistApi.addTask
|
|
148
|
+
.mockResolvedValueOnce(mockApiResponse)
|
|
149
|
+
.mockRejectedValueOnce(apiError);
|
|
150
|
+
await expect(tasksAddMultiple.execute({
|
|
151
|
+
tasks: [
|
|
152
|
+
{ content: 'First task content' },
|
|
153
|
+
{ content: 'Second task content' },
|
|
154
|
+
],
|
|
155
|
+
}, mockTodoistApi)).rejects.toThrow('API Error: Second task failed');
|
|
156
|
+
// Verify first task was attempted
|
|
157
|
+
expect(mockTodoistApi.addTask).toHaveBeenCalledTimes(2);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks-complete-multiple.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/tasks-complete-multiple.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { tasksCompleteMultiple } from '../tasks-complete-multiple.js';
|
|
3
|
+
// Mock the Todoist API
|
|
4
|
+
const mockTodoistApi = {
|
|
5
|
+
closeTask: jest.fn(),
|
|
6
|
+
};
|
|
7
|
+
describe('tasks-complete-multiple tool', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
describe('completing multiple tasks', () => {
|
|
12
|
+
it('should complete all tasks successfully', async () => {
|
|
13
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
14
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
|
|
15
|
+
// Verify API was called for each task
|
|
16
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
|
|
17
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(1, 'task-1');
|
|
18
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(2, 'task-2');
|
|
19
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(3, 'task-3');
|
|
20
|
+
// Verify all tasks were completed successfully
|
|
21
|
+
expect(result).toEqual({
|
|
22
|
+
success: true,
|
|
23
|
+
completed: ['task-1', 'task-2', 'task-3'],
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
it('should complete single task', async () => {
|
|
27
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
28
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['8485093748'] }, mockTodoistApi);
|
|
29
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(1);
|
|
30
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('8485093748');
|
|
31
|
+
expect(result).toEqual({
|
|
32
|
+
success: true,
|
|
33
|
+
completed: ['8485093748'],
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
it('should handle partial failures gracefully', async () => {
|
|
37
|
+
// Mock first and third tasks to succeed, second to fail
|
|
38
|
+
mockTodoistApi.closeTask
|
|
39
|
+
.mockResolvedValueOnce(true) // task-1 succeeds
|
|
40
|
+
.mockRejectedValueOnce(new Error('Task not found')) // task-2 fails
|
|
41
|
+
.mockResolvedValueOnce(true); // task-3 succeeds
|
|
42
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
|
|
43
|
+
// Verify API was called for all tasks despite failure
|
|
44
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
|
|
45
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(1, 'task-1');
|
|
46
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(2, 'task-2');
|
|
47
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(3, 'task-3');
|
|
48
|
+
// Verify only successful completions are reported
|
|
49
|
+
expect(result).toEqual({
|
|
50
|
+
success: true,
|
|
51
|
+
completed: ['task-1', 'task-3'], // task-2 excluded due to failure
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
it('should handle all tasks failing', async () => {
|
|
55
|
+
const apiError = new Error('API Error: Network timeout');
|
|
56
|
+
mockTodoistApi.closeTask.mockRejectedValue(apiError);
|
|
57
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['task-1', 'task-2'] }, mockTodoistApi);
|
|
58
|
+
// Verify API was attempted for all tasks
|
|
59
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(2);
|
|
60
|
+
// Verify no tasks were completed but still returns success
|
|
61
|
+
expect(result).toEqual({
|
|
62
|
+
success: true,
|
|
63
|
+
completed: [], // no tasks completed
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
it('should continue processing remaining tasks after failures', async () => {
|
|
67
|
+
// Mock various failure scenarios
|
|
68
|
+
mockTodoistApi.closeTask
|
|
69
|
+
.mockRejectedValueOnce(new Error('Task already completed'))
|
|
70
|
+
.mockRejectedValueOnce(new Error('Task not found'))
|
|
71
|
+
.mockResolvedValueOnce(true) // task-3 succeeds
|
|
72
|
+
.mockRejectedValueOnce(new Error('Permission denied'))
|
|
73
|
+
.mockResolvedValueOnce(true); // task-5 succeeds
|
|
74
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['task-1', 'task-2', 'task-3', 'task-4', 'task-5'] }, mockTodoistApi);
|
|
75
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(5);
|
|
76
|
+
// Only tasks 3 and 5 should be in completed list
|
|
77
|
+
expect(result).toEqual({
|
|
78
|
+
success: true,
|
|
79
|
+
completed: ['task-3', 'task-5'],
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
it('should handle different types of API errors', async () => {
|
|
83
|
+
mockTodoistApi.closeTask
|
|
84
|
+
.mockRejectedValueOnce(new Error('Task not found'))
|
|
85
|
+
.mockRejectedValueOnce(new Error('Task already completed'))
|
|
86
|
+
.mockRejectedValueOnce(new Error('Permission denied'))
|
|
87
|
+
.mockRejectedValueOnce(new Error('Rate limit exceeded'));
|
|
88
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['not-found', 'already-done', 'no-permission', 'rate-limited'] }, mockTodoistApi);
|
|
89
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(4);
|
|
90
|
+
// All should fail, but the tool should handle it gracefully
|
|
91
|
+
expect(result).toEqual({
|
|
92
|
+
success: true,
|
|
93
|
+
completed: [],
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('mixed success and failure scenarios', () => {
|
|
98
|
+
it('should handle realistic mixed scenario', async () => {
|
|
99
|
+
// Simulate a realistic scenario with some tasks completing and others failing
|
|
100
|
+
mockTodoistApi.closeTask
|
|
101
|
+
.mockResolvedValueOnce(true) // regular task completion
|
|
102
|
+
.mockResolvedValueOnce(true) // another successful completion
|
|
103
|
+
.mockRejectedValueOnce(new Error('Task already completed')) // duplicate completion
|
|
104
|
+
.mockResolvedValueOnce(true) // successful completion
|
|
105
|
+
.mockRejectedValueOnce(new Error('Task not found')); // deleted task
|
|
106
|
+
const result = await tasksCompleteMultiple.execute({
|
|
107
|
+
ids: [
|
|
108
|
+
'8485093748', // regular task
|
|
109
|
+
'8485093749', // regular task
|
|
110
|
+
'8485093750', // already completed
|
|
111
|
+
'8485093751', // regular task
|
|
112
|
+
'8485093752', // deleted task
|
|
113
|
+
],
|
|
114
|
+
}, mockTodoistApi);
|
|
115
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(5);
|
|
116
|
+
expect(result).toEqual({
|
|
117
|
+
success: true,
|
|
118
|
+
completed: ['8485093748', '8485093749', '8485093751'],
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
describe('edge cases', () => {
|
|
123
|
+
it('should handle empty task completion (minimum one task required by schema)', async () => {
|
|
124
|
+
// Note: This test documents that the schema requires at least one task,
|
|
125
|
+
// so this scenario shouldn't occur in practice due to validation
|
|
126
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
127
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['single-task'] }, mockTodoistApi);
|
|
128
|
+
expect(result).toEqual({
|
|
129
|
+
success: true,
|
|
130
|
+
completed: ['single-task'],
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
it('should handle tasks with special ID formats', async () => {
|
|
134
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
135
|
+
const result = await tasksCompleteMultiple.execute({ ids: ['proj_123_task_456', 'task-with-dashes', '1234567890'] }, mockTodoistApi);
|
|
136
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
|
|
137
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('proj_123_task_456');
|
|
138
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('task-with-dashes');
|
|
139
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('1234567890');
|
|
140
|
+
expect(result).toEqual({
|
|
141
|
+
success: true,
|
|
142
|
+
completed: ['proj_123_task_456', 'task-with-dashes', '1234567890'],
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks-list-by-date.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/tasks-list-by-date.test.ts"],"names":[],"mappings":""}
|