@doist/todoist-ai 2.2.2 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/index.d.ts +496 -255
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +41 -29
- package/dist/mcp-helpers.d.ts +25 -3
- package/dist/mcp-helpers.d.ts.map +1 -1
- package/dist/mcp-helpers.js +37 -19
- package/dist/mcp-server.d.ts.map +1 -1
- package/dist/mcp-server.js +32 -28
- package/dist/tools/__tests__/add-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/add-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{tasks-add-multiple.test.js → add-tasks.test.js} +85 -81
- package/dist/tools/__tests__/complete-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/complete-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/complete-tasks.test.js +206 -0
- package/dist/tools/__tests__/delete-object.test.d.ts +2 -0
- package/dist/tools/__tests__/delete-object.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{delete-one.test.js → delete-object.test.js} +42 -22
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/find-completed-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{tasks-list-completed.test.js → find-completed-tasks.test.js} +13 -36
- package/dist/tools/__tests__/find-projects.test.d.ts +2 -0
- package/dist/tools/__tests__/find-projects.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{projects-list.test.js → find-projects.test.js} +55 -39
- package/dist/tools/__tests__/find-sections.test.d.ts +2 -0
- package/dist/tools/__tests__/find-sections.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{sections-search.test.js → find-sections.test.js} +64 -50
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts +2 -0
- package/dist/tools/__tests__/find-tasks-by-date.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{tasks-list-by-date.test.js → find-tasks-by-date.test.js} +96 -14
- package/dist/tools/__tests__/find-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/find-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/find-tasks.test.js +334 -0
- package/dist/tools/__tests__/get-overview.test.d.ts +2 -0
- package/dist/tools/__tests__/get-overview.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{overview.test.js → get-overview.test.js} +77 -13
- package/dist/tools/__tests__/manage-projects.test.d.ts +2 -0
- package/dist/tools/__tests__/manage-projects.test.d.ts.map +1 -0
- package/dist/tools/__tests__/{projects-manage.test.js → manage-projects.test.js} +33 -30
- package/dist/tools/__tests__/manage-sections.test.d.ts +2 -0
- package/dist/tools/__tests__/manage-sections.test.d.ts.map +1 -0
- package/dist/tools/__tests__/manage-sections.test.js +162 -0
- package/dist/tools/__tests__/update-tasks.test.d.ts +2 -0
- package/dist/tools/__tests__/update-tasks.test.d.ts.map +1 -0
- package/dist/tools/__tests__/update-tasks.test.js +645 -0
- package/dist/tools/{tasks-add-multiple.d.ts → add-tasks.d.ts} +36 -16
- package/dist/tools/add-tasks.d.ts.map +1 -0
- package/dist/tools/{tasks-add-multiple.js → add-tasks.js} +39 -4
- package/dist/tools/complete-tasks.d.ts +40 -0
- package/dist/tools/complete-tasks.d.ts.map +1 -0
- package/dist/tools/complete-tasks.js +68 -0
- package/dist/tools/delete-object.d.ts +38 -0
- package/dist/tools/delete-object.d.ts.map +1 -0
- package/dist/tools/delete-object.js +69 -0
- package/dist/tools/find-completed-tasks.d.ts +74 -0
- package/dist/tools/find-completed-tasks.d.ts.map +1 -0
- package/dist/tools/find-completed-tasks.js +112 -0
- package/dist/tools/find-projects.d.ts +53 -0
- package/dist/tools/find-projects.d.ts.map +1 -0
- package/dist/tools/find-projects.js +101 -0
- package/dist/tools/find-sections.d.ts +42 -0
- package/dist/tools/find-sections.d.ts.map +1 -0
- package/dist/tools/find-sections.js +96 -0
- package/dist/tools/find-tasks-by-date.d.ts +59 -0
- package/dist/tools/find-tasks-by-date.d.ts.map +1 -0
- package/dist/tools/find-tasks-by-date.js +121 -0
- package/dist/tools/find-tasks.d.ts +65 -0
- package/dist/tools/find-tasks.d.ts.map +1 -0
- package/dist/tools/find-tasks.js +182 -0
- package/dist/tools/get-overview.d.ts +67 -0
- package/dist/tools/get-overview.d.ts.map +1 -0
- package/dist/tools/{overview.js → get-overview.js} +66 -19
- package/dist/tools/manage-projects.d.ts +35 -0
- package/dist/tools/manage-projects.d.ts.map +1 -0
- package/dist/tools/manage-projects.js +63 -0
- package/dist/tools/manage-sections.d.ts +38 -0
- package/dist/tools/manage-sections.d.ts.map +1 -0
- package/dist/tools/manage-sections.js +78 -0
- package/dist/tools/update-tasks.d.ts +94 -0
- package/dist/tools/update-tasks.d.ts.map +1 -0
- package/dist/tools/update-tasks.js +120 -0
- package/dist/utils/constants.d.ts +35 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +37 -0
- package/dist/utils/response-builders.d.ts +88 -0
- package/dist/utils/response-builders.d.ts.map +1 -0
- package/dist/utils/response-builders.js +202 -0
- package/dist/{tools → utils}/test-helpers.d.ts +16 -0
- package/dist/utils/test-helpers.d.ts.map +1 -0
- package/dist/{tools → utils}/test-helpers.js +51 -0
- package/dist/utils/tool-names.d.ts +23 -0
- package/dist/utils/tool-names.d.ts.map +1 -0
- package/dist/utils/tool-names.js +25 -0
- package/package.json +1 -1
- package/dist/tools/__tests__/delete-one.test.d.ts +0 -2
- package/dist/tools/__tests__/delete-one.test.d.ts.map +0 -1
- package/dist/tools/__tests__/overview.test.d.ts +0 -2
- package/dist/tools/__tests__/overview.test.d.ts.map +0 -1
- package/dist/tools/__tests__/projects-list.test.d.ts +0 -2
- package/dist/tools/__tests__/projects-list.test.d.ts.map +0 -1
- package/dist/tools/__tests__/projects-manage.test.d.ts +0 -2
- package/dist/tools/__tests__/projects-manage.test.d.ts.map +0 -1
- package/dist/tools/__tests__/sections-manage.test.d.ts +0 -2
- package/dist/tools/__tests__/sections-manage.test.d.ts.map +0 -1
- package/dist/tools/__tests__/sections-manage.test.js +0 -138
- package/dist/tools/__tests__/sections-search.test.d.ts +0 -2
- package/dist/tools/__tests__/sections-search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-add-multiple.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-complete-multiple.test.js +0 -146
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-list-by-date.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-list-completed.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-list-for-container.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-list-for-container.test.js +0 -232
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-organize-multiple.test.js +0 -245
- package/dist/tools/__tests__/tasks-search.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-search.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-search.test.js +0 -106
- package/dist/tools/__tests__/tasks-update-one.test.d.ts +0 -2
- package/dist/tools/__tests__/tasks-update-one.test.d.ts.map +0 -1
- package/dist/tools/__tests__/tasks-update-one.test.js +0 -251
- package/dist/tools/delete-one.d.ts +0 -17
- package/dist/tools/delete-one.d.ts.map +0 -1
- package/dist/tools/delete-one.js +0 -25
- package/dist/tools/overview.d.ts +0 -14
- package/dist/tools/overview.d.ts.map +0 -1
- package/dist/tools/projects-list.d.ts +0 -29
- package/dist/tools/projects-list.d.ts.map +0 -1
- package/dist/tools/projects-list.js +0 -39
- package/dist/tools/projects-manage.d.ts +0 -24
- package/dist/tools/projects-manage.d.ts.map +0 -1
- package/dist/tools/projects-manage.js +0 -26
- package/dist/tools/sections-manage.d.ts +0 -23
- package/dist/tools/sections-manage.d.ts.map +0 -1
- package/dist/tools/sections-manage.js +0 -37
- package/dist/tools/sections-search.d.ts +0 -18
- package/dist/tools/sections-search.d.ts.map +0 -1
- package/dist/tools/sections-search.js +0 -27
- package/dist/tools/tasks-add-multiple.d.ts.map +0 -1
- package/dist/tools/tasks-complete-multiple.d.ts +0 -16
- package/dist/tools/tasks-complete-multiple.d.ts.map +0 -1
- package/dist/tools/tasks-complete-multiple.js +0 -23
- package/dist/tools/tasks-list-by-date.d.ts +0 -34
- package/dist/tools/tasks-list-by-date.d.ts.map +0 -1
- package/dist/tools/tasks-list-by-date.js +0 -53
- package/dist/tools/tasks-list-completed.d.ts +0 -44
- package/dist/tools/tasks-list-completed.d.ts.map +0 -1
- package/dist/tools/tasks-list-completed.js +0 -49
- package/dist/tools/tasks-list-for-container.d.ts +0 -34
- package/dist/tools/tasks-list-for-container.d.ts.map +0 -1
- package/dist/tools/tasks-list-for-container.js +0 -48
- package/dist/tools/tasks-organize-multiple.d.ts +0 -37
- package/dist/tools/tasks-organize-multiple.d.ts.map +0 -1
- package/dist/tools/tasks-organize-multiple.js +0 -34
- package/dist/tools/tasks-search.d.ts +0 -32
- package/dist/tools/tasks-search.d.ts.map +0 -1
- package/dist/tools/tasks-search.js +0 -30
- package/dist/tools/tasks-update-one.d.ts +0 -29
- package/dist/tools/tasks-update-one.d.ts.map +0 -1
- package/dist/tools/tasks-update-one.js +0 -63
- package/dist/tools/test-helpers.d.ts.map +0 -1
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { jest } from '@jest/globals';
|
|
2
|
+
import { extractTextContent } from '../../utils/test-helpers.js';
|
|
3
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
+
import { completeTasks } from '../complete-tasks.js';
|
|
5
|
+
// Mock the Todoist API
|
|
6
|
+
const mockTodoistApi = {
|
|
7
|
+
closeTask: jest.fn(),
|
|
8
|
+
};
|
|
9
|
+
const { COMPLETE_TASKS, GET_OVERVIEW } = ToolNames;
|
|
10
|
+
describe(`${COMPLETE_TASKS} tool`, () => {
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
jest.clearAllMocks();
|
|
13
|
+
});
|
|
14
|
+
describe('completing multiple tasks', () => {
|
|
15
|
+
it('should complete all tasks successfully', async () => {
|
|
16
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
17
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
|
|
18
|
+
// Verify API was called for each task
|
|
19
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
|
|
20
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(1, 'task-1');
|
|
21
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(2, 'task-2');
|
|
22
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(3, 'task-3');
|
|
23
|
+
// Verify result is a concise summary
|
|
24
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
25
|
+
// Verify structured content
|
|
26
|
+
const { structuredContent } = result;
|
|
27
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
28
|
+
completed: ['task-1', 'task-2', 'task-3'],
|
|
29
|
+
failures: [],
|
|
30
|
+
totalRequested: 3,
|
|
31
|
+
successCount: 3,
|
|
32
|
+
failureCount: 0,
|
|
33
|
+
}));
|
|
34
|
+
});
|
|
35
|
+
it('should complete single task', async () => {
|
|
36
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
37
|
+
const result = await completeTasks.execute({ ids: ['8485093748'] }, mockTodoistApi);
|
|
38
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(1);
|
|
39
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('8485093748');
|
|
40
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
41
|
+
// Verify structured content
|
|
42
|
+
const { structuredContent } = result;
|
|
43
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
44
|
+
completed: ['8485093748'],
|
|
45
|
+
failures: [],
|
|
46
|
+
totalRequested: 1,
|
|
47
|
+
successCount: 1,
|
|
48
|
+
failureCount: 0,
|
|
49
|
+
}));
|
|
50
|
+
});
|
|
51
|
+
it('should handle partial failures gracefully', async () => {
|
|
52
|
+
// Mock first and third tasks to succeed, second to fail
|
|
53
|
+
mockTodoistApi.closeTask
|
|
54
|
+
.mockResolvedValueOnce(true) // task-1 succeeds
|
|
55
|
+
.mockRejectedValueOnce(new Error('Task not found')) // task-2 fails
|
|
56
|
+
.mockResolvedValueOnce(true); // task-3 succeeds
|
|
57
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
|
|
58
|
+
// Verify API was called for all tasks despite failure
|
|
59
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
|
|
60
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(1, 'task-1');
|
|
61
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(2, 'task-2');
|
|
62
|
+
expect(mockTodoistApi.closeTask).toHaveBeenNthCalledWith(3, 'task-3');
|
|
63
|
+
// Verify only successful completions are reported
|
|
64
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
65
|
+
// Verify structured content with partial failures
|
|
66
|
+
const { structuredContent } = result;
|
|
67
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
68
|
+
completed: ['task-1', 'task-3'],
|
|
69
|
+
failures: [
|
|
70
|
+
expect.objectContaining({
|
|
71
|
+
item: 'task-2',
|
|
72
|
+
error: 'Task not found',
|
|
73
|
+
}),
|
|
74
|
+
],
|
|
75
|
+
totalRequested: 3,
|
|
76
|
+
successCount: 2,
|
|
77
|
+
failureCount: 1,
|
|
78
|
+
}));
|
|
79
|
+
});
|
|
80
|
+
it('should handle all tasks failing', async () => {
|
|
81
|
+
const apiError = new Error('API Error: Network timeout');
|
|
82
|
+
mockTodoistApi.closeTask.mockRejectedValue(apiError);
|
|
83
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2'] }, mockTodoistApi);
|
|
84
|
+
// Verify API was attempted for all tasks
|
|
85
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(2);
|
|
86
|
+
// Verify no tasks were completed but still returns success
|
|
87
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
88
|
+
});
|
|
89
|
+
it('should continue processing remaining tasks after failures', async () => {
|
|
90
|
+
// Mock various failure scenarios
|
|
91
|
+
mockTodoistApi.closeTask
|
|
92
|
+
.mockRejectedValueOnce(new Error('Task already completed'))
|
|
93
|
+
.mockRejectedValueOnce(new Error('Task not found'))
|
|
94
|
+
.mockResolvedValueOnce(true) // task-3 succeeds
|
|
95
|
+
.mockRejectedValueOnce(new Error('Permission denied'))
|
|
96
|
+
.mockResolvedValueOnce(true); // task-5 succeeds
|
|
97
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3', 'task-4', 'task-5'] }, mockTodoistApi);
|
|
98
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(5);
|
|
99
|
+
// Only tasks 3 and 5 should be in completed list
|
|
100
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
101
|
+
});
|
|
102
|
+
it('should handle different types of API errors', async () => {
|
|
103
|
+
mockTodoistApi.closeTask
|
|
104
|
+
.mockRejectedValueOnce(new Error('Task not found'))
|
|
105
|
+
.mockRejectedValueOnce(new Error('Task already completed'))
|
|
106
|
+
.mockRejectedValueOnce(new Error('Permission denied'))
|
|
107
|
+
.mockRejectedValueOnce(new Error('Rate limit exceeded'));
|
|
108
|
+
const result = await completeTasks.execute({ ids: ['not-found', 'already-done', 'no-permission', 'rate-limited'] }, mockTodoistApi);
|
|
109
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(4);
|
|
110
|
+
// All should fail, but the tool should handle it gracefully
|
|
111
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe('mixed success and failure scenarios', () => {
|
|
115
|
+
it('should handle realistic mixed scenario', async () => {
|
|
116
|
+
// Simulate a realistic scenario with some tasks completing and others failing
|
|
117
|
+
mockTodoistApi.closeTask
|
|
118
|
+
.mockResolvedValueOnce(true) // regular task completion
|
|
119
|
+
.mockResolvedValueOnce(true) // another successful completion
|
|
120
|
+
.mockRejectedValueOnce(new Error('Task already completed')) // duplicate completion
|
|
121
|
+
.mockResolvedValueOnce(true) // successful completion
|
|
122
|
+
.mockRejectedValueOnce(new Error('Task not found')); // deleted task
|
|
123
|
+
const result = await completeTasks.execute({
|
|
124
|
+
ids: [
|
|
125
|
+
'8485093748', // regular task
|
|
126
|
+
'8485093749', // regular task
|
|
127
|
+
'8485093750', // already completed
|
|
128
|
+
'8485093751', // regular task
|
|
129
|
+
'8485093752', // deleted task
|
|
130
|
+
],
|
|
131
|
+
}, mockTodoistApi);
|
|
132
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(5);
|
|
133
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe('next steps logic validation', () => {
|
|
137
|
+
it('should suggest overdue tasks when all tasks complete successfully', async () => {
|
|
138
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
139
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2'] }, mockTodoistApi);
|
|
140
|
+
const textContent = extractTextContent(result);
|
|
141
|
+
expect(textContent).toMatchSnapshot();
|
|
142
|
+
expect(textContent).toContain("Use find-tasks-by-date('overdue')");
|
|
143
|
+
});
|
|
144
|
+
it('should suggest reviewing failures when mixed results', async () => {
|
|
145
|
+
mockTodoistApi.closeTask
|
|
146
|
+
.mockResolvedValueOnce(true)
|
|
147
|
+
.mockRejectedValueOnce(new Error('Task not found'));
|
|
148
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2'] }, mockTodoistApi);
|
|
149
|
+
const textContent = extractTextContent(result);
|
|
150
|
+
expect(textContent).toMatchSnapshot();
|
|
151
|
+
expect(textContent).toContain('Review failed completions and retry if needed');
|
|
152
|
+
});
|
|
153
|
+
it('should suggest checking IDs when all tasks fail', async () => {
|
|
154
|
+
mockTodoistApi.closeTask.mockRejectedValue(new Error('Task not found'));
|
|
155
|
+
const result = await completeTasks.execute({ ids: ['bad-id-1', 'bad-id-2'] }, mockTodoistApi);
|
|
156
|
+
const textContent = extractTextContent(result);
|
|
157
|
+
expect(textContent).toMatchSnapshot();
|
|
158
|
+
expect(textContent).toContain('Check task IDs and permissions, then retry');
|
|
159
|
+
expect(textContent).not.toContain(`Use ${GET_OVERVIEW} tool`); // Should only show retry message
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('error message truncation', () => {
|
|
163
|
+
it('should truncate failure messages after 3 errors', async () => {
|
|
164
|
+
mockTodoistApi.closeTask
|
|
165
|
+
.mockRejectedValueOnce(new Error('Error 1'))
|
|
166
|
+
.mockRejectedValueOnce(new Error('Error 2'))
|
|
167
|
+
.mockRejectedValueOnce(new Error('Error 3'))
|
|
168
|
+
.mockRejectedValueOnce(new Error('Error 4'))
|
|
169
|
+
.mockRejectedValueOnce(new Error('Error 5'));
|
|
170
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3', 'task-4', 'task-5'] }, mockTodoistApi);
|
|
171
|
+
const textContent = extractTextContent(result);
|
|
172
|
+
expect(textContent).toMatchSnapshot();
|
|
173
|
+
expect(textContent).toContain('+2 more'); // 5 total failures, showing first 3, so +2 more
|
|
174
|
+
expect(textContent).not.toContain('Error 4'); // Should not show 4th error
|
|
175
|
+
expect(textContent).not.toContain('Error 5'); // Should not show 5th error
|
|
176
|
+
});
|
|
177
|
+
it('should not show truncation message for exactly 3 errors', async () => {
|
|
178
|
+
mockTodoistApi.closeTask
|
|
179
|
+
.mockRejectedValueOnce(new Error('Error 1'))
|
|
180
|
+
.mockRejectedValueOnce(new Error('Error 2'))
|
|
181
|
+
.mockRejectedValueOnce(new Error('Error 3'));
|
|
182
|
+
const result = await completeTasks.execute({ ids: ['task-1', 'task-2', 'task-3'] }, mockTodoistApi);
|
|
183
|
+
const textContent = extractTextContent(result);
|
|
184
|
+
expect(textContent).toMatchSnapshot();
|
|
185
|
+
expect(textContent).not.toContain('more'); // Should not show truncation
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
describe('edge cases', () => {
|
|
189
|
+
it('should handle empty task completion (minimum one task required by schema)', async () => {
|
|
190
|
+
// Note: This test documents that the schema requires at least one task,
|
|
191
|
+
// so this scenario shouldn't occur in practice due to validation
|
|
192
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
193
|
+
const result = await completeTasks.execute({ ids: ['single-task'] }, mockTodoistApi);
|
|
194
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
195
|
+
});
|
|
196
|
+
it('should handle tasks with special ID formats', async () => {
|
|
197
|
+
mockTodoistApi.closeTask.mockResolvedValue(true);
|
|
198
|
+
const result = await completeTasks.execute({ ids: ['proj_123_task_456', 'task-with-dashes', '1234567890'] }, mockTodoistApi);
|
|
199
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledTimes(3);
|
|
200
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('proj_123_task_456');
|
|
201
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('task-with-dashes');
|
|
202
|
+
expect(mockTodoistApi.closeTask).toHaveBeenCalledWith('1234567890');
|
|
203
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"delete-object.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/delete-object.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,85 +1,105 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
|
-
import {
|
|
2
|
+
import { extractTextContent } from '../../utils/test-helpers.js';
|
|
3
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
+
import { deleteObject } from '../delete-object.js';
|
|
3
5
|
// Mock the Todoist API
|
|
4
6
|
const mockTodoistApi = {
|
|
5
7
|
deleteProject: jest.fn(),
|
|
6
8
|
deleteSection: jest.fn(),
|
|
7
9
|
deleteTask: jest.fn(),
|
|
8
10
|
};
|
|
9
|
-
|
|
11
|
+
const { FIND_PROJECTS, FIND_TASKS_BY_DATE, DELETE_OBJECT } = ToolNames;
|
|
12
|
+
describe(`${DELETE_OBJECT} tool`, () => {
|
|
10
13
|
beforeEach(() => {
|
|
11
14
|
jest.clearAllMocks();
|
|
12
15
|
});
|
|
13
16
|
describe('deleting projects', () => {
|
|
14
17
|
it('should delete a project by ID', async () => {
|
|
15
18
|
mockTodoistApi.deleteProject.mockResolvedValue(true);
|
|
16
|
-
const result = await
|
|
17
|
-
// Verify API was called correctly
|
|
19
|
+
const result = await deleteObject.execute({ type: 'project', id: '6cfCcrrCFg2xP94Q' }, mockTodoistApi);
|
|
18
20
|
expect(mockTodoistApi.deleteProject).toHaveBeenCalledWith('6cfCcrrCFg2xP94Q');
|
|
19
21
|
expect(mockTodoistApi.deleteSection).not.toHaveBeenCalled();
|
|
20
22
|
expect(mockTodoistApi.deleteTask).not.toHaveBeenCalled();
|
|
21
|
-
|
|
22
|
-
expect(
|
|
23
|
+
const textContent = extractTextContent(result);
|
|
24
|
+
expect(textContent).toMatchSnapshot();
|
|
25
|
+
expect(textContent).toContain('Deleted project: id=6cfCcrrCFg2xP94Q');
|
|
26
|
+
expect(textContent).toContain(`Use ${FIND_PROJECTS} to see remaining projects`);
|
|
27
|
+
expect(result.structuredContent).toEqual({
|
|
28
|
+
deletedEntity: {
|
|
29
|
+
type: 'project',
|
|
30
|
+
id: '6cfCcrrCFg2xP94Q',
|
|
31
|
+
},
|
|
32
|
+
success: true,
|
|
33
|
+
});
|
|
23
34
|
});
|
|
24
35
|
it('should propagate project deletion errors', async () => {
|
|
25
36
|
const apiError = new Error('API Error: Cannot delete project with tasks');
|
|
26
37
|
mockTodoistApi.deleteProject.mockRejectedValue(apiError);
|
|
27
|
-
await expect(
|
|
38
|
+
await expect(deleteObject.execute({ type: 'project', id: 'project-with-tasks' }, mockTodoistApi)).rejects.toThrow('API Error: Cannot delete project with tasks');
|
|
28
39
|
});
|
|
29
40
|
});
|
|
30
41
|
describe('deleting sections', () => {
|
|
31
42
|
it('should delete a section by ID', async () => {
|
|
32
43
|
mockTodoistApi.deleteSection.mockResolvedValue(true);
|
|
33
|
-
const result = await
|
|
34
|
-
// Verify API was called correctly
|
|
44
|
+
const result = await deleteObject.execute({ type: 'section', id: 'section-123' }, mockTodoistApi);
|
|
35
45
|
expect(mockTodoistApi.deleteSection).toHaveBeenCalledWith('section-123');
|
|
36
46
|
expect(mockTodoistApi.deleteProject).not.toHaveBeenCalled();
|
|
37
47
|
expect(mockTodoistApi.deleteTask).not.toHaveBeenCalled();
|
|
38
|
-
|
|
39
|
-
expect(
|
|
48
|
+
const textContent = extractTextContent(result);
|
|
49
|
+
expect(textContent).toMatchSnapshot();
|
|
50
|
+
expect(textContent).toContain('Deleted section: id=section-123');
|
|
51
|
+
expect(textContent).toContain(`Use ${ToolNames.FIND_SECTIONS} to see remaining sections`);
|
|
52
|
+
expect(result.structuredContent).toEqual({
|
|
53
|
+
deletedEntity: { type: 'section', id: 'section-123' },
|
|
54
|
+
success: true,
|
|
55
|
+
});
|
|
40
56
|
});
|
|
41
57
|
it('should propagate section deletion errors', async () => {
|
|
42
58
|
const apiError = new Error('API Error: Section not found');
|
|
43
59
|
mockTodoistApi.deleteSection.mockRejectedValue(apiError);
|
|
44
|
-
await expect(
|
|
60
|
+
await expect(deleteObject.execute({ type: 'section', id: 'non-existent-section' }, mockTodoistApi)).rejects.toThrow('API Error: Section not found');
|
|
45
61
|
});
|
|
46
62
|
});
|
|
47
63
|
describe('deleting tasks', () => {
|
|
48
64
|
it('should delete a task by ID', async () => {
|
|
49
65
|
mockTodoistApi.deleteTask.mockResolvedValue(true);
|
|
50
|
-
const result = await
|
|
51
|
-
// Verify API was called correctly
|
|
66
|
+
const result = await deleteObject.execute({ type: 'task', id: '8485093748' }, mockTodoistApi);
|
|
52
67
|
expect(mockTodoistApi.deleteTask).toHaveBeenCalledWith('8485093748');
|
|
53
68
|
expect(mockTodoistApi.deleteProject).not.toHaveBeenCalled();
|
|
54
69
|
expect(mockTodoistApi.deleteSection).not.toHaveBeenCalled();
|
|
55
|
-
|
|
56
|
-
expect(
|
|
70
|
+
const textContent = extractTextContent(result);
|
|
71
|
+
expect(textContent).toMatchSnapshot();
|
|
72
|
+
expect(textContent).toContain('Deleted task: id=8485093748');
|
|
73
|
+
expect(textContent).toContain(`Use ${FIND_TASKS_BY_DATE} to see remaining tasks`);
|
|
74
|
+
expect(result.structuredContent).toEqual({
|
|
75
|
+
deletedEntity: { type: 'task', id: '8485093748' },
|
|
76
|
+
success: true,
|
|
77
|
+
});
|
|
57
78
|
});
|
|
58
79
|
it('should propagate task deletion errors', async () => {
|
|
59
80
|
const apiError = new Error('API Error: Task not found');
|
|
60
81
|
mockTodoistApi.deleteTask.mockRejectedValue(apiError);
|
|
61
|
-
await expect(
|
|
82
|
+
await expect(deleteObject.execute({ type: 'task', id: 'non-existent-task' }, mockTodoistApi)).rejects.toThrow('API Error: Task not found');
|
|
62
83
|
});
|
|
63
84
|
it('should handle permission errors', async () => {
|
|
64
85
|
const apiError = new Error('API Error: Insufficient permissions to delete task');
|
|
65
86
|
mockTodoistApi.deleteTask.mockRejectedValue(apiError);
|
|
66
|
-
await expect(
|
|
87
|
+
await expect(deleteObject.execute({ type: 'task', id: 'restricted-task' }, mockTodoistApi)).rejects.toThrow('API Error: Insufficient permissions to delete task');
|
|
67
88
|
});
|
|
68
89
|
});
|
|
69
90
|
describe('type validation', () => {
|
|
70
91
|
it('should handle all supported entity types', async () => {
|
|
71
|
-
// Test all three supported types work correctly
|
|
72
92
|
mockTodoistApi.deleteProject.mockResolvedValue(true);
|
|
73
93
|
mockTodoistApi.deleteSection.mockResolvedValue(true);
|
|
74
94
|
mockTodoistApi.deleteTask.mockResolvedValue(true);
|
|
75
95
|
// Delete project
|
|
76
|
-
await
|
|
96
|
+
await deleteObject.execute({ type: 'project', id: 'proj-1' }, mockTodoistApi);
|
|
77
97
|
expect(mockTodoistApi.deleteProject).toHaveBeenCalledWith('proj-1');
|
|
78
98
|
// Delete section
|
|
79
|
-
await
|
|
99
|
+
await deleteObject.execute({ type: 'section', id: 'sect-1' }, mockTodoistApi);
|
|
80
100
|
expect(mockTodoistApi.deleteSection).toHaveBeenCalledWith('sect-1');
|
|
81
101
|
// Delete task
|
|
82
|
-
await
|
|
102
|
+
await deleteObject.execute({ type: 'task', id: 'task-1' }, mockTodoistApi);
|
|
83
103
|
expect(mockTodoistApi.deleteTask).toHaveBeenCalledWith('task-1');
|
|
84
104
|
// Verify each API method was called exactly once
|
|
85
105
|
expect(mockTodoistApi.deleteProject).toHaveBeenCalledTimes(1);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-completed-tasks.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-completed-tasks.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { createMockTask, extractTextContent } from '../../utils/test-helpers.js';
|
|
3
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
+
import { findCompletedTasks } from '../find-completed-tasks.js';
|
|
4
5
|
// Mock the Todoist API
|
|
5
6
|
const mockTodoistApi = {
|
|
6
7
|
getCompletedTasksByCompletionDate: jest.fn(),
|
|
7
8
|
getCompletedTasksByDueDate: jest.fn(),
|
|
8
9
|
};
|
|
9
|
-
|
|
10
|
+
const { FIND_COMPLETED_TASKS } = ToolNames;
|
|
11
|
+
describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
10
12
|
beforeEach(() => {
|
|
11
13
|
jest.clearAllMocks();
|
|
12
14
|
});
|
|
@@ -35,32 +37,20 @@ describe('tasks-list-completed tool', () => {
|
|
|
35
37
|
items: mockCompletedTasks,
|
|
36
38
|
nextCursor: null,
|
|
37
39
|
});
|
|
38
|
-
const result = await
|
|
40
|
+
const result = await findCompletedTasks.execute({ getBy: 'completion', limit: 50, since: '2025-08-10', until: '2025-08-15' }, mockTodoistApi);
|
|
39
41
|
expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
|
|
40
42
|
since: '2025-08-10',
|
|
41
43
|
until: '2025-08-15',
|
|
42
44
|
limit: 50,
|
|
43
45
|
});
|
|
44
|
-
expect(result).
|
|
45
|
-
tasks: [
|
|
46
|
-
expect.objectContaining({
|
|
47
|
-
id: '8485093748',
|
|
48
|
-
content: 'Completed task 1',
|
|
49
|
-
description: 'Task completed yesterday',
|
|
50
|
-
dueDate: '2025-08-14',
|
|
51
|
-
priority: 2,
|
|
52
|
-
labels: ['work'],
|
|
53
|
-
}),
|
|
54
|
-
],
|
|
55
|
-
nextCursor: null,
|
|
56
|
-
});
|
|
46
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
57
47
|
});
|
|
58
48
|
it('should handle explicit completion date query', async () => {
|
|
59
49
|
mockTodoistApi.getCompletedTasksByCompletionDate.mockResolvedValue({
|
|
60
50
|
items: [],
|
|
61
51
|
nextCursor: 'next-cursor',
|
|
62
52
|
});
|
|
63
|
-
const result = await
|
|
53
|
+
const result = await findCompletedTasks.execute({
|
|
64
54
|
getBy: 'completion',
|
|
65
55
|
limit: 100,
|
|
66
56
|
since: '2025-08-01',
|
|
@@ -75,7 +65,7 @@ describe('tasks-list-completed tool', () => {
|
|
|
75
65
|
limit: 100,
|
|
76
66
|
cursor: 'current-cursor',
|
|
77
67
|
});
|
|
78
|
-
expect(result).
|
|
68
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
79
69
|
});
|
|
80
70
|
});
|
|
81
71
|
describe('getting completed tasks by due date', () => {
|
|
@@ -103,7 +93,7 @@ describe('tasks-list-completed tool', () => {
|
|
|
103
93
|
items: mockCompletedTasks,
|
|
104
94
|
nextCursor: null,
|
|
105
95
|
});
|
|
106
|
-
const result = await
|
|
96
|
+
const result = await findCompletedTasks.execute({
|
|
107
97
|
getBy: 'due',
|
|
108
98
|
limit: 50,
|
|
109
99
|
since: '2025-08-10',
|
|
@@ -115,34 +105,21 @@ describe('tasks-list-completed tool', () => {
|
|
|
115
105
|
limit: 50,
|
|
116
106
|
});
|
|
117
107
|
expect(mockTodoistApi.getCompletedTasksByCompletionDate).not.toHaveBeenCalled();
|
|
118
|
-
expect(result).
|
|
119
|
-
tasks: [
|
|
120
|
-
expect.objectContaining({
|
|
121
|
-
id: '8485093750',
|
|
122
|
-
content: 'Task completed by due date',
|
|
123
|
-
description: 'This task was due and completed',
|
|
124
|
-
dueDate: '2025-08-15',
|
|
125
|
-
recurring: 'every Monday',
|
|
126
|
-
priority: 3,
|
|
127
|
-
labels: ['urgent'],
|
|
128
|
-
}),
|
|
129
|
-
],
|
|
130
|
-
nextCursor: null,
|
|
131
|
-
});
|
|
108
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
132
109
|
});
|
|
133
110
|
});
|
|
134
111
|
describe('error handling', () => {
|
|
135
112
|
it('should propagate completion date API errors', async () => {
|
|
136
113
|
const apiError = new Error('API Error: Invalid date range');
|
|
137
114
|
mockTodoistApi.getCompletedTasksByCompletionDate.mockRejectedValue(apiError);
|
|
138
|
-
await expect(
|
|
115
|
+
await expect(findCompletedTasks.execute(
|
|
139
116
|
// invalid date range
|
|
140
117
|
{ getBy: 'completion', limit: 50, since: '2025-08-31', until: '2025-08-01' }, mockTodoistApi)).rejects.toThrow('API Error: Invalid date range');
|
|
141
118
|
});
|
|
142
119
|
it('should propagate due date API errors', async () => {
|
|
143
120
|
const apiError = new Error('API Error: Project not found');
|
|
144
121
|
mockTodoistApi.getCompletedTasksByDueDate.mockRejectedValue(apiError);
|
|
145
|
-
await expect(
|
|
122
|
+
await expect(findCompletedTasks.execute({
|
|
146
123
|
getBy: 'due',
|
|
147
124
|
limit: 50,
|
|
148
125
|
since: '2025-08-01',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-projects.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-projects.test.ts"],"names":[],"mappings":""}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { jest } from '@jest/globals';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { TEST_ERRORS, TEST_IDS, createMockApiResponse, createMockProject, extractStructuredContent, extractTextContent, } from '../../utils/test-helpers.js';
|
|
3
|
+
import { ToolNames } from '../../utils/tool-names.js';
|
|
4
|
+
import { findProjects } from '../find-projects.js';
|
|
4
5
|
// Mock the Todoist API
|
|
5
6
|
const mockTodoistApi = {
|
|
6
7
|
getProjects: jest.fn(),
|
|
7
8
|
};
|
|
8
|
-
|
|
9
|
+
const { FIND_PROJECTS } = ToolNames;
|
|
10
|
+
describe(`${FIND_PROJECTS} tool`, () => {
|
|
9
11
|
beforeEach(() => {
|
|
10
12
|
jest.clearAllMocks();
|
|
11
13
|
});
|
|
@@ -38,37 +40,27 @@ describe('projects-list tool', () => {
|
|
|
38
40
|
}),
|
|
39
41
|
];
|
|
40
42
|
mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse(mockProjects));
|
|
41
|
-
const result = await
|
|
43
|
+
const result = await findProjects.execute({ limit: 50 }, mockTodoistApi);
|
|
42
44
|
// Verify API was called correctly
|
|
43
45
|
expect(mockTodoistApi.getProjects).toHaveBeenCalledWith({
|
|
44
46
|
limit: 50,
|
|
45
47
|
cursor: null,
|
|
46
48
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
inboxProject: true,
|
|
55
|
-
}),
|
|
56
|
-
expect.objectContaining({
|
|
57
|
-
id: TEST_IDS.PROJECT_TEST,
|
|
58
|
-
name: 'test-abc123def456-project',
|
|
59
|
-
color: 'charcoal',
|
|
60
|
-
}),
|
|
61
|
-
expect.objectContaining({
|
|
62
|
-
id: TEST_IDS.PROJECT_WORK,
|
|
63
|
-
name: 'Work Project',
|
|
64
|
-
color: 'blue',
|
|
65
|
-
isFavorite: true,
|
|
66
|
-
isShared: true,
|
|
67
|
-
viewStyle: 'board',
|
|
68
|
-
}),
|
|
69
|
-
],
|
|
49
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
50
|
+
// Verify structured content
|
|
51
|
+
const structuredContent = extractStructuredContent(result);
|
|
52
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
53
|
+
projects: expect.any(Array),
|
|
54
|
+
totalCount: 3,
|
|
55
|
+
hasMore: false,
|
|
70
56
|
nextCursor: null,
|
|
71
|
-
|
|
57
|
+
appliedFilters: {
|
|
58
|
+
search: undefined,
|
|
59
|
+
limit: 50,
|
|
60
|
+
cursor: undefined,
|
|
61
|
+
},
|
|
62
|
+
}));
|
|
63
|
+
expect(structuredContent.projects).toHaveLength(3);
|
|
72
64
|
});
|
|
73
65
|
it('should handle pagination with limit and cursor', async () => {
|
|
74
66
|
const mockProject = createMockProject({
|
|
@@ -77,14 +69,23 @@ describe('projects-list tool', () => {
|
|
|
77
69
|
color: 'red',
|
|
78
70
|
});
|
|
79
71
|
mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse([mockProject], 'next-page-cursor'));
|
|
80
|
-
const result = await
|
|
72
|
+
const result = await findProjects.execute({ limit: 10, cursor: 'current-page-cursor' }, mockTodoistApi);
|
|
81
73
|
expect(mockTodoistApi.getProjects).toHaveBeenCalledWith({
|
|
82
74
|
limit: 10,
|
|
83
75
|
cursor: 'current-page-cursor',
|
|
84
76
|
});
|
|
85
|
-
expect(result
|
|
86
|
-
|
|
87
|
-
|
|
77
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
78
|
+
// Verify structured content
|
|
79
|
+
const structuredContent = extractStructuredContent(result);
|
|
80
|
+
expect(structuredContent.projects).toHaveLength(1);
|
|
81
|
+
expect(structuredContent.totalCount).toBe(1);
|
|
82
|
+
expect(structuredContent.hasMore).toBe(true);
|
|
83
|
+
expect(structuredContent.nextCursor).toBe('next-page-cursor');
|
|
84
|
+
expect(structuredContent.appliedFilters).toEqual({
|
|
85
|
+
search: undefined,
|
|
86
|
+
limit: 10,
|
|
87
|
+
cursor: 'current-page-cursor',
|
|
88
|
+
});
|
|
88
89
|
});
|
|
89
90
|
});
|
|
90
91
|
describe('searching projects', () => {
|
|
@@ -103,10 +104,20 @@ describe('projects-list tool', () => {
|
|
|
103
104
|
createMockProject({ id: 'hobby-project-id', name: 'Hobby Work', color: 'orange' }),
|
|
104
105
|
];
|
|
105
106
|
mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse(mockProjects));
|
|
106
|
-
const result = await
|
|
107
|
+
const result = await findProjects.execute({ search: 'work', limit: 50 }, mockTodoistApi);
|
|
107
108
|
expect(mockTodoistApi.getProjects).toHaveBeenCalledWith({ limit: 50, cursor: null });
|
|
108
|
-
expect(result
|
|
109
|
-
|
|
109
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
110
|
+
// Verify structured content with search filter
|
|
111
|
+
const structuredContent = extractStructuredContent(result);
|
|
112
|
+
expect(structuredContent.projects).toHaveLength(2); // Should match filtered results
|
|
113
|
+
expect(structuredContent.totalCount).toBe(2);
|
|
114
|
+
expect(structuredContent.hasMore).toBe(false);
|
|
115
|
+
expect(structuredContent.nextCursor).toBeNull();
|
|
116
|
+
expect(structuredContent.appliedFilters).toEqual({
|
|
117
|
+
search: 'work',
|
|
118
|
+
limit: 50,
|
|
119
|
+
cursor: undefined,
|
|
120
|
+
});
|
|
110
121
|
});
|
|
111
122
|
it.each([
|
|
112
123
|
{
|
|
@@ -121,11 +132,16 @@ describe('projects-list tool', () => {
|
|
|
121
132
|
expectedCount: 1,
|
|
122
133
|
description: 'case insensitive matching',
|
|
123
134
|
},
|
|
124
|
-
])('should handle search with $description', async ({ search, projects
|
|
135
|
+
])('should handle search with $description', async ({ search, projects }) => {
|
|
125
136
|
const mockProjects = projects.map((name) => createMockProject({ name }));
|
|
126
137
|
mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse(mockProjects));
|
|
127
|
-
const result = await
|
|
128
|
-
expect(result
|
|
138
|
+
const result = await findProjects.execute({ search, limit: 50 }, mockTodoistApi);
|
|
139
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
140
|
+
// Verify structured content
|
|
141
|
+
const structuredContent = extractStructuredContent(result);
|
|
142
|
+
expect(structuredContent).toEqual(expect.objectContaining({
|
|
143
|
+
appliedFilters: expect.objectContaining({ search }),
|
|
144
|
+
}));
|
|
129
145
|
});
|
|
130
146
|
});
|
|
131
147
|
describe('error handling', () => {
|
|
@@ -134,7 +150,7 @@ describe('projects-list tool', () => {
|
|
|
134
150
|
{ error: TEST_ERRORS.INVALID_CURSOR, params: { cursor: 'invalid-cursor', limit: 50 } },
|
|
135
151
|
])('should propagate $error', async ({ error, params }) => {
|
|
136
152
|
mockTodoistApi.getProjects.mockRejectedValue(new Error(error));
|
|
137
|
-
await expect(
|
|
153
|
+
await expect(findProjects.execute(params, mockTodoistApi)).rejects.toThrow(error);
|
|
138
154
|
});
|
|
139
155
|
});
|
|
140
156
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-sections.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/find-sections.test.ts"],"names":[],"mappings":""}
|