/**
* Tests for get_note tool
*
* Tests note retrieval, validation, and error handling.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { handleGetNote } from '../../../src/tools/get-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('get_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 retrieval', () => {
it('should get note attached to company', 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('GET', '/notes/note-123', {
data: mockNoteResponse,
});
mockClient.mockResponse(
'GET',
'/objects/companies/records/rec-company-123',
{ data: mockCompanyResponse }
);
const result = await handleGetNote({ note_id: 'note-123' });
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.parent_object).toBe('companies');
expect(parsed.data.parent_record_id).toBe('rec-company-123');
expect(parsed.data.parent_record_name).toBe('Acme Corp');
expect(parsed.data.created_by.type).toBe('api-token');
expect(parsed.data.created_at).toBe('2024-11-15T10:00:00Z');
});
it('should get note attached to person', async () => {
const mockNoteResponse = {
data: {
id: {
workspace_id: 'ws-123',
note_id: 'note-456',
},
parent_object: 'people',
parent_record_id: 'rec-person-123',
title: 'Call Summary',
content_plaintext: 'Spoke about partnership.',
created_by_actor: {
type: 'workspace-member',
id: 'member-123',
},
created_at: '2024-11-15T11: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('GET', '/notes/note-456', {
data: mockNoteResponse,
});
mockClient.mockResponse(
'GET',
'/objects/people/records/rec-person-123',
{ data: mockPersonResponse }
);
const result = await handleGetNote({ note_id: 'note-456' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.parent_object).toBe('people');
expect(parsed.data.parent_record_name).toBe('John Doe');
});
it('should get note attached to investment opportunity', async () => {
const mockNoteResponse = {
data: {
id: {
workspace_id: 'ws-123',
note_id: 'note-789',
},
parent_object: 'investment_opportunities',
parent_record_id: 'rec-opp-123',
title: 'Due Diligence',
content_plaintext: 'Reviewed financials.',
created_by_actor: {
type: 'api-token',
id: 'token-123',
},
created_at: '2024-11-15T12:00:00Z',
},
};
const mockOppResponse = {
data: {
id: { entry_id: 'rec-opp-123' },
entry_values: {
display_name: [{ value: 'Series A - TechCo' }],
},
},
};
mockClient.mockResponse('GET', '/notes/note-789', {
data: mockNoteResponse,
});
mockClient.mockResponse(
'GET',
'/lists/investment_opportunities/entries/rec-opp-123',
{ data: mockOppResponse }
);
const result = await handleGetNote({ note_id: 'note-789' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.parent_object).toBe('investment_opportunities');
expect(parsed.data.parent_record_name).toBe('Series A - TechCo');
});
it('should handle note with whitespace in note_id', async () => {
const mockNoteResponse = {
data: {
id: {
workspace_id: 'ws-123',
note_id: 'note-123',
},
parent_object: 'companies',
parent_record_id: 'rec-company-123',
title: 'Test',
content_plaintext: 'Content',
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: 'Test Co' }],
},
},
};
mockClient.mockResponse('GET', '/notes/note-123', {
data: mockNoteResponse,
});
mockClient.mockResponse(
'GET',
'/objects/companies/records/rec-company-123',
{ data: mockCompanyResponse }
);
const result = await handleGetNote({ note_id: ' note-123 ' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.note_id).toBe('note-123');
});
});
describe('Validation errors', () => {
it('should fail when note_id is missing', async () => {
const result = await handleGetNote({ note_id: '' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('note_id');
});
it('should fail when note_id is only whitespace', async () => {
const result = await handleGetNote({ note_id: ' ' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain('note_id');
});
});
describe('Configuration errors', () => {
it('should fail when ATTIO_API_KEY is not configured', async () => {
delete process.env.ATTIO_API_KEY;
const result = await handleGetNote({ note_id: 'note-123' });
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 note not found', async () => {
mockClient.mockResponse('GET', '/notes/nonexistent', {
error: {
statusCode: 404,
message: 'Note not found',
},
});
const result = await handleGetNote({ note_id: 'nonexistent' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error).toBeDefined();
});
it('should handle API errors gracefully', async () => {
mockClient.mockResponse('GET', '/notes/note-123', {
error: {
statusCode: 500,
message: 'Internal server error',
},
});
const result = await handleGetNote({ note_id: 'note-123' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error).toBeDefined();
});
});
});