@doist/todoist-ai 3.0.0 → 4.1.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.
- package/README.md +2 -18
- package/dist/index.d.ts +296 -30
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -6
- package/dist/main.js +2 -1
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +16 -4
- package/dist/tools/__tests__/add-comments.test.d.ts +2 -0
- package/dist/tools/__tests__/add-comments.test.d.ts.map +1 -0
- package/dist/tools/__tests__/add-comments.test.js +241 -0
- package/dist/tools/__tests__/add-projects.test.d.ts +2 -0
- package/dist/tools/__tests__/add-projects.test.d.ts.map +1 -0
- package/dist/tools/__tests__/add-projects.test.js +152 -0
- package/dist/tools/__tests__/add-sections.test.d.ts +2 -0
- package/dist/tools/__tests__/add-sections.test.d.ts.map +1 -0
- package/dist/tools/__tests__/add-sections.test.js +181 -0
- package/dist/tools/__tests__/add-tasks.test.js +16 -10
- package/dist/tools/__tests__/find-comments.test.d.ts +2 -0
- package/dist/tools/__tests__/find-comments.test.d.ts.map +1 -0
- package/dist/tools/__tests__/find-comments.test.js +242 -0
- package/dist/tools/__tests__/find-sections.test.js +2 -2
- package/dist/tools/__tests__/update-comments.test.d.ts +2 -0
- package/dist/tools/__tests__/update-comments.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-comments.test.js +296 -0
- package/dist/tools/__tests__/update-projects.test.d.ts +2 -0
- package/dist/tools/__tests__/update-projects.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-projects.test.js +205 -0
- package/dist/tools/__tests__/update-sections.test.d.ts +2 -0
- package/dist/tools/__tests__/update-sections.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-sections.test.js +156 -0
- package/dist/tools/add-comments.d.ts +51 -0
- package/dist/tools/add-comments.d.ts.map +1 -0
- package/dist/tools/add-comments.js +79 -0
- package/dist/tools/add-projects.d.ts +50 -0
- package/dist/tools/add-projects.d.ts.map +1 -0
- package/dist/tools/add-projects.js +59 -0
- package/dist/tools/{manage-sections.d.ts → add-sections.d.ts} +21 -13
- package/dist/tools/add-sections.d.ts.map +1 -0
- package/dist/tools/add-sections.js +61 -0
- package/dist/tools/add-tasks.d.ts +15 -8
- package/dist/tools/add-tasks.d.ts.map +1 -1
- package/dist/tools/add-tasks.js +46 -37
- package/dist/tools/delete-object.d.ts +3 -3
- package/dist/tools/delete-object.d.ts.map +1 -1
- package/dist/tools/delete-object.js +13 -3
- package/dist/tools/find-comments.d.ts +46 -0
- package/dist/tools/find-comments.d.ts.map +1 -0
- package/dist/tools/find-comments.js +143 -0
- package/dist/tools/find-projects.js +2 -2
- package/dist/tools/find-sections.js +4 -4
- package/dist/tools/update-comments.d.ts +50 -0
- package/dist/tools/update-comments.d.ts.map +1 -0
- package/dist/tools/update-comments.js +82 -0
- package/dist/tools/update-projects.d.ts +59 -0
- package/dist/tools/update-projects.d.ts.map +1 -0
- package/dist/tools/update-projects.js +84 -0
- package/dist/tools/update-sections.d.ts +47 -0
- package/dist/tools/update-sections.d.ts.map +1 -0
- package/dist/tools/update-sections.js +70 -0
- package/dist/utils/constants.d.ts +4 -0
- package/dist/utils/constants.d.ts.map +1 -1
- package/dist/utils/constants.js +4 -0
- package/dist/utils/tool-names.d.ts +7 -2
- package/dist/utils/tool-names.d.ts.map +1 -1
- package/dist/utils/tool-names.js +8 -2
- package/package.json +1 -1
- package/dist/tools/__tests__/manage-projects.test.d.ts +0 -2
- package/dist/tools/__tests__/manage-projects.test.d.ts.map +0 -1
- package/dist/tools/__tests__/manage-projects.test.js +0 -109
- package/dist/tools/__tests__/manage-sections.test.d.ts +0 -2
- package/dist/tools/__tests__/manage-sections.test.d.ts.map +0 -1
- package/dist/tools/__tests__/manage-sections.test.js +0 -162
- package/dist/tools/manage-projects.d.ts +0 -35
- package/dist/tools/manage-projects.d.ts.map +0 -1
- package/dist/tools/manage-projects.js +0 -63
- package/dist/tools/manage-sections.d.ts.map +0 -1
- package/dist/tools/manage-sections.js +0 -78
|
@@ -0,0 +1,241 @@
|
|
|
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 { addComments } from '../add-comments.js';
|
|
5
|
+
// Mock the Todoist API
|
|
6
|
+
const mockTodoistApi = {
|
|
7
|
+
addComment: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
const { ADD_COMMENTS } = ToolNames;
|
|
10
|
+
const createMockComment = (overrides = {}) => ({
|
|
11
|
+
id: '12345',
|
|
12
|
+
content: 'Test comment content',
|
|
13
|
+
postedAt: '2024-01-01T12:00:00Z',
|
|
14
|
+
postedUid: 'user123',
|
|
15
|
+
taskId: 'task123',
|
|
16
|
+
projectId: undefined,
|
|
17
|
+
fileAttachment: null,
|
|
18
|
+
uidsToNotify: null,
|
|
19
|
+
reactions: null,
|
|
20
|
+
isDeleted: false,
|
|
21
|
+
...overrides,
|
|
22
|
+
});
|
|
23
|
+
describe(`${ADD_COMMENTS} tool`, () => {
|
|
24
|
+
beforeEach(() => {
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
describe('adding comments to tasks', () => {
|
|
28
|
+
it('should add comment to task', async () => {
|
|
29
|
+
const mockComment = createMockComment({
|
|
30
|
+
id: '98765',
|
|
31
|
+
content: 'This is a task comment',
|
|
32
|
+
taskId: 'task456',
|
|
33
|
+
});
|
|
34
|
+
mockTodoistApi.addComment.mockResolvedValue(mockComment);
|
|
35
|
+
const result = await addComments.execute({ comments: [{ taskId: 'task456', content: 'This is a task comment' }] }, mockTodoistApi);
|
|
36
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledWith({
|
|
37
|
+
content: 'This is a task comment',
|
|
38
|
+
taskId: 'task456',
|
|
39
|
+
});
|
|
40
|
+
// Verify result is a concise summary
|
|
41
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
42
|
+
// Verify structured content
|
|
43
|
+
const structuredContent = extractStructuredContent(result);
|
|
44
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
45
|
+
comments: [
|
|
46
|
+
expect.objectContaining({
|
|
47
|
+
id: '98765',
|
|
48
|
+
content: 'This is a task comment',
|
|
49
|
+
taskId: 'task456',
|
|
50
|
+
}),
|
|
51
|
+
],
|
|
52
|
+
totalCount: 1,
|
|
53
|
+
addedCommentIds: ['98765'],
|
|
54
|
+
}));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe('adding comments to projects', () => {
|
|
58
|
+
it('should add comment to project', async () => {
|
|
59
|
+
const mockComment = createMockComment({
|
|
60
|
+
id: '98767',
|
|
61
|
+
content: 'This is a project comment',
|
|
62
|
+
taskId: undefined,
|
|
63
|
+
projectId: 'project789',
|
|
64
|
+
});
|
|
65
|
+
mockTodoistApi.addComment.mockResolvedValue(mockComment);
|
|
66
|
+
const result = await addComments.execute({ comments: [{ projectId: 'project789', content: 'This is a project comment' }] }, mockTodoistApi);
|
|
67
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledWith({
|
|
68
|
+
content: 'This is a project comment',
|
|
69
|
+
projectId: 'project789',
|
|
70
|
+
});
|
|
71
|
+
// Verify result is a concise summary
|
|
72
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
73
|
+
// Verify structured content
|
|
74
|
+
const structuredContent = extractStructuredContent(result);
|
|
75
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
76
|
+
comments: [
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
id: '98767',
|
|
79
|
+
content: 'This is a project comment',
|
|
80
|
+
taskId: undefined,
|
|
81
|
+
projectId: 'project789',
|
|
82
|
+
}),
|
|
83
|
+
],
|
|
84
|
+
totalCount: 1,
|
|
85
|
+
addedCommentIds: ['98767'],
|
|
86
|
+
}));
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
describe('bulk operations', () => {
|
|
90
|
+
it('should add multiple comments to different entities (task + project)', async () => {
|
|
91
|
+
const mockTaskComment = createMockComment({
|
|
92
|
+
id: '11111',
|
|
93
|
+
content: 'Task comment',
|
|
94
|
+
taskId: 'task123',
|
|
95
|
+
projectId: undefined,
|
|
96
|
+
});
|
|
97
|
+
const mockProjectComment = createMockComment({
|
|
98
|
+
id: '22222',
|
|
99
|
+
content: 'Project comment',
|
|
100
|
+
taskId: undefined,
|
|
101
|
+
projectId: 'project456',
|
|
102
|
+
});
|
|
103
|
+
mockTodoistApi.addComment
|
|
104
|
+
.mockResolvedValueOnce(mockTaskComment)
|
|
105
|
+
.mockResolvedValueOnce(mockProjectComment);
|
|
106
|
+
const result = await addComments.execute({
|
|
107
|
+
comments: [
|
|
108
|
+
{ taskId: 'task123', content: 'Task comment' },
|
|
109
|
+
{ projectId: 'project456', content: 'Project comment' },
|
|
110
|
+
],
|
|
111
|
+
}, mockTodoistApi);
|
|
112
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledTimes(2);
|
|
113
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledWith({
|
|
114
|
+
content: 'Task comment',
|
|
115
|
+
taskId: 'task123',
|
|
116
|
+
});
|
|
117
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledWith({
|
|
118
|
+
content: 'Project comment',
|
|
119
|
+
projectId: 'project456',
|
|
120
|
+
});
|
|
121
|
+
// Verify result is a concise summary
|
|
122
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
123
|
+
const structuredContent = extractStructuredContent(result);
|
|
124
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
125
|
+
comments: [
|
|
126
|
+
expect.objectContaining({
|
|
127
|
+
id: '11111',
|
|
128
|
+
content: 'Task comment',
|
|
129
|
+
taskId: 'task123',
|
|
130
|
+
}),
|
|
131
|
+
expect.objectContaining({
|
|
132
|
+
id: '22222',
|
|
133
|
+
content: 'Project comment',
|
|
134
|
+
projectId: 'project456',
|
|
135
|
+
}),
|
|
136
|
+
],
|
|
137
|
+
totalCount: 2,
|
|
138
|
+
addedCommentIds: ['11111', '22222'],
|
|
139
|
+
}));
|
|
140
|
+
});
|
|
141
|
+
it('should add multiple comments to different tasks', async () => {
|
|
142
|
+
const mockComment1 = createMockComment({
|
|
143
|
+
id: '33333',
|
|
144
|
+
content: 'First task comment',
|
|
145
|
+
taskId: 'task111',
|
|
146
|
+
});
|
|
147
|
+
const mockComment2 = createMockComment({
|
|
148
|
+
id: '44444',
|
|
149
|
+
content: 'Second task comment',
|
|
150
|
+
taskId: 'task222',
|
|
151
|
+
});
|
|
152
|
+
mockTodoistApi.addComment
|
|
153
|
+
.mockResolvedValueOnce(mockComment1)
|
|
154
|
+
.mockResolvedValueOnce(mockComment2);
|
|
155
|
+
const result = await addComments.execute({
|
|
156
|
+
comments: [
|
|
157
|
+
{ taskId: 'task111', content: 'First task comment' },
|
|
158
|
+
{ taskId: 'task222', content: 'Second task comment' },
|
|
159
|
+
],
|
|
160
|
+
}, mockTodoistApi);
|
|
161
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledTimes(2);
|
|
162
|
+
// Verify result is a concise summary
|
|
163
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
164
|
+
const structuredContent = extractStructuredContent(result);
|
|
165
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
166
|
+
comments: expect.arrayContaining([
|
|
167
|
+
expect.objectContaining({
|
|
168
|
+
id: '33333',
|
|
169
|
+
content: 'First task comment',
|
|
170
|
+
taskId: 'task111',
|
|
171
|
+
}),
|
|
172
|
+
expect.objectContaining({
|
|
173
|
+
id: '44444',
|
|
174
|
+
content: 'Second task comment',
|
|
175
|
+
taskId: 'task222',
|
|
176
|
+
}),
|
|
177
|
+
]),
|
|
178
|
+
totalCount: 2,
|
|
179
|
+
addedCommentIds: ['33333', '44444'],
|
|
180
|
+
}));
|
|
181
|
+
});
|
|
182
|
+
it('should add multiple comments to the same task', async () => {
|
|
183
|
+
const mockComment1 = createMockComment({
|
|
184
|
+
id: '55555',
|
|
185
|
+
content: 'First comment on same task',
|
|
186
|
+
taskId: 'task999',
|
|
187
|
+
});
|
|
188
|
+
const mockComment2 = createMockComment({
|
|
189
|
+
id: '66666',
|
|
190
|
+
content: 'Second comment on same task',
|
|
191
|
+
taskId: 'task999',
|
|
192
|
+
});
|
|
193
|
+
mockTodoistApi.addComment
|
|
194
|
+
.mockResolvedValueOnce(mockComment1)
|
|
195
|
+
.mockResolvedValueOnce(mockComment2);
|
|
196
|
+
const result = await addComments.execute({
|
|
197
|
+
comments: [
|
|
198
|
+
{ taskId: 'task999', content: 'First comment on same task' },
|
|
199
|
+
{ taskId: 'task999', content: 'Second comment on same task' },
|
|
200
|
+
],
|
|
201
|
+
}, mockTodoistApi);
|
|
202
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledTimes(2);
|
|
203
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledWith({
|
|
204
|
+
content: 'First comment on same task',
|
|
205
|
+
taskId: 'task999',
|
|
206
|
+
});
|
|
207
|
+
expect(mockTodoistApi.addComment).toHaveBeenCalledWith({
|
|
208
|
+
content: 'Second comment on same task',
|
|
209
|
+
taskId: 'task999',
|
|
210
|
+
});
|
|
211
|
+
// Verify result is a concise summary
|
|
212
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
213
|
+
const structuredContent = extractStructuredContent(result);
|
|
214
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
215
|
+
comments: expect.arrayContaining([
|
|
216
|
+
expect.objectContaining({
|
|
217
|
+
id: '55555',
|
|
218
|
+
content: 'First comment on same task',
|
|
219
|
+
taskId: 'task999',
|
|
220
|
+
}),
|
|
221
|
+
expect.objectContaining({
|
|
222
|
+
id: '66666',
|
|
223
|
+
content: 'Second comment on same task',
|
|
224
|
+
taskId: 'task999',
|
|
225
|
+
}),
|
|
226
|
+
]),
|
|
227
|
+
totalCount: 2,
|
|
228
|
+
addedCommentIds: ['55555', '66666'],
|
|
229
|
+
}));
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
describe('validation', () => {
|
|
233
|
+
it('should throw error when neither taskId nor projectId provided', async () => {
|
|
234
|
+
await expect(addComments.execute({ comments: [{ content: 'Test comment' }] }, mockTodoistApi)).rejects.toThrow('Comment 1: Either taskId or projectId must be provided.');
|
|
235
|
+
});
|
|
236
|
+
it('should throw error when both taskId and projectId provided', async () => {
|
|
237
|
+
const comment = { taskId: 'task123', projectId: 'project456', content: 'Test comment' };
|
|
238
|
+
await expect(addComments.execute({ comments: [comment] }, mockTodoistApi)).rejects.toThrow('Comment 1: Cannot provide both taskId and projectId. Choose one.');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-projects.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/add-projects.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { TEST_IDS, createMockProject, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
|
|
3
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
+
import { addProjects } from '../add-projects.js';
|
|
5
|
+
// Mock the Todoist API
|
|
6
|
+
const mockTodoistApi = {
|
|
7
|
+
addProject: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
const { ADD_TASKS, ADD_PROJECTS, ADD_SECTIONS } = ToolNames;
|
|
10
|
+
describe(`${ADD_PROJECTS} tool`, () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
describe('creating a single project', () => {
|
|
15
|
+
it('should create a project and return mapped result', async () => {
|
|
16
|
+
const mockApiResponse = createMockProject({
|
|
17
|
+
id: TEST_IDS.PROJECT_TEST,
|
|
18
|
+
name: 'test-abc123def456-project',
|
|
19
|
+
childOrder: 1,
|
|
20
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
21
|
+
});
|
|
22
|
+
mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
|
|
23
|
+
const result = await addProjects.execute({ projects: [{ name: 'test-abc123def456-project' }] }, mockTodoistApi);
|
|
24
|
+
// Verify API was called correctly
|
|
25
|
+
expect(mockTodoistApi.addProject).toHaveBeenCalledWith({
|
|
26
|
+
name: 'test-abc123def456-project',
|
|
27
|
+
});
|
|
28
|
+
const textContent = extractTextContent(result);
|
|
29
|
+
expect(textContent).toMatchSnapshot();
|
|
30
|
+
expect(textContent).toContain('Added 1 project:');
|
|
31
|
+
expect(textContent).toContain('test-abc123def456-project');
|
|
32
|
+
expect(textContent).toContain(`id=${TEST_IDS.PROJECT_TEST}`);
|
|
33
|
+
expect(textContent).toContain(`Use ${ADD_TASKS} to add your first tasks`);
|
|
34
|
+
// Verify structured content
|
|
35
|
+
const structuredContent = extractStructuredContent(result);
|
|
36
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
37
|
+
projects: [
|
|
38
|
+
expect.objectContaining({
|
|
39
|
+
id: TEST_IDS.PROJECT_TEST,
|
|
40
|
+
name: 'test-abc123def456-project',
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
totalCount: 1,
|
|
44
|
+
}));
|
|
45
|
+
});
|
|
46
|
+
it('should handle different project properties from API', async () => {
|
|
47
|
+
const mockApiResponse = createMockProject({
|
|
48
|
+
id: 'project-456',
|
|
49
|
+
name: 'My Blue Project',
|
|
50
|
+
color: 'blue',
|
|
51
|
+
isFavorite: true,
|
|
52
|
+
isShared: true,
|
|
53
|
+
parentId: 'parent-123',
|
|
54
|
+
viewStyle: 'board',
|
|
55
|
+
childOrder: 2,
|
|
56
|
+
description: 'A test project',
|
|
57
|
+
createdAt: '2024-01-01T00:00:00Z',
|
|
58
|
+
});
|
|
59
|
+
mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
|
|
60
|
+
const result = await addProjects.execute({ projects: [{ name: 'My Blue Project' }] }, mockTodoistApi);
|
|
61
|
+
expect(mockTodoistApi.addProject).toHaveBeenCalledWith({
|
|
62
|
+
name: 'My Blue Project',
|
|
63
|
+
isFavorite: undefined,
|
|
64
|
+
viewStyle: undefined,
|
|
65
|
+
});
|
|
66
|
+
const textContent = extractTextContent(result);
|
|
67
|
+
expect(textContent).toMatchSnapshot();
|
|
68
|
+
expect(textContent).toContain('Added 1 project:');
|
|
69
|
+
expect(textContent).toContain('My Blue Project');
|
|
70
|
+
expect(textContent).toContain('id=project-456');
|
|
71
|
+
expect(textContent).toContain(`Use ${ADD_SECTIONS} to organize new project`);
|
|
72
|
+
});
|
|
73
|
+
it('should create project with isFavorite and viewStyle options', async () => {
|
|
74
|
+
const mockApiResponse = createMockProject({
|
|
75
|
+
id: 'project-789',
|
|
76
|
+
name: 'Board Project',
|
|
77
|
+
isFavorite: true,
|
|
78
|
+
viewStyle: 'board',
|
|
79
|
+
});
|
|
80
|
+
mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
|
|
81
|
+
const result = await addProjects.execute({ projects: [{ name: 'Board Project', isFavorite: true, viewStyle: 'board' }] }, mockTodoistApi);
|
|
82
|
+
expect(mockTodoistApi.addProject).toHaveBeenCalledWith({
|
|
83
|
+
name: 'Board Project',
|
|
84
|
+
isFavorite: true,
|
|
85
|
+
viewStyle: 'board',
|
|
86
|
+
});
|
|
87
|
+
const textContent = extractTextContent(result);
|
|
88
|
+
expect(textContent).toMatchSnapshot();
|
|
89
|
+
expect(textContent).toContain('Added 1 project:');
|
|
90
|
+
expect(textContent).toContain('Board Project');
|
|
91
|
+
expect(textContent).toContain('id=project-789');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('creating multiple projects', () => {
|
|
95
|
+
it('should create multiple projects and return mapped results', async () => {
|
|
96
|
+
const mockProjects = [
|
|
97
|
+
createMockProject({ id: 'project-1', name: 'First Project' }),
|
|
98
|
+
createMockProject({ id: 'project-2', name: 'Second Project' }),
|
|
99
|
+
createMockProject({ id: 'project-3', name: 'Third Project' }),
|
|
100
|
+
];
|
|
101
|
+
const [project1, project2, project3] = mockProjects;
|
|
102
|
+
mockTodoistApi.addProject
|
|
103
|
+
.mockResolvedValueOnce(project1)
|
|
104
|
+
.mockResolvedValueOnce(project2)
|
|
105
|
+
.mockResolvedValueOnce(project3);
|
|
106
|
+
const result = await addProjects.execute({
|
|
107
|
+
projects: [
|
|
108
|
+
{ name: 'First Project' },
|
|
109
|
+
{ name: 'Second Project' },
|
|
110
|
+
{ name: 'Third Project' },
|
|
111
|
+
],
|
|
112
|
+
}, mockTodoistApi);
|
|
113
|
+
// Verify API was called correctly for each project
|
|
114
|
+
expect(mockTodoistApi.addProject).toHaveBeenCalledTimes(3);
|
|
115
|
+
expect(mockTodoistApi.addProject).toHaveBeenNthCalledWith(1, { name: 'First Project' });
|
|
116
|
+
expect(mockTodoistApi.addProject).toHaveBeenNthCalledWith(2, { name: 'Second Project' });
|
|
117
|
+
expect(mockTodoistApi.addProject).toHaveBeenNthCalledWith(3, { name: 'Third Project' });
|
|
118
|
+
const textContent = extractTextContent(result);
|
|
119
|
+
expect(textContent).toMatchSnapshot();
|
|
120
|
+
expect(textContent).toContain('Added 3 projects:');
|
|
121
|
+
expect(textContent).toContain('First Project (id=project-1)');
|
|
122
|
+
expect(textContent).toContain('Second Project (id=project-2)');
|
|
123
|
+
expect(textContent).toContain('Third Project (id=project-3)');
|
|
124
|
+
expect(textContent).toContain(`Use ${ADD_SECTIONS} to organize these projects`);
|
|
125
|
+
// Verify structured content
|
|
126
|
+
const structuredContent = extractStructuredContent(result);
|
|
127
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
128
|
+
projects: mockProjects,
|
|
129
|
+
totalCount: 3,
|
|
130
|
+
}));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('error handling', () => {
|
|
134
|
+
it('should propagate API errors', async () => {
|
|
135
|
+
const apiError = new Error('API Error: Project name is required');
|
|
136
|
+
mockTodoistApi.addProject.mockRejectedValue(apiError);
|
|
137
|
+
await expect(addProjects.execute({ projects: [{ name: '' }] }, mockTodoistApi)).rejects.toThrow('API Error: Project name is required');
|
|
138
|
+
});
|
|
139
|
+
it('should handle partial failures in multiple projects', async () => {
|
|
140
|
+
const mockProject = createMockProject({
|
|
141
|
+
id: 'project-1',
|
|
142
|
+
name: 'First Project',
|
|
143
|
+
});
|
|
144
|
+
mockTodoistApi.addProject
|
|
145
|
+
.mockResolvedValueOnce(mockProject)
|
|
146
|
+
.mockRejectedValueOnce(new Error('API Error: Invalid project name'));
|
|
147
|
+
await expect(addProjects.execute({
|
|
148
|
+
projects: [{ name: 'First Project' }, { name: 'Invalid' }],
|
|
149
|
+
}, mockTodoistApi)).rejects.toThrow('API Error: Invalid project name');
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-sections.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/add-sections.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { TEST_IDS, createMockSection, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
|
|
3
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
+
import { addSections } from '../add-sections.js';
|
|
5
|
+
// Mock the Todoist API
|
|
6
|
+
const mockTodoistApi = {
|
|
7
|
+
addSection: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
const { ADD_TASKS, ADD_SECTIONS, FIND_TASKS } = ToolNames;
|
|
10
|
+
describe(`${ADD_SECTIONS} tool`, () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
describe('creating a single section', () => {
|
|
15
|
+
it('should create a section and return mapped result', async () => {
|
|
16
|
+
const mockApiResponse = createMockSection({
|
|
17
|
+
id: TEST_IDS.SECTION_1,
|
|
18
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
19
|
+
name: 'test-abc123def456-section',
|
|
20
|
+
});
|
|
21
|
+
mockTodoistApi.addSection.mockResolvedValue(mockApiResponse);
|
|
22
|
+
const result = await addSections.execute({
|
|
23
|
+
sections: [
|
|
24
|
+
{ name: 'test-abc123def456-section', projectId: TEST_IDS.PROJECT_TEST },
|
|
25
|
+
],
|
|
26
|
+
}, mockTodoistApi);
|
|
27
|
+
// Verify API was called correctly
|
|
28
|
+
expect(mockTodoistApi.addSection).toHaveBeenCalledWith({
|
|
29
|
+
name: 'test-abc123def456-section',
|
|
30
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
31
|
+
});
|
|
32
|
+
const textContent = extractTextContent(result);
|
|
33
|
+
expect(textContent).toMatchSnapshot();
|
|
34
|
+
expect(textContent).toContain('Added 1 section:');
|
|
35
|
+
expect(textContent).toContain('test-abc123def456-section');
|
|
36
|
+
expect(textContent).toContain(`id=${TEST_IDS.SECTION_1}`);
|
|
37
|
+
expect(textContent).toContain(`Use ${ADD_TASKS} with sectionId`);
|
|
38
|
+
// Verify structured content
|
|
39
|
+
const structuredContent = extractStructuredContent(result);
|
|
40
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
41
|
+
sections: [
|
|
42
|
+
expect.objectContaining({
|
|
43
|
+
id: TEST_IDS.SECTION_1,
|
|
44
|
+
name: 'test-abc123def456-section',
|
|
45
|
+
}),
|
|
46
|
+
],
|
|
47
|
+
totalCount: 1,
|
|
48
|
+
}));
|
|
49
|
+
});
|
|
50
|
+
it('should handle different section properties from API', async () => {
|
|
51
|
+
const mockApiResponse = createMockSection({
|
|
52
|
+
id: TEST_IDS.SECTION_2,
|
|
53
|
+
projectId: 'project-789',
|
|
54
|
+
sectionOrder: 2,
|
|
55
|
+
name: 'My Section Name',
|
|
56
|
+
});
|
|
57
|
+
mockTodoistApi.addSection.mockResolvedValue(mockApiResponse);
|
|
58
|
+
const result = await addSections.execute({ sections: [{ name: 'My Section Name', projectId: 'project-789' }] }, mockTodoistApi);
|
|
59
|
+
expect(mockTodoistApi.addSection).toHaveBeenCalledWith({
|
|
60
|
+
name: 'My Section Name',
|
|
61
|
+
projectId: 'project-789',
|
|
62
|
+
});
|
|
63
|
+
const textContent = extractTextContent(result);
|
|
64
|
+
expect(textContent).toMatchSnapshot();
|
|
65
|
+
expect(textContent).toContain('Added 1 section:');
|
|
66
|
+
expect(textContent).toContain('My Section Name');
|
|
67
|
+
expect(textContent).toContain(`id=${TEST_IDS.SECTION_2}`);
|
|
68
|
+
expect(textContent).toContain(`Use ${FIND_TASKS} with sectionId`);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('creating multiple sections', () => {
|
|
72
|
+
it('should create multiple sections and return mapped results', async () => {
|
|
73
|
+
const mockSections = [
|
|
74
|
+
createMockSection({
|
|
75
|
+
id: 'section-1',
|
|
76
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
77
|
+
name: 'First Section',
|
|
78
|
+
}),
|
|
79
|
+
createMockSection({
|
|
80
|
+
id: 'section-2',
|
|
81
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
82
|
+
name: 'Second Section',
|
|
83
|
+
}),
|
|
84
|
+
createMockSection({
|
|
85
|
+
id: 'section-3',
|
|
86
|
+
projectId: 'different-project',
|
|
87
|
+
name: 'Third Section',
|
|
88
|
+
}),
|
|
89
|
+
];
|
|
90
|
+
const [section1, section2, section3] = mockSections;
|
|
91
|
+
mockTodoistApi.addSection
|
|
92
|
+
.mockResolvedValueOnce(section1)
|
|
93
|
+
.mockResolvedValueOnce(section2)
|
|
94
|
+
.mockResolvedValueOnce(section3);
|
|
95
|
+
const result = await addSections.execute({
|
|
96
|
+
sections: [
|
|
97
|
+
{ name: 'First Section', projectId: TEST_IDS.PROJECT_TEST },
|
|
98
|
+
{ name: 'Second Section', projectId: TEST_IDS.PROJECT_TEST },
|
|
99
|
+
{ name: 'Third Section', projectId: 'different-project' },
|
|
100
|
+
],
|
|
101
|
+
}, mockTodoistApi);
|
|
102
|
+
// Verify API was called correctly for each section
|
|
103
|
+
expect(mockTodoistApi.addSection).toHaveBeenCalledTimes(3);
|
|
104
|
+
expect(mockTodoistApi.addSection).toHaveBeenNthCalledWith(1, {
|
|
105
|
+
name: 'First Section',
|
|
106
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
107
|
+
});
|
|
108
|
+
expect(mockTodoistApi.addSection).toHaveBeenNthCalledWith(2, {
|
|
109
|
+
name: 'Second Section',
|
|
110
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
111
|
+
});
|
|
112
|
+
expect(mockTodoistApi.addSection).toHaveBeenNthCalledWith(3, {
|
|
113
|
+
name: 'Third Section',
|
|
114
|
+
projectId: 'different-project',
|
|
115
|
+
});
|
|
116
|
+
const textContent = extractTextContent(result);
|
|
117
|
+
expect(textContent).toMatchSnapshot();
|
|
118
|
+
expect(textContent).toContain('Added 3 sections:');
|
|
119
|
+
expect(textContent).toContain('First Section (id=section-1, projectId=');
|
|
120
|
+
expect(textContent).toContain('Second Section (id=section-2, projectId=');
|
|
121
|
+
expect(textContent).toContain('Third Section (id=section-3, projectId=different-project)');
|
|
122
|
+
// Verify structured content
|
|
123
|
+
const structuredContent = extractStructuredContent(result);
|
|
124
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
125
|
+
sections: mockSections,
|
|
126
|
+
totalCount: 3,
|
|
127
|
+
}));
|
|
128
|
+
});
|
|
129
|
+
it('should handle sections for the same project', async () => {
|
|
130
|
+
const mockSections = [
|
|
131
|
+
createMockSection({
|
|
132
|
+
id: 'section-1',
|
|
133
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
134
|
+
name: 'To Do',
|
|
135
|
+
}),
|
|
136
|
+
createMockSection({
|
|
137
|
+
id: 'section-2',
|
|
138
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
139
|
+
name: 'In Progress',
|
|
140
|
+
}),
|
|
141
|
+
];
|
|
142
|
+
const [section1, section2] = mockSections;
|
|
143
|
+
mockTodoistApi.addSection
|
|
144
|
+
.mockResolvedValueOnce(section1)
|
|
145
|
+
.mockResolvedValueOnce(section2);
|
|
146
|
+
const result = await addSections.execute({
|
|
147
|
+
sections: [
|
|
148
|
+
{ name: 'To Do', projectId: TEST_IDS.PROJECT_TEST },
|
|
149
|
+
{ name: 'In Progress', projectId: TEST_IDS.PROJECT_TEST },
|
|
150
|
+
],
|
|
151
|
+
}, mockTodoistApi);
|
|
152
|
+
const textContent = extractTextContent(result);
|
|
153
|
+
expect(textContent).toMatchSnapshot();
|
|
154
|
+
expect(textContent).toContain('Added 2 sections:');
|
|
155
|
+
expect(textContent).toContain(`Use ${ADD_TASKS} to add tasks to these new sections`);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
describe('error handling', () => {
|
|
159
|
+
it('should propagate API errors', async () => {
|
|
160
|
+
const apiError = new Error('API Error: Section name is required');
|
|
161
|
+
mockTodoistApi.addSection.mockRejectedValue(apiError);
|
|
162
|
+
await expect(addSections.execute({ sections: [{ name: '', projectId: TEST_IDS.PROJECT_TEST }] }, mockTodoistApi)).rejects.toThrow('API Error: Section name is required');
|
|
163
|
+
});
|
|
164
|
+
it('should handle partial failures in multiple sections', async () => {
|
|
165
|
+
const mockSection = createMockSection({
|
|
166
|
+
id: 'section-1',
|
|
167
|
+
projectId: TEST_IDS.PROJECT_TEST,
|
|
168
|
+
name: 'First Section',
|
|
169
|
+
});
|
|
170
|
+
mockTodoistApi.addSection
|
|
171
|
+
.mockResolvedValueOnce(mockSection)
|
|
172
|
+
.mockRejectedValueOnce(new Error('API Error: Invalid project ID'));
|
|
173
|
+
await expect(addSections.execute({
|
|
174
|
+
sections: [
|
|
175
|
+
{ name: 'First Section', projectId: TEST_IDS.PROJECT_TEST },
|
|
176
|
+
{ name: 'Second Section', projectId: 'invalid-project' },
|
|
177
|
+
],
|
|
178
|
+
}, mockTodoistApi)).rejects.toThrow('API Error: Invalid project ID');
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -41,14 +41,14 @@ describe(`${ADD_TASKS} tool`, () => {
|
|
|
41
41
|
.mockResolvedValueOnce(mockApiResponse1)
|
|
42
42
|
.mockResolvedValueOnce(mockApiResponse2);
|
|
43
43
|
const result = await addTasks.execute({
|
|
44
|
-
projectId: '6cfCcrrCFg2xP94Q',
|
|
45
44
|
tasks: [
|
|
46
|
-
{ content: 'First task content' },
|
|
45
|
+
{ content: 'First task content', projectId: '6cfCcrrCFg2xP94Q' },
|
|
47
46
|
{
|
|
48
47
|
content: 'Second task content',
|
|
49
48
|
description: 'Task description',
|
|
50
49
|
priority: 2,
|
|
51
50
|
dueString: 'Aug 15',
|
|
51
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
52
52
|
},
|
|
53
53
|
],
|
|
54
54
|
}, mockTodoistApi);
|
|
@@ -95,14 +95,14 @@ describe(`${ADD_TASKS} tool`, () => {
|
|
|
95
95
|
});
|
|
96
96
|
mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
|
|
97
97
|
const result = await addTasks.execute({
|
|
98
|
-
projectId: '6cfCcrrCFg2xP94Q',
|
|
99
|
-
sectionId: 'section-123',
|
|
100
|
-
parentId: 'parent-task-456',
|
|
101
98
|
tasks: [
|
|
102
99
|
{
|
|
103
100
|
content: 'Subtask content',
|
|
104
101
|
description: 'Subtask description',
|
|
105
102
|
priority: 3,
|
|
103
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
104
|
+
sectionId: 'section-123',
|
|
105
|
+
parentId: 'parent-task-456',
|
|
106
106
|
},
|
|
107
107
|
],
|
|
108
108
|
}, mockTodoistApi);
|
|
@@ -142,10 +142,17 @@ describe(`${ADD_TASKS} tool`, () => {
|
|
|
142
142
|
.mockResolvedValueOnce(mockApiResponse1)
|
|
143
143
|
.mockResolvedValueOnce(mockApiResponse2);
|
|
144
144
|
const result = await addTasks.execute({
|
|
145
|
-
projectId: '6cfCcrrCFg2xP94Q',
|
|
146
145
|
tasks: [
|
|
147
|
-
{
|
|
148
|
-
|
|
146
|
+
{
|
|
147
|
+
content: 'Task with 2 hour duration',
|
|
148
|
+
duration: '2h',
|
|
149
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
content: 'Task with 45 minute duration',
|
|
153
|
+
duration: '45m',
|
|
154
|
+
projectId: '6cfCcrrCFg2xP94Q',
|
|
155
|
+
},
|
|
149
156
|
],
|
|
150
157
|
}, mockTodoistApi);
|
|
151
158
|
// Verify API was called with parsed duration
|
|
@@ -267,8 +274,7 @@ describe(`${ADD_TASKS} tool`, () => {
|
|
|
267
274
|
});
|
|
268
275
|
mockTodoistApi.addTask.mockResolvedValue(mockApiResponse);
|
|
269
276
|
const result = await addTasks.execute({
|
|
270
|
-
projectId: '6cfCcrrCFg2xP94Q',
|
|
271
|
-
tasks: [{ content: 'Regular task' }],
|
|
277
|
+
tasks: [{ content: 'Regular task', projectId: '6cfCcrrCFg2xP94Q' }],
|
|
272
278
|
}, mockTodoistApi);
|
|
273
279
|
const textContent = extractTextContent(result);
|
|
274
280
|
expect(textContent).toMatchSnapshot();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-comments.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-comments.test.ts"],"names":[],"mappings":""}
|