@doist/todoist-ai 4.10.0 → 4.11.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.
|
@@ -6,11 +6,25 @@ import { findCompletedTasks } from '../find-completed-tasks.js';
|
|
|
6
6
|
const mockTodoistApi = {
|
|
7
7
|
getCompletedTasksByCompletionDate: jest.fn(),
|
|
8
8
|
getCompletedTasksByDueDate: jest.fn(),
|
|
9
|
+
getUser: jest.fn(),
|
|
9
10
|
};
|
|
10
11
|
const { FIND_COMPLETED_TASKS } = ToolNames;
|
|
11
12
|
describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
12
13
|
beforeEach(() => {
|
|
13
14
|
jest.clearAllMocks();
|
|
15
|
+
// Mock default user with UTC timezone
|
|
16
|
+
mockTodoistApi.getUser.mockResolvedValue({
|
|
17
|
+
id: 'test-user-id',
|
|
18
|
+
fullName: 'Test User',
|
|
19
|
+
email: 'test@example.com',
|
|
20
|
+
tzInfo: {
|
|
21
|
+
timezone: 'UTC',
|
|
22
|
+
gmtString: '+00:00',
|
|
23
|
+
hours: 0,
|
|
24
|
+
minutes: 0,
|
|
25
|
+
isDst: 0,
|
|
26
|
+
},
|
|
27
|
+
});
|
|
14
28
|
});
|
|
15
29
|
describe('getting completed tasks by completion date (default)', () => {
|
|
16
30
|
it('should get completed tasks by completion date', async () => {
|
|
@@ -46,8 +60,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
|
46
60
|
labelsOperator: 'or',
|
|
47
61
|
}, mockTodoistApi);
|
|
48
62
|
expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
|
|
49
|
-
since: '2025-08-
|
|
50
|
-
until: '2025-08-
|
|
63
|
+
since: '2025-08-10T00:00:00.000Z',
|
|
64
|
+
until: '2025-08-15T23:59:59.000Z',
|
|
51
65
|
limit: 50,
|
|
52
66
|
});
|
|
53
67
|
expect(extractTextContent(result)).toMatchSnapshot();
|
|
@@ -68,8 +82,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
|
68
82
|
labelsOperator: 'or',
|
|
69
83
|
}, mockTodoistApi);
|
|
70
84
|
expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
|
|
71
|
-
since: '2025-08-
|
|
72
|
-
until: '2025-08-
|
|
85
|
+
since: '2025-08-01T00:00:00.000Z',
|
|
86
|
+
until: '2025-08-31T23:59:59.000Z',
|
|
73
87
|
projectId: 'specific-project-id',
|
|
74
88
|
limit: 100,
|
|
75
89
|
cursor: 'current-cursor',
|
|
@@ -111,8 +125,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
|
111
125
|
labelsOperator: 'or',
|
|
112
126
|
}, mockTodoistApi);
|
|
113
127
|
expect(mockTodoistApi.getCompletedTasksByDueDate).toHaveBeenCalledWith({
|
|
114
|
-
since: '2025-08-
|
|
115
|
-
until: '2025-08-
|
|
128
|
+
since: '2025-08-10T00:00:00.000Z',
|
|
129
|
+
until: '2025-08-20T23:59:59.000Z',
|
|
116
130
|
limit: 50,
|
|
117
131
|
});
|
|
118
132
|
expect(mockTodoistApi.getCompletedTasksByCompletionDate).not.toHaveBeenCalled();
|
|
@@ -172,8 +186,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
|
172
186
|
mockMethod.mockResolvedValue(mockResponse);
|
|
173
187
|
const result = await findCompletedTasks.execute(params, mockTodoistApi);
|
|
174
188
|
expect(mockMethod).toHaveBeenCalledWith({
|
|
175
|
-
since: params.since
|
|
176
|
-
until: params.until
|
|
189
|
+
since: `${params.since}T00:00:00.000Z`,
|
|
190
|
+
until: `${params.until}T23:59:59.000Z`,
|
|
177
191
|
limit: params.limit,
|
|
178
192
|
filterQuery: expectedFilter,
|
|
179
193
|
filterLang: 'en',
|
|
@@ -194,8 +208,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
|
194
208
|
mockTodoistApi.getCompletedTasksByCompletionDate.mockResolvedValue(mockResponse);
|
|
195
209
|
await findCompletedTasks.execute(params, mockTodoistApi);
|
|
196
210
|
expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
|
|
197
|
-
since: params.since
|
|
198
|
-
until: params.until
|
|
211
|
+
since: `${params.since}T00:00:00.000Z`,
|
|
212
|
+
until: `${params.until}T23:59:59.000Z`,
|
|
199
213
|
limit: params.limit,
|
|
200
214
|
});
|
|
201
215
|
});
|
|
@@ -221,8 +235,8 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
|
221
235
|
mockTodoistApi.getCompletedTasksByDueDate.mockResolvedValue(mockResponse);
|
|
222
236
|
const result = await findCompletedTasks.execute(params, mockTodoistApi);
|
|
223
237
|
expect(mockTodoistApi.getCompletedTasksByDueDate).toHaveBeenCalledWith({
|
|
224
|
-
since: params.since
|
|
225
|
-
until: params.until
|
|
238
|
+
since: `${params.since}T00:00:00.000Z`,
|
|
239
|
+
until: `${params.until}T23:59:59.000Z`,
|
|
226
240
|
limit: params.limit,
|
|
227
241
|
projectId: params.projectId,
|
|
228
242
|
sectionId: params.sectionId,
|
|
@@ -233,6 +247,51 @@ describe(`${FIND_COMPLETED_TASKS} tool`, () => {
|
|
|
233
247
|
expect(textContent).toMatchSnapshot();
|
|
234
248
|
});
|
|
235
249
|
});
|
|
250
|
+
describe('timezone handling', () => {
|
|
251
|
+
it('should convert user timezone to UTC correctly (Europe/Madrid)', async () => {
|
|
252
|
+
// Mock user with Madrid timezone
|
|
253
|
+
mockTodoistApi.getUser.mockResolvedValue({
|
|
254
|
+
id: 'test-user-id',
|
|
255
|
+
fullName: 'Test User',
|
|
256
|
+
email: 'test@example.com',
|
|
257
|
+
tzInfo: {
|
|
258
|
+
timezone: 'Europe/Madrid',
|
|
259
|
+
gmtString: '+02:00',
|
|
260
|
+
hours: 2,
|
|
261
|
+
minutes: 0,
|
|
262
|
+
isDst: 0,
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
const mockCompletedTasks = [
|
|
266
|
+
createMockTask({
|
|
267
|
+
id: '8485093750',
|
|
268
|
+
content: 'Task completed in Madrid timezone',
|
|
269
|
+
completedAt: '2025-10-11T15:30:00Z',
|
|
270
|
+
}),
|
|
271
|
+
];
|
|
272
|
+
mockTodoistApi.getCompletedTasksByCompletionDate.mockResolvedValue({
|
|
273
|
+
items: mockCompletedTasks,
|
|
274
|
+
nextCursor: null,
|
|
275
|
+
});
|
|
276
|
+
const result = await findCompletedTasks.execute({
|
|
277
|
+
getBy: 'completion',
|
|
278
|
+
limit: 50,
|
|
279
|
+
since: '2025-10-11',
|
|
280
|
+
until: '2025-10-11',
|
|
281
|
+
labels: [],
|
|
282
|
+
labelsOperator: 'or',
|
|
283
|
+
}, mockTodoistApi);
|
|
284
|
+
// Should convert Madrid local time to UTC
|
|
285
|
+
// 2025-10-11 00:00:00 +02:00 = 2025-10-10 22:00:00 UTC
|
|
286
|
+
// 2025-10-11 23:59:59 +02:00 = 2025-10-11 21:59:59 UTC
|
|
287
|
+
expect(mockTodoistApi.getCompletedTasksByCompletionDate).toHaveBeenCalledWith({
|
|
288
|
+
since: '2025-10-10T22:00:00.000Z',
|
|
289
|
+
until: '2025-10-11T21:59:59.000Z',
|
|
290
|
+
limit: 50,
|
|
291
|
+
});
|
|
292
|
+
expect(extractTextContent(result)).toMatchSnapshot();
|
|
293
|
+
});
|
|
294
|
+
});
|
|
236
295
|
describe('error handling', () => {
|
|
237
296
|
it('should propagate completion date API errors', async () => {
|
|
238
297
|
const apiError = new Error('API Error: Invalid date range');
|
|
@@ -16,17 +16,17 @@ declare const findCompletedTasks: {
|
|
|
16
16
|
cursor: z.ZodOptional<z.ZodString>;
|
|
17
17
|
};
|
|
18
18
|
execute(args: {
|
|
19
|
-
|
|
20
|
-
getBy: "due" | "completion";
|
|
19
|
+
getBy: "completion" | "due";
|
|
21
20
|
since: string;
|
|
22
21
|
until: string;
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
limit: number;
|
|
23
|
+
labels?: string[] | undefined;
|
|
24
|
+
labelsOperator?: "and" | "or" | undefined;
|
|
25
25
|
workspaceId?: string | undefined;
|
|
26
|
+
projectId?: string | undefined;
|
|
26
27
|
sectionId?: string | undefined;
|
|
27
|
-
|
|
28
|
+
parentId?: string | undefined;
|
|
28
29
|
cursor?: string | undefined;
|
|
29
|
-
labelsOperator?: "and" | "or" | undefined;
|
|
30
30
|
}, client: import("@doist/todoist-api-typescript").TodoistApi): Promise<{
|
|
31
31
|
content: {
|
|
32
32
|
type: "text";
|
|
@@ -52,17 +52,17 @@ declare const findCompletedTasks: {
|
|
|
52
52
|
totalCount: number;
|
|
53
53
|
hasMore: boolean;
|
|
54
54
|
appliedFilters: {
|
|
55
|
-
|
|
56
|
-
getBy: "due" | "completion";
|
|
55
|
+
getBy: "completion" | "due";
|
|
57
56
|
since: string;
|
|
58
57
|
until: string;
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
limit: number;
|
|
59
|
+
labels?: string[] | undefined;
|
|
60
|
+
labelsOperator?: "and" | "or" | undefined;
|
|
61
61
|
workspaceId?: string | undefined;
|
|
62
|
+
projectId?: string | undefined;
|
|
62
63
|
sectionId?: string | undefined;
|
|
63
|
-
|
|
64
|
+
parentId?: string | undefined;
|
|
64
65
|
cursor?: string | undefined;
|
|
65
|
-
labelsOperator?: "and" | "or" | undefined;
|
|
66
66
|
};
|
|
67
67
|
};
|
|
68
68
|
} | {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"find-completed-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-completed-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiDvB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"find-completed-tasks.d.ts","sourceRoot":"","sources":["../../src/tools/find-completed-tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAiDvB,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsDkB,CAAA;AAmE1C,OAAO,EAAE,kBAAkB,EAAE,CAAA"}
|
|
@@ -43,15 +43,29 @@ const findCompletedTasks = {
|
|
|
43
43
|
description: 'Get completed tasks.',
|
|
44
44
|
parameters: ArgsSchema,
|
|
45
45
|
async execute(args, client) {
|
|
46
|
-
const { getBy, labels, labelsOperator, ...rest } = args;
|
|
46
|
+
const { getBy, labels, labelsOperator, since, until, ...rest } = args;
|
|
47
47
|
const labelsFilter = generateLabelsFilter(labels, labelsOperator);
|
|
48
|
+
// Get user timezone to convert local dates to UTC
|
|
49
|
+
const user = await client.getUser();
|
|
50
|
+
const userGmtOffset = user.tzInfo?.gmtString || '+00:00';
|
|
51
|
+
// Convert user's local date to UTC timestamps
|
|
52
|
+
// This ensures we capture the entire day from the user's perspective
|
|
53
|
+
const sinceWithOffset = `${since}T00:00:00${userGmtOffset}`;
|
|
54
|
+
const untilWithOffset = `${until}T23:59:59${userGmtOffset}`;
|
|
55
|
+
// Parse and convert to UTC
|
|
56
|
+
const sinceDateTime = new Date(sinceWithOffset).toISOString();
|
|
57
|
+
const untilDateTime = new Date(untilWithOffset).toISOString();
|
|
48
58
|
const { items, nextCursor } = getBy === 'completion'
|
|
49
59
|
? await client.getCompletedTasksByCompletionDate({
|
|
50
60
|
...rest,
|
|
61
|
+
since: sinceDateTime,
|
|
62
|
+
until: untilDateTime,
|
|
51
63
|
...(labelsFilter ? { filterQuery: labelsFilter, filterLang: 'en' } : {}),
|
|
52
64
|
})
|
|
53
65
|
: await client.getCompletedTasksByDueDate({
|
|
54
66
|
...rest,
|
|
67
|
+
since: sinceDateTime,
|
|
68
|
+
until: untilDateTime,
|
|
55
69
|
...(labelsFilter ? { filterQuery: labelsFilter, filterLang: 'en' } : {}),
|
|
56
70
|
});
|
|
57
71
|
const mappedTasks = items.map(mapTask);
|