Skip to main content
Glama
todoist_tasks.test.ts12.5 kB
import { describe, test, expect, beforeEach, jest } from '@jest/globals'; import { TodoistTasksTool } from '../../src/tools/todoist-tasks.js'; import { CacheService } from '../../src/services/cache.js'; import { TodoistApiService } from '../../src/services/todoist-api.js'; import { BatchOperationsService, BatchOperationRequest, } from '../../src/services/batch.js'; import { BatchOperationResult } from '../../src/types/errors.js'; import { createTasksApiMock, TasksApiMock, } from '../helpers/mockTodoistApiService.js'; import { TokenValidatorSingleton } from '../../src/services/token-validator.js'; import { resetConfig } from '../../src/config/index.js'; type BatchServiceMock = jest.Mocked< Pick<BatchOperationsService, 'executeBatch'> >; const mockApiConfig = { token: 'test_token_123456', base_url: 'https://api.todoist.com/rest/v1', timeout: 10000, retry_attempts: 3, }; function createMockBatchService(): BatchServiceMock { const result: BatchOperationResult = { success: true, completed_commands: 2, failed_commands: 0, errors: [], temp_id_mapping: { temp_1: 'generated-id-1', temp_2: 'generated-id-2', }, }; const executeBatch = jest.fn( async (_request: BatchOperationRequest): Promise<BatchOperationResult> => result ); return { executeBatch, } as unknown as BatchServiceMock; } describe('todoist_tasks MCP Tool Contract', () => { let apiService: TasksApiMock; let batchService: BatchServiceMock; let todoistTasksTool: TodoistTasksTool; beforeEach(() => { apiService = createTasksApiMock(); batchService = createMockBatchService(); (TokenValidatorSingleton as any).resetForTesting(); (TokenValidatorSingleton as any).setMockApiService({ validateToken: jest.fn(async () => undefined), } as unknown as TodoistApiService); process.env.TODOIST_API_TOKEN = mockApiConfig.token; resetConfig(); todoistTasksTool = new TodoistTasksTool(mockApiConfig, { apiService: apiService as unknown as TodoistApiService, batchService: batchService as unknown as BatchOperationsService, cacheService: new CacheService(), }); }); describe('Tool Registration', () => { test('should provide MCP tool metadata', () => { const definition = TodoistTasksTool.getToolDefinition(); expect(definition.name).toBe('todoist_tasks'); expect(definition.description).toContain('task management'); expect(definition.inputSchema).toBeDefined(); }); }); describe('Parameter Validation', () => { test('should reject missing action parameter', async () => { const result = await todoistTasksTool.execute({} as any); expect(result.success).toBe(false); expect(result.error?.code).toBeDefined(); }); test('should reject invalid action value', async () => { const result = await todoistTasksTool.execute({ action: 'invalid' }); expect(result.success).toBe(false); expect(result.error?.code).toBeDefined(); }); }); describe('CREATE action', () => { test('creates a task with minimal parameters', async () => { const params = { action: 'create' as const, content: 'Test task', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.message).toContain('Task created'); expect(apiService.createTask).toHaveBeenCalledWith( expect.objectContaining({ content: 'Test task', project_id: '220474322', }) ); }); test('validates required fields', async () => { const result = await todoistTasksTool.execute({ action: 'create', } as any); expect(result.success).toBe(false); expect(result.error?.code).toBe('VALIDATION_ERROR'); }); }); describe('GET action', () => { test('retrieves a task by id', async () => { const result = await todoistTasksTool.execute({ action: 'get', task_id: '2995104339', }); expect(apiService.getTask).toHaveBeenCalledWith('2995104339'); expect(result.success).toBe(true); expect(result.data).toBeDefined(); }); }); describe('UPDATE action', () => { test('updates task properties', async () => { const result = await todoistTasksTool.execute({ action: 'update', task_id: '2995104339', content: 'Updated task content', }); expect(result.success).toBe(true); expect(apiService.updateTask).toHaveBeenCalledWith( '2995104339', expect.objectContaining({ content: 'Updated task content' }) ); }); }); describe('DELETE action', () => { test('deletes a task', async () => { const result = await todoistTasksTool.execute({ action: 'delete', task_id: '2995104339', }); expect(result.success).toBe(true); expect(result.message).toContain('Task deleted'); expect(apiService.deleteTask).toHaveBeenCalledWith('2995104339'); }); }); describe('LIST action', () => { test('lists tasks by project', async () => { const result = await todoistTasksTool.execute({ action: 'list', project_id: '220474322', }); expect(result.success).toBe(true); expect(Array.isArray(result.data)).toBe(true); expect(apiService.getTasks).toHaveBeenCalledWith( expect.objectContaining({ project_id: '220474322' }) ); }); }); describe('COMPLETE / UNCOMPLETE actions', () => { test('completes a task', async () => { const result = await todoistTasksTool.execute({ action: 'complete', task_id: '2995104339', }); expect(result.success).toBe(true); expect(apiService.completeTask).toHaveBeenCalledWith('2995104339'); }); test('reopens a task', async () => { const result = await todoistTasksTool.execute({ action: 'uncomplete', task_id: '2995104339', }); expect(result.success).toBe(true); expect(apiService.reopenTask).toHaveBeenCalledWith('2995104339'); }); }); describe('BATCH action', () => { test('executes batch operations', async () => { const params = { action: 'batch' as const, batch_commands: [ { type: 'item_add', temp_id: 'temp_1', args: { content: 'Batch task 1', project_id: '220474322', }, }, { type: 'item_add', temp_id: 'temp_2', args: { content: 'Batch task 2', project_id: '220474322', }, }, ], }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(true); expect(result.metadata?.completed_commands).toBe(2); expect(batchService.executeBatch).toHaveBeenCalledWith( expect.objectContaining({ commands: expect.arrayContaining([ expect.objectContaining({ type: 'item_add', temp_id: 'temp_1' }), expect.objectContaining({ type: 'item_add', temp_id: 'temp_2' }), ]), }) ); }); }); describe('DEADLINE support', () => { describe('CREATE with deadline', () => { test('creates task with valid deadline', async () => { const params = { action: 'create' as const, content: 'Submit quarterly report', deadline: '2025-12-31', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(apiService.createTask).toHaveBeenCalledWith( expect.objectContaining({ content: 'Submit quarterly report', deadline: '2025-12-31', }) ); }); test('creates task with both due_date and deadline', async () => { const params = { action: 'create' as const, content: 'Review PR', due_date: '2025-10-10', deadline: '2025-10-15', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(true); expect(apiService.createTask).toHaveBeenCalledWith( expect.objectContaining({ content: 'Review PR', due_date: '2025-10-10', deadline: '2025-10-15', }) ); }); }); describe('UPDATE with deadline', () => { test('adds deadline to existing task', async () => { const params = { action: 'update' as const, task_id: '2995104339', deadline: '2025-11-30', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(true); expect(apiService.updateTask).toHaveBeenCalledWith( '2995104339', expect.objectContaining({ deadline: '2025-11-30' }) ); }); test('removes deadline with null value', async () => { const params = { action: 'update' as const, task_id: '2995104339', deadline: null, }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(true); expect(apiService.updateTask).toHaveBeenCalledWith( '2995104339', expect.objectContaining({ deadline: null }) ); }); }); describe('GET with deadline', () => { test('retrieves task with deadline field', async () => { const result = await todoistTasksTool.execute({ action: 'get', task_id: '2995104339', }); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(apiService.getTask).toHaveBeenCalledWith('2995104339'); }); }); describe('VALIDATION errors', () => { test('rejects invalid deadline format - US format', async () => { const params = { action: 'create' as const, content: 'Test task', deadline: '10/15/2025', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(false); expect(result.error?.code).toBe('VALIDATION_ERROR'); expect(result.error?.message).toContain('YYYY-MM-DD'); }); test('rejects invalid deadline format - no separators', async () => { const params = { action: 'create' as const, content: 'Test task', deadline: '20251015', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(false); expect(result.error?.code).toBe('VALIDATION_ERROR'); expect(result.error?.message).toContain('YYYY-MM-DD'); }); test('rejects invalid deadline format - wrong separators', async () => { const params = { action: 'create' as const, content: 'Test task', deadline: '2025/10/15', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(false); expect(result.error?.code).toBe('VALIDATION_ERROR'); expect(result.error?.message).toContain('YYYY-MM-DD'); }); test('rejects invalid deadline format - partial date', async () => { const params = { action: 'create' as const, content: 'Test task', deadline: '2025-10', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(false); expect(result.error?.code).toBe('VALIDATION_ERROR'); expect(result.error?.message).toContain('YYYY-MM-DD'); }); test('rejects natural language deadline', async () => { const params = { action: 'create' as const, content: 'Test task', deadline: 'tomorrow', project_id: '220474322', }; const result = await todoistTasksTool.execute(params); expect(result.success).toBe(false); expect(result.error?.code).toBe('VALIDATION_ERROR'); expect(result.error?.message).toContain('YYYY-MM-DD'); }); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/shayonpal/mcp-todoist'

If you have feedback or need assistance with the MCP directory API, please join our Discord server