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