Skip to main content
Glama
tasks.create.validation.test.ts7.09 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { createTask } from '../../../src/api/operations/tasks.js'; // Mock the Attio client const mockPost = vi.fn(); const mockClient = { post: mockPost, defaults: { baseURL: 'https://api.attio.com', headers: { Authorization: 'Bearer test-token' }, }, }; vi.mock('../../../src/api/attio-client.js', () => ({ getAttioClient: () => mockClient, createAttioClient: () => mockClient, })); // Mock lazy client vi.mock('../../../src/api/lazy-client.js', () => ({ getLazyAttioClient: () => mockClient, })); // Mock client resolver vi.mock('../../../src/utils/client-resolver.js', () => ({ getValidatedAttioClient: () => mockClient, })); // Mock retry utility to avoid delays in tests vi.mock('../../../src/api/operations/retry.js', () => ({ callWithRetry: vi.fn((fn) => fn()), })); describe('tasks.createTask validation', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('Parameter validation', () => { it('throws when recordId provided without targetObject', async () => { await expect( createTask('content', { recordId: '11111111-1111-1111-1111-111111111111', }) ).rejects.toThrow(/both 'recordId' and 'targetObject'/i); }); it('throws when targetObject provided without recordId', async () => { await expect( createTask('content', { targetObject: 'companies' as any }) ).rejects.toThrow(/both 'recordId' and 'targetObject'/i); }); it('accepts neither recordId nor targetObject (unlinked task)', async () => { mockPost.mockResolvedValue({ data: { data: { id: 'task-123', content_plaintext: 'test content', assignee_id: null, status: 'pending', }, }, }); const result = await createTask('test content'); expect(result.id).toBe('task-123'); expect(result.content).toBe('test content'); expect(mockPost).toHaveBeenCalledWith( '/tasks', expect.objectContaining({ data: expect.objectContaining({ content: 'test content', format: 'plaintext', linked_records: [], }), }) ); }); it('accepts both recordId and targetObject (linked task)', async () => { mockPost.mockResolvedValue({ data: { data: { id: 'task-456', content_plaintext: 'linked task', assignee_id: null, status: 'pending', }, }, }); const result = await createTask('linked task', { recordId: '11111111-1111-1111-1111-111111111111', targetObject: 'companies', }); expect(result.id).toBe('task-456'); expect(mockPost).toHaveBeenCalledWith( '/tasks', expect.objectContaining({ data: expect.objectContaining({ content: 'linked task', format: 'plaintext', linked_records: [ { target_object: 'companies', target_record_id: '11111111-1111-1111-1111-111111111111', }, ], }), }) ); }); }); describe('Response handling', () => { it('handles nested data response structure', async () => { mockPost.mockResolvedValue({ data: { data: { id: 'task-nested', content_plaintext: 'nested response', status: 'pending', }, }, }); const result = await createTask('test'); expect(result.id).toBe('task-nested'); expect(result.content).toBe('nested response'); }); it('handles direct data response structure', async () => { mockPost.mockResolvedValue({ data: { id: 'task-direct', content_plaintext: 'direct response', status: 'pending', }, }); const result = await createTask('test'); expect(result.id).toBe('task-direct'); expect(result.content).toBe('direct response'); }); it('transforms is_completed to status for backward compatibility', async () => { mockPost.mockResolvedValue({ data: { data: { id: 'task-completed', content_plaintext: 'completed task', is_completed: true, }, }, }); const result = await createTask('test'); expect(result.status).toBe('completed'); }); it('throws error for invalid response structure', async () => { mockPost.mockResolvedValue({ data: null, }); await expect(createTask('test')).rejects.toThrow( 'Invalid API response structure' ); }); it('throws error for null response', async () => { mockPost.mockResolvedValue(null); await expect(createTask('test')).rejects.toThrow( 'Invalid API response: no response data received' ); }); }); describe('Edge cases', () => { it('handles empty content', async () => { mockPost.mockResolvedValue({ data: { data: { id: 'task-empty', content_plaintext: '', status: 'pending', }, }, }); const result = await createTask(''); expect(result.id).toBe('task-empty'); expect(result.content).toBe(''); }); it('includes deadline_at when dueDate provided', async () => { mockPost.mockResolvedValue({ data: { data: { id: 'task-deadline', content_plaintext: 'task with deadline', deadline_at: '2024-12-31T23:59:59Z', }, }, }); const deadline = '2024-12-31T23:59:59Z'; await createTask('test', { dueDate: deadline }); expect(mockPost).toHaveBeenCalledWith( '/tasks', expect.objectContaining({ data: expect.objectContaining({ content: 'test', format: 'plaintext', deadline_at: deadline, }), }) ); }); it('includes assignees when assigneeId provided', async () => { mockPost.mockResolvedValue({ data: { data: { id: 'task-assigned', content_plaintext: 'assigned task', assignee_id: 'user-123', }, }, }); await createTask('test', { assigneeId: 'user-123' }); expect(mockPost).toHaveBeenCalledWith( '/tasks', expect.objectContaining({ data: expect.objectContaining({ content: 'test', format: 'plaintext', assignees: [ { referenced_actor_type: 'workspace-member', referenced_actor_id: 'user-123', }, ], }), }) ); }); it('handles network errors gracefully', async () => { mockPost.mockRejectedValue(new Error('Network error')); await expect(createTask('test')).rejects.toThrow('Network error'); }); }); });

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/kesslerio/attio-mcp-server'

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