@doist/todoist-ai 1.1.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +8 -22
  2. package/dist/index.d.ts +64 -209
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +31 -74
  5. package/dist/main.js +6 -11
  6. package/dist/mcp-helpers.d.ts +10 -3
  7. package/dist/mcp-helpers.d.ts.map +1 -1
  8. package/dist/mcp-helpers.js +1 -3
  9. package/dist/mcp-server.d.ts +1 -1
  10. package/dist/mcp-server.d.ts.map +1 -1
  11. package/dist/mcp-server.js +34 -54
  12. package/dist/todoist-tool.js +1 -2
  13. package/dist/{tools/shared.d.ts → tool-helpers.d.ts} +12 -2
  14. package/dist/tool-helpers.d.ts.map +1 -0
  15. package/dist/{tools/shared.js → tool-helpers.js} +41 -22
  16. package/dist/tool-helpers.test.d.ts +2 -0
  17. package/dist/tool-helpers.test.d.ts.map +1 -0
  18. package/dist/{tools/shared.test.js → tool-helpers.test.js} +35 -14
  19. package/dist/tools/__tests__/delete-one.test.d.ts +2 -0
  20. package/dist/tools/__tests__/delete-one.test.d.ts.map +1 -0
  21. package/dist/tools/__tests__/delete-one.test.js +90 -0
  22. package/dist/tools/__tests__/overview.test.d.ts +2 -0
  23. package/dist/tools/__tests__/overview.test.d.ts.map +1 -0
  24. package/dist/tools/__tests__/overview.test.js +163 -0
  25. package/dist/tools/__tests__/projects-list.test.d.ts +2 -0
  26. package/dist/tools/__tests__/projects-list.test.d.ts.map +1 -0
  27. package/dist/tools/__tests__/projects-list.test.js +140 -0
  28. package/dist/tools/__tests__/projects-manage.test.d.ts +2 -0
  29. package/dist/tools/__tests__/projects-manage.test.d.ts.map +1 -0
  30. package/dist/tools/__tests__/projects-manage.test.js +106 -0
  31. package/dist/tools/__tests__/sections-manage.test.d.ts +2 -0
  32. package/dist/tools/__tests__/sections-manage.test.d.ts.map +1 -0
  33. package/dist/tools/__tests__/sections-manage.test.js +138 -0
  34. package/dist/tools/__tests__/sections-search.test.d.ts +2 -0
  35. package/dist/tools/__tests__/sections-search.test.d.ts.map +1 -0
  36. package/dist/tools/__tests__/sections-search.test.js +235 -0
  37. package/dist/tools/__tests__/tasks-add-multiple.test.d.ts +2 -0
  38. package/dist/tools/__tests__/tasks-add-multiple.test.d.ts.map +1 -0
  39. package/dist/tools/__tests__/tasks-add-multiple.test.js +160 -0
  40. package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts +2 -0
  41. package/dist/tools/__tests__/tasks-complete-multiple.test.d.ts.map +1 -0
  42. package/dist/tools/__tests__/tasks-complete-multiple.test.js +146 -0
  43. package/dist/tools/__tests__/tasks-list-by-date.test.d.ts +2 -0
  44. package/dist/tools/__tests__/tasks-list-by-date.test.d.ts.map +1 -0
  45. package/dist/tools/__tests__/tasks-list-by-date.test.js +192 -0
  46. package/dist/tools/__tests__/tasks-list-completed.test.d.ts +2 -0
  47. package/dist/tools/__tests__/tasks-list-completed.test.d.ts.map +1 -0
  48. package/dist/tools/__tests__/tasks-list-completed.test.js +154 -0
  49. package/dist/tools/__tests__/tasks-list-for-container.test.d.ts +2 -0
  50. package/dist/tools/__tests__/tasks-list-for-container.test.d.ts.map +1 -0
  51. package/dist/tools/__tests__/tasks-list-for-container.test.js +232 -0
  52. package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts +2 -0
  53. package/dist/tools/__tests__/tasks-organize-multiple.test.d.ts.map +1 -0
  54. package/dist/tools/__tests__/tasks-organize-multiple.test.js +245 -0
  55. package/dist/tools/__tests__/tasks-search.test.d.ts +2 -0
  56. package/dist/tools/__tests__/tasks-search.test.d.ts.map +1 -0
  57. package/dist/tools/__tests__/tasks-search.test.js +106 -0
  58. package/dist/tools/__tests__/tasks-update-one.test.d.ts +2 -0
  59. package/dist/tools/__tests__/tasks-update-one.test.d.ts.map +1 -0
  60. package/dist/tools/__tests__/tasks-update-one.test.js +161 -0
  61. package/dist/tools/{tasks-delete-one.d.ts → delete-one.d.ts} +5 -3
  62. package/dist/tools/delete-one.d.ts.map +1 -0
  63. package/dist/tools/delete-one.js +25 -0
  64. package/dist/tools/{project-overview.d.ts → overview.d.ts} +5 -5
  65. package/dist/tools/overview.d.ts.map +1 -0
  66. package/dist/tools/overview.js +202 -0
  67. package/dist/tools/projects-list.d.ts +12 -1
  68. package/dist/tools/projects-list.d.ts.map +1 -1
  69. package/dist/tools/projects-list.js +15 -9
  70. package/dist/tools/{projects-add-one.d.ts → projects-manage.d.ts} +6 -4
  71. package/dist/tools/projects-manage.d.ts.map +1 -0
  72. package/dist/tools/projects-manage.js +26 -0
  73. package/dist/tools/sections-manage.d.ts +23 -0
  74. package/dist/tools/sections-manage.d.ts.map +1 -0
  75. package/dist/tools/sections-manage.js +37 -0
  76. package/dist/tools/sections-search.js +4 -7
  77. package/dist/tools/tasks-add-multiple.js +13 -16
  78. package/dist/tools/tasks-complete-multiple.js +3 -6
  79. package/dist/tools/tasks-list-by-date.d.ts.map +1 -1
  80. package/dist/tools/tasks-list-by-date.js +25 -22
  81. package/dist/tools/tasks-list-completed.d.ts +1 -1
  82. package/dist/tools/tasks-list-completed.js +13 -16
  83. package/dist/tools/{tasks-list-for-project.d.ts → tasks-list-for-container.d.ts} +7 -5
  84. package/dist/tools/tasks-list-for-container.d.ts.map +1 -0
  85. package/dist/tools/tasks-list-for-container.js +48 -0
  86. package/dist/tools/tasks-organize-multiple.d.ts.map +1 -1
  87. package/dist/tools/tasks-organize-multiple.js +20 -14
  88. package/dist/tools/tasks-search.js +7 -10
  89. package/dist/tools/tasks-update-one.d.ts +2 -2
  90. package/dist/tools/tasks-update-one.d.ts.map +1 -1
  91. package/dist/tools/tasks-update-one.js +22 -15
  92. package/dist/tools/test-helpers.d.ts +79 -0
  93. package/dist/tools/test-helpers.d.ts.map +1 -0
  94. package/dist/tools/test-helpers.js +139 -0
  95. package/package.json +20 -5
  96. package/scripts/test-executable.cjs +69 -0
  97. package/dist/tools/account-overview.d.ts +0 -9
  98. package/dist/tools/account-overview.d.ts.map +0 -1
  99. package/dist/tools/account-overview.js +0 -98
  100. package/dist/tools/project-overview.d.ts.map +0 -1
  101. package/dist/tools/project-overview.js +0 -107
  102. package/dist/tools/projects-add-one.d.ts.map +0 -1
  103. package/dist/tools/projects-add-one.js +0 -18
  104. package/dist/tools/projects-delete-one.d.ts +0 -15
  105. package/dist/tools/projects-delete-one.d.ts.map +0 -1
  106. package/dist/tools/projects-delete-one.js +0 -17
  107. package/dist/tools/projects-search.d.ts +0 -29
  108. package/dist/tools/projects-search.d.ts.map +0 -1
  109. package/dist/tools/projects-search.js +0 -42
  110. package/dist/tools/projects-update-one.d.ts +0 -15
  111. package/dist/tools/projects-update-one.d.ts.map +0 -1
  112. package/dist/tools/projects-update-one.js +0 -19
  113. package/dist/tools/sections-add-one.d.ts +0 -15
  114. package/dist/tools/sections-add-one.d.ts.map +0 -1
  115. package/dist/tools/sections-add-one.js +0 -21
  116. package/dist/tools/sections-delete-one.d.ts +0 -15
  117. package/dist/tools/sections-delete-one.d.ts.map +0 -1
  118. package/dist/tools/sections-delete-one.js +0 -17
  119. package/dist/tools/sections-update-one.d.ts +0 -15
  120. package/dist/tools/sections-update-one.d.ts.map +0 -1
  121. package/dist/tools/sections-update-one.js +0 -19
  122. package/dist/tools/shared.d.ts.map +0 -1
  123. package/dist/tools/shared.test.d.ts +0 -2
  124. package/dist/tools/shared.test.d.ts.map +0 -1
  125. package/dist/tools/subtasks-list-for-parent-task.d.ts +0 -31
  126. package/dist/tools/subtasks-list-for-parent-task.d.ts.map +0 -1
  127. package/dist/tools/subtasks-list-for-parent-task.js +0 -37
  128. package/dist/tools/tasks-delete-one.d.ts.map +0 -1
  129. package/dist/tools/tasks-delete-one.js +0 -17
  130. package/dist/tools/tasks-list-for-project.d.ts.map +0 -1
  131. package/dist/tools/tasks-list-for-project.js +0 -37
  132. package/dist/tools/tasks-list-for-section.d.ts +0 -31
  133. package/dist/tools/tasks-list-for-section.d.ts.map +0 -1
  134. package/dist/tools/tasks-list-for-section.js +0 -37
  135. package/dist/tools/tasks-list-overdue.d.ts +0 -29
  136. package/dist/tools/tasks-list-overdue.d.ts.map +0 -1
  137. package/dist/tools/tasks-list-overdue.js +0 -32
@@ -0,0 +1,140 @@
1
+ import { jest } from '@jest/globals';
2
+ import { projectsList } from '../projects-list.js';
3
+ import { TEST_ERRORS, TEST_IDS, createMockApiResponse, createMockProject } from '../test-helpers.js';
4
+ // Mock the Todoist API
5
+ const mockTodoistApi = {
6
+ getProjects: jest.fn(),
7
+ };
8
+ describe('projects-list tool', () => {
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+ describe('listing all projects', () => {
13
+ it('should list all projects when no search parameter is provided', async () => {
14
+ const mockProjects = [
15
+ createMockProject({
16
+ id: TEST_IDS.PROJECT_INBOX,
17
+ name: 'Inbox',
18
+ color: 'grey',
19
+ inboxProject: true,
20
+ childOrder: 0,
21
+ }),
22
+ createMockProject({
23
+ id: TEST_IDS.PROJECT_TEST,
24
+ name: 'test-abc123def456-project',
25
+ color: 'charcoal',
26
+ childOrder: 1,
27
+ }),
28
+ createMockProject({
29
+ id: TEST_IDS.PROJECT_WORK,
30
+ name: 'Work Project',
31
+ color: 'blue',
32
+ isFavorite: true,
33
+ isShared: true,
34
+ viewStyle: 'board',
35
+ childOrder: 2,
36
+ description: 'Important work tasks',
37
+ canAssignTasks: true,
38
+ }),
39
+ ];
40
+ mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse(mockProjects));
41
+ const result = await projectsList.execute({ limit: 50 }, mockTodoistApi);
42
+ // Verify API was called correctly
43
+ expect(mockTodoistApi.getProjects).toHaveBeenCalledWith({
44
+ limit: 50,
45
+ cursor: null,
46
+ });
47
+ // Verify result is properly mapped
48
+ expect(result).toEqual({
49
+ projects: [
50
+ expect.objectContaining({
51
+ id: TEST_IDS.PROJECT_INBOX,
52
+ name: 'Inbox',
53
+ color: 'grey',
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
+ ],
70
+ nextCursor: null,
71
+ });
72
+ });
73
+ it('should handle pagination with limit and cursor', async () => {
74
+ const mockProject = createMockProject({
75
+ id: 'project-1',
76
+ name: 'First Project',
77
+ color: 'red',
78
+ });
79
+ mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse([mockProject], 'next-page-cursor'));
80
+ const result = await projectsList.execute({ limit: 10, cursor: 'current-page-cursor' }, mockTodoistApi);
81
+ expect(mockTodoistApi.getProjects).toHaveBeenCalledWith({
82
+ limit: 10,
83
+ cursor: 'current-page-cursor',
84
+ });
85
+ expect(result.projects).toHaveLength(1);
86
+ expect(result.projects[0]?.name).toBe('First Project');
87
+ expect(result.nextCursor).toBe('next-page-cursor');
88
+ });
89
+ });
90
+ describe('searching projects', () => {
91
+ it('should filter projects by search term (case insensitive)', async () => {
92
+ const mockProjects = [
93
+ createMockProject({
94
+ id: TEST_IDS.PROJECT_WORK,
95
+ name: 'Work Project',
96
+ color: 'blue',
97
+ }),
98
+ createMockProject({
99
+ id: 'personal-project-id',
100
+ name: 'Personal Tasks',
101
+ color: 'green',
102
+ }),
103
+ createMockProject({ id: 'hobby-project-id', name: 'Hobby Work', color: 'orange' }),
104
+ ];
105
+ mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse(mockProjects));
106
+ const result = await projectsList.execute({ search: 'work', limit: 50 }, mockTodoistApi);
107
+ expect(mockTodoistApi.getProjects).toHaveBeenCalledWith({ limit: 50, cursor: null });
108
+ expect(result.projects).toHaveLength(2);
109
+ expect(result.projects.map((p) => p.name)).toEqual(['Work Project', 'Hobby Work']);
110
+ });
111
+ it.each([
112
+ {
113
+ search: 'nonexistent',
114
+ projects: ['Project One'],
115
+ expectedCount: 0,
116
+ description: 'no matches',
117
+ },
118
+ {
119
+ search: 'IMPORTANT',
120
+ projects: ['Important Project'],
121
+ expectedCount: 1,
122
+ description: 'case insensitive matching',
123
+ },
124
+ ])('should handle search with $description', async ({ search, projects, expectedCount }) => {
125
+ const mockProjects = projects.map((name) => createMockProject({ name }));
126
+ mockTodoistApi.getProjects.mockResolvedValue(createMockApiResponse(mockProjects));
127
+ const result = await projectsList.execute({ search, limit: 50 }, mockTodoistApi);
128
+ expect(result.projects).toHaveLength(expectedCount);
129
+ });
130
+ });
131
+ describe('error handling', () => {
132
+ it.each([
133
+ { error: TEST_ERRORS.API_UNAUTHORIZED, params: { limit: 50 } },
134
+ { error: TEST_ERRORS.INVALID_CURSOR, params: { cursor: 'invalid-cursor', limit: 50 } },
135
+ ])('should propagate $error', async ({ error, params }) => {
136
+ mockTodoistApi.getProjects.mockRejectedValue(new Error(error));
137
+ await expect(projectsList.execute(params, mockTodoistApi)).rejects.toThrow(error);
138
+ });
139
+ });
140
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=projects-manage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects-manage.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/projects-manage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import { jest } from '@jest/globals';
2
+ import { projectsManage } from '../projects-manage.js';
3
+ import { TEST_IDS, createMockProject } from '../test-helpers.js';
4
+ // Mock the Todoist API
5
+ const mockTodoistApi = {
6
+ addProject: jest.fn(),
7
+ updateProject: jest.fn(),
8
+ };
9
+ describe('projects-manage tool', () => {
10
+ beforeEach(() => {
11
+ jest.clearAllMocks();
12
+ });
13
+ describe('creating a new project', () => {
14
+ it('should create a project and return mapped result', async () => {
15
+ const mockApiResponse = createMockProject({
16
+ id: TEST_IDS.PROJECT_TEST,
17
+ name: 'test-abc123def456-project',
18
+ childOrder: 1,
19
+ createdAt: '2024-01-01T00:00:00Z',
20
+ });
21
+ mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
22
+ const result = await projectsManage.execute({ name: 'test-abc123def456-project' }, mockTodoistApi);
23
+ // Verify API was called correctly
24
+ expect(mockTodoistApi.addProject).toHaveBeenCalledWith({
25
+ name: 'test-abc123def456-project',
26
+ });
27
+ // Verify result is properly mapped
28
+ expect(result).toEqual({
29
+ id: TEST_IDS.PROJECT_TEST,
30
+ name: 'test-abc123def456-project',
31
+ color: 'charcoal',
32
+ isFavorite: false,
33
+ isShared: false,
34
+ parentId: null,
35
+ inboxProject: false,
36
+ viewStyle: 'list',
37
+ });
38
+ });
39
+ it('should handle different project properties from API', async () => {
40
+ const mockApiResponse = createMockProject({
41
+ id: 'project-456',
42
+ name: 'My Blue Project',
43
+ color: 'blue',
44
+ isFavorite: true,
45
+ isShared: true,
46
+ parentId: 'parent-123',
47
+ viewStyle: 'board',
48
+ childOrder: 2,
49
+ description: 'A test project',
50
+ createdAt: '2024-01-01T00:00:00Z',
51
+ });
52
+ mockTodoistApi.addProject.mockResolvedValue(mockApiResponse);
53
+ const result = await projectsManage.execute({ name: 'My Blue Project' }, mockTodoistApi);
54
+ expect(mockTodoistApi.addProject).toHaveBeenCalledWith({ name: 'My Blue Project' });
55
+ expect(result).toEqual({
56
+ id: 'project-456',
57
+ name: 'My Blue Project',
58
+ color: 'blue',
59
+ isFavorite: true,
60
+ isShared: true,
61
+ parentId: 'parent-123',
62
+ inboxProject: false,
63
+ viewStyle: 'board',
64
+ });
65
+ });
66
+ });
67
+ describe('updating an existing project', () => {
68
+ it('should update a project when id is provided', async () => {
69
+ const mockApiResponse = {
70
+ url: 'https://todoist.com/projects/existing-project-123',
71
+ id: 'existing-project-123',
72
+ parentId: null,
73
+ isDeleted: false,
74
+ updatedAt: '2025-08-13T22:10:30.000000Z',
75
+ childOrder: 1,
76
+ description: '',
77
+ isCollapsed: false,
78
+ canAssignTasks: false,
79
+ color: 'red',
80
+ isFavorite: false,
81
+ isFrozen: false,
82
+ name: 'Updated Project Name',
83
+ viewStyle: 'list',
84
+ isArchived: false,
85
+ inboxProject: false,
86
+ isShared: false,
87
+ createdAt: '2024-01-01T00:00:00Z',
88
+ defaultOrder: 0,
89
+ };
90
+ mockTodoistApi.updateProject.mockResolvedValue(mockApiResponse);
91
+ const result = await projectsManage.execute({ id: 'existing-project-123', name: 'Updated Project Name' }, mockTodoistApi);
92
+ expect(mockTodoistApi.updateProject).toHaveBeenCalledWith('existing-project-123', {
93
+ name: 'Updated Project Name',
94
+ });
95
+ // Update returns raw project (not mapped) - this is the actual behavior
96
+ expect(result).toEqual(mockApiResponse);
97
+ });
98
+ });
99
+ describe('error handling', () => {
100
+ it('should propagate API errors', async () => {
101
+ const apiError = new Error('API Error: Project name is required');
102
+ mockTodoistApi.addProject.mockRejectedValue(apiError);
103
+ await expect(projectsManage.execute({ name: '' }, mockTodoistApi)).rejects.toThrow('API Error: Project name is required');
104
+ });
105
+ });
106
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sections-manage.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections-manage.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/sections-manage.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,138 @@
1
+ import { jest } from '@jest/globals';
2
+ import { sectionsManage } from '../sections-manage.js';
3
+ import { TEST_IDS, createMockSection } from '../test-helpers.js';
4
+ // Mock the Todoist API
5
+ const mockTodoistApi = {
6
+ addSection: jest.fn(),
7
+ updateSection: jest.fn(),
8
+ };
9
+ describe('sections-manage tool', () => {
10
+ beforeEach(() => {
11
+ jest.clearAllMocks();
12
+ });
13
+ describe('creating a new section', () => {
14
+ it('should create a section and return result', async () => {
15
+ const mockApiResponse = createMockSection({
16
+ id: TEST_IDS.SECTION_1,
17
+ projectId: TEST_IDS.PROJECT_TEST,
18
+ name: 'test-abc123def456-section',
19
+ });
20
+ mockTodoistApi.addSection.mockResolvedValue(mockApiResponse);
21
+ const result = await sectionsManage.execute({
22
+ name: 'test-abc123def456-section',
23
+ projectId: TEST_IDS.PROJECT_TEST,
24
+ }, mockTodoistApi);
25
+ // Verify API was called correctly
26
+ expect(mockTodoistApi.addSection).toHaveBeenCalledWith({
27
+ name: 'test-abc123def456-section',
28
+ projectId: TEST_IDS.PROJECT_TEST,
29
+ });
30
+ // Verify result matches API response
31
+ expect(result).toEqual(mockApiResponse);
32
+ });
33
+ it('should handle different section properties from API', async () => {
34
+ const mockApiResponse = createMockSection({
35
+ id: TEST_IDS.SECTION_2,
36
+ projectId: 'project-789',
37
+ sectionOrder: 2,
38
+ name: 'My Section Name',
39
+ });
40
+ mockTodoistApi.addSection.mockResolvedValue(mockApiResponse);
41
+ const result = await sectionsManage.execute({
42
+ name: 'My Section Name',
43
+ projectId: 'project-789',
44
+ }, mockTodoistApi);
45
+ expect(mockTodoistApi.addSection).toHaveBeenCalledWith({
46
+ name: 'My Section Name',
47
+ projectId: 'project-789',
48
+ });
49
+ expect(result).toEqual(mockApiResponse);
50
+ });
51
+ it('should return error when projectId is missing for new section', async () => {
52
+ const result = await sectionsManage.execute({
53
+ name: 'test-section',
54
+ }, mockTodoistApi);
55
+ // Should not call API when projectId is missing
56
+ expect(mockTodoistApi.addSection).not.toHaveBeenCalled();
57
+ // Should return error content
58
+ expect(result).toEqual({
59
+ content: [
60
+ {
61
+ type: 'text',
62
+ text: 'Error: projectId is required when creating a new section (when id is not provided).',
63
+ },
64
+ ],
65
+ isError: true,
66
+ });
67
+ });
68
+ });
69
+ describe('updating an existing section', () => {
70
+ it('should update a section when id is provided', async () => {
71
+ const mockApiResponse = {
72
+ id: 'existing-section-123',
73
+ projectId: '6cfCcrrCFg2xP94Q',
74
+ sectionOrder: 1,
75
+ userId: 'test-user',
76
+ addedAt: '2024-01-01T00:00:00Z',
77
+ updatedAt: '2024-01-01T00:00:00Z',
78
+ archivedAt: null,
79
+ isArchived: false,
80
+ isDeleted: false,
81
+ isCollapsed: false,
82
+ name: 'Updated Section Name',
83
+ };
84
+ mockTodoistApi.updateSection.mockResolvedValue(mockApiResponse);
85
+ const result = await sectionsManage.execute({
86
+ id: 'existing-section-123',
87
+ name: 'Updated Section Name',
88
+ }, mockTodoistApi);
89
+ expect(mockTodoistApi.updateSection).toHaveBeenCalledWith('existing-section-123', {
90
+ name: 'Updated Section Name',
91
+ });
92
+ expect(result).toEqual(mockApiResponse);
93
+ });
94
+ it('should update section without requiring projectId', async () => {
95
+ const mockApiResponse = {
96
+ id: 'section-update-test',
97
+ projectId: 'original-project-id',
98
+ sectionOrder: 3,
99
+ userId: 'test-user',
100
+ addedAt: '2024-01-01T00:00:00Z',
101
+ updatedAt: '2024-01-01T00:00:00Z',
102
+ archivedAt: null,
103
+ isArchived: false,
104
+ isDeleted: false,
105
+ isCollapsed: false,
106
+ name: 'Section New Name',
107
+ };
108
+ mockTodoistApi.updateSection.mockResolvedValue(mockApiResponse);
109
+ const result = await sectionsManage.execute({
110
+ id: 'section-update-test',
111
+ name: 'Section New Name',
112
+ // Note: projectId not provided for update
113
+ }, mockTodoistApi);
114
+ expect(mockTodoistApi.updateSection).toHaveBeenCalledWith('section-update-test', {
115
+ name: 'Section New Name',
116
+ });
117
+ expect(result).toEqual(mockApiResponse);
118
+ });
119
+ });
120
+ describe('error handling', () => {
121
+ it('should propagate API errors for section creation', async () => {
122
+ const apiError = new Error('API Error: Section name is required');
123
+ mockTodoistApi.addSection.mockRejectedValue(apiError);
124
+ await expect(sectionsManage.execute({
125
+ name: '',
126
+ projectId: '6cfCcrrCFg2xP94Q',
127
+ }, mockTodoistApi)).rejects.toThrow('API Error: Section name is required');
128
+ });
129
+ it('should propagate API errors for section updates', async () => {
130
+ const apiError = new Error('API Error: Section not found');
131
+ mockTodoistApi.updateSection.mockRejectedValue(apiError);
132
+ await expect(sectionsManage.execute({
133
+ id: 'non-existent-section',
134
+ name: 'Updated Name',
135
+ }, mockTodoistApi)).rejects.toThrow('API Error: Section not found');
136
+ });
137
+ });
138
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=sections-search.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections-search.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/sections-search.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,235 @@
1
+ import { jest } from '@jest/globals';
2
+ import { sectionsSearch } from '../sections-search.js';
3
+ import { TEST_ERRORS, TEST_IDS, createMockSection } from '../test-helpers.js';
4
+ // Mock the Todoist API
5
+ const mockTodoistApi = {
6
+ getSections: jest.fn(),
7
+ };
8
+ describe('sections-search tool', () => {
9
+ beforeEach(() => {
10
+ jest.clearAllMocks();
11
+ });
12
+ describe('listing all sections in a project', () => {
13
+ it('should list all sections when no search parameter is provided', async () => {
14
+ const mockSections = [
15
+ createMockSection({
16
+ id: TEST_IDS.SECTION_1,
17
+ projectId: TEST_IDS.PROJECT_TEST,
18
+ name: 'To Do',
19
+ }),
20
+ createMockSection({
21
+ id: TEST_IDS.SECTION_2,
22
+ projectId: TEST_IDS.PROJECT_TEST,
23
+ sectionOrder: 2,
24
+ name: 'In Progress',
25
+ }),
26
+ createMockSection({
27
+ id: 'section-789',
28
+ projectId: TEST_IDS.PROJECT_TEST,
29
+ sectionOrder: 3,
30
+ name: 'Done',
31
+ }),
32
+ createMockSection({
33
+ id: 'section-999',
34
+ projectId: TEST_IDS.PROJECT_TEST,
35
+ sectionOrder: 4,
36
+ name: 'Backlog Items',
37
+ }),
38
+ ];
39
+ mockTodoistApi.getSections.mockResolvedValue({
40
+ results: mockSections,
41
+ nextCursor: null,
42
+ });
43
+ const result = await sectionsSearch.execute({ projectId: TEST_IDS.PROJECT_TEST }, mockTodoistApi);
44
+ // Verify API was called correctly
45
+ expect(mockTodoistApi.getSections).toHaveBeenCalledWith({
46
+ projectId: TEST_IDS.PROJECT_TEST,
47
+ });
48
+ // Verify result is properly mapped (simplified format)
49
+ expect(result).toEqual([
50
+ { id: TEST_IDS.SECTION_1, name: 'To Do' },
51
+ { id: TEST_IDS.SECTION_2, name: 'In Progress' },
52
+ { id: 'section-789', name: 'Done' },
53
+ { id: 'section-999', name: 'Backlog Items' },
54
+ ]);
55
+ });
56
+ it('should handle project with no sections', async () => {
57
+ mockTodoistApi.getSections.mockResolvedValue({
58
+ results: [],
59
+ nextCursor: null,
60
+ });
61
+ const result = await sectionsSearch.execute({ projectId: 'empty-project-id' }, mockTodoistApi);
62
+ expect(mockTodoistApi.getSections).toHaveBeenCalledWith({
63
+ projectId: 'empty-project-id',
64
+ });
65
+ expect(result).toEqual([]);
66
+ });
67
+ });
68
+ describe('searching sections by name', () => {
69
+ it('should filter sections by search term (case insensitive)', async () => {
70
+ const mockSections = [
71
+ createMockSection({
72
+ id: TEST_IDS.SECTION_1,
73
+ projectId: TEST_IDS.PROJECT_TEST,
74
+ name: 'To Do',
75
+ }),
76
+ createMockSection({
77
+ id: TEST_IDS.SECTION_2,
78
+ projectId: TEST_IDS.PROJECT_TEST,
79
+ sectionOrder: 2,
80
+ name: 'In Progress',
81
+ }),
82
+ createMockSection({
83
+ id: 'section-789',
84
+ projectId: TEST_IDS.PROJECT_TEST,
85
+ sectionOrder: 3,
86
+ name: 'Done',
87
+ }),
88
+ createMockSection({
89
+ id: 'section-999',
90
+ projectId: TEST_IDS.PROJECT_TEST,
91
+ sectionOrder: 4,
92
+ name: 'Progress Review',
93
+ }),
94
+ ];
95
+ mockTodoistApi.getSections.mockResolvedValue({
96
+ results: mockSections,
97
+ nextCursor: null,
98
+ });
99
+ const result = await sectionsSearch.execute({
100
+ projectId: TEST_IDS.PROJECT_TEST,
101
+ search: 'progress',
102
+ }, mockTodoistApi);
103
+ expect(mockTodoistApi.getSections).toHaveBeenCalledWith({
104
+ projectId: TEST_IDS.PROJECT_TEST,
105
+ });
106
+ // Should return both "In Progress" and "Progress Review" (case insensitive partial match)
107
+ expect(result).toEqual([
108
+ { id: TEST_IDS.SECTION_2, name: 'In Progress' },
109
+ { id: 'section-999', name: 'Progress Review' },
110
+ ]);
111
+ });
112
+ it('should handle search with no matches', async () => {
113
+ const mockSections = [
114
+ createMockSection({
115
+ id: TEST_IDS.SECTION_1,
116
+ projectId: TEST_IDS.PROJECT_TEST,
117
+ name: 'To Do',
118
+ }),
119
+ createMockSection({
120
+ id: TEST_IDS.SECTION_2,
121
+ projectId: TEST_IDS.PROJECT_TEST,
122
+ sectionOrder: 2,
123
+ name: 'In Progress',
124
+ }),
125
+ ];
126
+ mockTodoistApi.getSections.mockResolvedValue({
127
+ results: mockSections,
128
+ nextCursor: null,
129
+ });
130
+ const result = await sectionsSearch.execute({
131
+ projectId: TEST_IDS.PROJECT_TEST,
132
+ search: 'nonexistent',
133
+ }, mockTodoistApi);
134
+ expect(result).toEqual([]);
135
+ });
136
+ it('should handle case sensitive search correctly', async () => {
137
+ const mockSections = [
138
+ createMockSection({
139
+ id: TEST_IDS.SECTION_1,
140
+ projectId: TEST_IDS.PROJECT_TEST,
141
+ name: 'Important Tasks',
142
+ }),
143
+ createMockSection({
144
+ id: TEST_IDS.SECTION_2,
145
+ projectId: TEST_IDS.PROJECT_TEST,
146
+ sectionOrder: 2,
147
+ name: 'Regular Work',
148
+ }),
149
+ ];
150
+ mockTodoistApi.getSections.mockResolvedValue({
151
+ results: mockSections,
152
+ nextCursor: null,
153
+ });
154
+ const result = await sectionsSearch.execute({
155
+ projectId: TEST_IDS.PROJECT_TEST,
156
+ search: 'IMPORTANT',
157
+ }, mockTodoistApi);
158
+ // Should match despite different case
159
+ expect(result).toHaveLength(1);
160
+ expect(result[0]).toEqual({ id: TEST_IDS.SECTION_1, name: 'Important Tasks' });
161
+ });
162
+ it('should handle partial matches correctly', async () => {
163
+ const mockSections = [
164
+ createMockSection({
165
+ id: TEST_IDS.SECTION_1,
166
+ projectId: TEST_IDS.PROJECT_TEST,
167
+ name: 'Development Tasks',
168
+ }),
169
+ createMockSection({
170
+ id: TEST_IDS.SECTION_2,
171
+ projectId: TEST_IDS.PROJECT_TEST,
172
+ sectionOrder: 2,
173
+ name: 'Testing Tasks',
174
+ }),
175
+ createMockSection({
176
+ id: 'section-789',
177
+ projectId: TEST_IDS.PROJECT_TEST,
178
+ sectionOrder: 3,
179
+ name: 'Deployment',
180
+ }),
181
+ ];
182
+ mockTodoistApi.getSections.mockResolvedValue({
183
+ results: mockSections,
184
+ nextCursor: null,
185
+ });
186
+ const result = await sectionsSearch.execute({
187
+ projectId: TEST_IDS.PROJECT_TEST,
188
+ search: 'task',
189
+ }, mockTodoistApi);
190
+ // Should match both sections with "task" in the name
191
+ expect(result).toEqual([
192
+ { id: TEST_IDS.SECTION_1, name: 'Development Tasks' },
193
+ { id: TEST_IDS.SECTION_2, name: 'Testing Tasks' },
194
+ ]);
195
+ });
196
+ it('should handle exact matches', async () => {
197
+ const mockSections = [
198
+ createMockSection({
199
+ id: TEST_IDS.SECTION_1,
200
+ projectId: TEST_IDS.PROJECT_TEST,
201
+ name: 'Done',
202
+ }),
203
+ createMockSection({
204
+ id: TEST_IDS.SECTION_2,
205
+ projectId: TEST_IDS.PROJECT_TEST,
206
+ sectionOrder: 2,
207
+ name: 'Done Soon',
208
+ }),
209
+ ];
210
+ mockTodoistApi.getSections.mockResolvedValue({
211
+ results: mockSections,
212
+ nextCursor: null,
213
+ });
214
+ const result = await sectionsSearch.execute({
215
+ projectId: TEST_IDS.PROJECT_TEST,
216
+ search: 'done',
217
+ }, mockTodoistApi);
218
+ // Should match both sections containing "done"
219
+ expect(result).toEqual([
220
+ { id: TEST_IDS.SECTION_1, name: 'Done' },
221
+ { id: TEST_IDS.SECTION_2, name: 'Done Soon' },
222
+ ]);
223
+ });
224
+ });
225
+ describe('error handling', () => {
226
+ it.each([
227
+ { error: 'API Error: Project not found', projectId: 'non-existent-project' },
228
+ { error: TEST_ERRORS.API_UNAUTHORIZED, projectId: 'restricted-project' },
229
+ { error: 'API Error: Invalid project ID format', projectId: 'invalid-id-format' },
230
+ ])('should propagate $error', async ({ error, projectId }) => {
231
+ mockTodoistApi.getSections.mockRejectedValue(new Error(error));
232
+ await expect(sectionsSearch.execute({ projectId }, mockTodoistApi)).rejects.toThrow(error);
233
+ });
234
+ });
235
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tasks-add-multiple.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks-add-multiple.test.d.ts","sourceRoot":"","sources":["../../../src/tools/__tests__/tasks-add-multiple.test.ts"],"names":[],"mappings":""}