/**
* Tests for create_note tool
*
* Tests note creation with required/optional fields, validation, and error handling.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { handleCreateNote } from '../../../src/tools/create-note.js';
import { createMockAttioClient } from '../../helpers/mock-attio-client.js';
import * as attioClientModule from '../../../src/attio-client.js';
vi.mock('../../../src/attio-client.js', () => ({
createAttioClient: vi.fn(),
}));
describe('create_note', () => {
let mockClient: ReturnType<typeof createMockAttioClient>;
let originalApiKey: string | undefined;
beforeEach(() => {
originalApiKey = process.env.ATTIO_API_KEY;
process.env.ATTIO_API_KEY = 'test-api-key';
mockClient = createMockAttioClient();
vi.mocked(attioClientModule.createAttioClient).mockReturnValue(
mockClient as any
);
});
afterEach(() => {
if (originalApiKey) {
process.env.ATTIO_API_KEY = originalApiKey;
} else {
delete process.env.ATTIO_API_KEY;
}
mockClient.reset();
});
describe('Successful creation', () => {
it('should create note with plaintext format (default)', async () => {
const mockNoteResponse = {
data: {
id: {
workspace_id: 'ws-123',
note_id: 'note-123',
},
parent_object: 'companies',
parent_record_id: 'rec-company-123',
title: 'Meeting Notes',
content_plaintext: 'Discussed Q1 goals.',
created_by_actor: {
type: 'api-token',
id: 'token-123',
},
created_at: '2024-11-15T10:00:00Z',
},
};
const mockCompanyResponse = {
data: {
id: { record_id: 'rec-company-123' },
values: {
name: [{ value: 'Acme Corp' }],
},
},
};
mockClient.mockResponse('POST', '/notes', { data: mockNoteResponse });
mockClient.mockResponse(
'GET',
'/objects/companies/records/rec-company-123',
{ data: mockCompanyResponse }
);
const result = await handleCreateNote({
parent_object: 'companies',
parent_record_id: 'rec-company-123',
title: 'Meeting Notes',
content: 'Discussed Q1 goals.',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.note_id).toBe('note-123');
expect(parsed.data.title).toBe('Meeting Notes');
expect(parsed.data.content).toBe('Discussed Q1 goals.');
expect(parsed.data.format).toBe('plaintext');
expect(parsed.data.parent_object).toBe('companies');
expect(parsed.data.parent_record_id).toBe('rec-company-123');
expect(parsed.data.parent_record_name).toBe('Acme Corp');
});
it('should create note with markdown format', async () => {
const mockNoteResponse = {
data: {
id: {
workspace_id: 'ws-123',
note_id: 'note-456',
},
parent_object: 'people',
parent_record_id: 'rec-person-123',
title: 'Project Update',
content_plaintext: '# Header\n\n**Bold text**',
created_by_actor: {
type: 'api-token',
id: 'token-123',
},
created_at: '2024-11-15T10:00:00Z',
},
};
const mockPersonResponse = {
data: {
id: { record_id: 'rec-person-123' },
values: {
name: [{ first_name: 'John', last_name: 'Doe', full_name: 'John Doe' }],
},
},
};
mockClient.mockResponse('POST', '/notes', { data: mockNoteResponse });
mockClient.mockResponse(
'GET',
'/objects/people/records/rec-person-123',
{ data: mockPersonResponse }
);
const result = await handleCreateNote({
parent_object: 'people',
parent_record_id: 'rec-person-123',
title: 'Project Update',
content: '# Header\n\n**Bold text**',
format: 'markdown',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.format).toBe('markdown');
});
it('should create note on custom object (name not fetched)', async () => {
const mockNoteResponse = {
data: {
id: {
workspace_id: 'ws-123',
note_id: 'note-789',
},
parent_object: 'custom_objects',
parent_record_id: 'rec-opp-123',
title: 'Due Diligence Notes',
content_plaintext: 'Reviewed financials.',
created_by_actor: {
type: 'api-token',
id: 'token-123',
},
created_at: '2024-11-15T10:00:00Z',
},
};
mockClient.mockResponse('POST', '/notes', { data: mockNoteResponse });
const result = await handleCreateNote({
parent_object: 'custom_objects',
parent_record_id: 'rec-opp-123',
title: 'Due Diligence Notes',
content: 'Reviewed financials.',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.parent_object).toBe('custom_objects');
// Custom objects don't have name fetching - only people and companies do
expect(parsed.data.parent_record_name).toBeNull();
});
});
describe('Validation errors', () => {
it('should fail when parent_object is missing', async () => {
const result = await handleCreateNote({
parent_object: '',
parent_record_id: 'rec-123',
title: 'Test',
content: 'Content',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('parent_object');
});
it('should fail when parent_record_id is missing', async () => {
const result = await handleCreateNote({
parent_object: 'companies',
parent_record_id: '',
title: 'Test',
content: 'Content',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('parent_record_id');
});
it('should fail when title is missing', async () => {
const result = await handleCreateNote({
parent_object: 'companies',
parent_record_id: 'rec-123',
title: '',
content: 'Content',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('title');
});
it('should fail when content is missing', async () => {
const result = await handleCreateNote({
parent_object: 'companies',
parent_record_id: 'rec-123',
title: 'Test',
content: '',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('content');
});
it('should fail when format is invalid', async () => {
const result = await handleCreateNote({
parent_object: 'companies',
parent_record_id: 'rec-123',
title: 'Test',
content: 'Content',
format: 'invalid',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('Invalid format');
});
});
describe('Configuration errors', () => {
it('should fail when ATTIO_API_KEY is not configured', async () => {
delete process.env.ATTIO_API_KEY;
const result = await handleCreateNote({
parent_object: 'companies',
parent_record_id: 'rec-123',
title: 'Test',
content: 'Content',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('ATTIO_API_KEY');
});
});
describe('API error handling', () => {
it('should handle API errors gracefully', async () => {
mockClient.mockResponse('POST', '/notes', {
error: {
statusCode: 400,
message: 'Invalid parent_object',
},
});
const result = await handleCreateNote({
parent_object: 'invalid_object',
parent_record_id: 'rec-123',
title: 'Test',
content: 'Content',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error).toBeDefined();
});
});
});