Skip to main content
Glama
by RadonX
get-note.test.js15.4 kB
import { jest } from '@jest/globals'; import { getNote } from '../src/tools/get-note.js'; import { ValidationError } from '../src/utils/validation.js'; import { TriliumAPIError } from '../src/utils/trilium-client.js'; // Mock the logger to avoid console output during tests jest.mock('../src/utils/logger.js', () => ({ logger: { debug: jest.fn(), info: jest.fn(), error: jest.fn(), warn: jest.fn() } })); describe('getNote', () => { let mockTriliumClient; beforeEach(() => { // Create a fresh mock for each test mockTriliumClient = { get: jest.fn() }; jest.clearAllMocks(); }); describe('successful note retrieval', () => { test('should return note with text content', async () => { const mockNote = { noteId: 'note123', title: 'Test Note', type: 'text', mime: 'text/html', isProtected: false, isDeleted: false, dateCreated: '2024-01-01T10:00:00.000Z', dateModified: '2024-01-15T14:30:00.000Z', utcDateCreated: '2024-01-01T10:00:00.000Z', utcDateModified: '2024-01-15T14:30:00.000Z', parentNoteIds: ['parent123'], childNoteIds: ['child456'], attributes: [] }; const mockContent = 'This is the note content'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(mockTriliumClient.get).toHaveBeenCalledTimes(2); expect(mockTriliumClient.get).toHaveBeenNthCalledWith(1, 'notes/note123'); expect(mockTriliumClient.get).toHaveBeenNthCalledWith(2, 'notes/note123/content'); expect(result.content).toHaveLength(2); expect(result.content[0].type).toBe('text'); expect(result.content[0].text).toBe('Note: "Test Note" (text, 24 chars)'); expect(result.content[1].type).toBe('application/json'); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.operation).toBe('get_note'); expect(parsedData.note.noteId).toBe('note123'); expect(parsedData.note.title).toBe('Test Note'); expect(parsedData.note.content.data).toBe('This is the note content'); expect(parsedData.note.content.length).toBe(24); expect(parsedData.note.triliumUrl).toBe('trilium://note/note123'); }); test('should handle note with no title', async () => { const mockNote = { noteId: 'note123', type: 'text' }; const mockContent = 'Content'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.content[0].text).toBe('Note: "Untitled" (text, 7 chars)'); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.title).toBe('Untitled'); }); test('should handle large content with truncation', async () => { const mockNote = { noteId: 'note123', title: 'Large Note', type: 'text' }; const largeContent = 'A'.repeat(15000); // Exceeds 10000 char limit mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(largeContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.content.type).toBe('text'); expect(parsedData.note.content.length).toBe(15000); expect(parsedData.note.content.truncated).toBe(true); expect(parsedData.note.content.fullLength).toBe(15000); expect(parsedData.note.content.preview).toBe('A'.repeat(1000) + '...'); expect(parsedData.note.content.data).toBeUndefined(); }); test('should handle binary content', async () => { const mockNote = { noteId: 'note123', title: 'Image Note', type: 'image', mime: 'image/png' }; const binaryContent = new ArrayBuffer(1024); mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(binaryContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.content[0].text).toBe('Note: "Image Note" (image, binary)'); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.content.type).toBe('binary'); expect(parsedData.note.content.length).toBe(0); expect(parsedData.note.content.data).toBeUndefined(); }); test('should include all note metadata when present', async () => { const mockNote = { noteId: 'note123', title: 'Complete Note', type: 'code', mime: 'text/javascript', isProtected: true, isDeleted: false, dateCreated: '2024-01-01T10:00:00.000Z', dateModified: '2024-01-15T14:30:00.000Z', utcDateCreated: '2024-01-01T10:00:00.000Z', utcDateModified: '2024-01-15T14:30:00.000Z', parentNoteIds: ['parent123', 'parent456'], childNoteIds: ['child789'], attributes: [{ name: 'lang', value: 'javascript' }] }; const mockContent = 'console.log("hello");'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.isProtected).toBe(true); expect(parsedData.note.parentNoteIds).toEqual(['parent123', 'parent456']); expect(parsedData.note.childNoteIds).toEqual(['child789']); expect(parsedData.note.attributes).toEqual([{ name: 'lang', value: 'javascript' }]); expect(parsedData.note.mime).toBe('text/javascript'); }); }); describe('input validation', () => { test('should reject empty noteId', async () => { const result = await getNote(mockTriliumClient, { noteId: '' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Validation error:'); expect(mockTriliumClient.get).not.toHaveBeenCalled(); }); test('should reject null noteId', async () => { const result = await getNote(mockTriliumClient, { noteId: null }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Validation error:'); expect(mockTriliumClient.get).not.toHaveBeenCalled(); }); test('should reject undefined noteId', async () => { const result = await getNote(mockTriliumClient, {}); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Validation error:'); expect(mockTriliumClient.get).not.toHaveBeenCalled(); }); test('should reject non-string noteId', async () => { const result = await getNote(mockTriliumClient, { noteId: 123 }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Validation error:'); expect(mockTriliumClient.get).not.toHaveBeenCalled(); }); test('should accept noteId with special characters', async () => { const mockNote = { noteId: 'note/123!', title: 'Test' }; const mockContent = 'Content'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note/123!' }); expect(result.isError).toBeFalsy(); expect(mockTriliumClient.get).toHaveBeenCalledWith('notes/note/123!'); }); test('should trim whitespace from noteId', async () => { const mockNote = { noteId: 'note123', title: 'Test' }; const mockContent = 'Content'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); await getNote(mockTriliumClient, { noteId: ' note123 ' }); expect(mockTriliumClient.get).toHaveBeenCalledWith('notes/note123'); }); }); describe('API error handling', () => { test('should handle note not found (404)', async () => { const notFoundError = new TriliumAPIError('Note not found', 404); mockTriliumClient.get.mockRejectedValue(notFoundError); const result = await getNote(mockTriliumClient, { noteId: 'nonexistent123' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Note not found: nonexistent123'); }); test('should handle authentication error (401)', async () => { const authError = new TriliumAPIError('Authentication failed', 401); mockTriliumClient.get.mockRejectedValue(authError); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('TriliumNext API error: Authentication failed'); }); test('should handle server error (500)', async () => { const serverError = new TriliumAPIError('Internal server error', 500); mockTriliumClient.get.mockRejectedValue(serverError); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('TriliumNext API error: Internal server error'); }); test('should handle network errors', async () => { const networkError = new Error('Network timeout'); mockTriliumClient.get.mockRejectedValue(networkError); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Failed to get note: Network timeout'); }); test('should handle missing note data', async () => { mockTriliumClient.get.mockResolvedValueOnce(null); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('Note not found: note123'); }); test('should handle content fetch error after successful note fetch', async () => { const mockNote = { noteId: 'note123', title: 'Test Note', type: 'text' }; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockRejectedValueOnce(new TriliumAPIError('Content not accessible', 403)); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.isError).toBe(true); expect(result.content[0].text).toContain('TriliumNext API error: Content not accessible'); }); }); describe('validation error responses include structured data', () => { test('should include structured data in validation error response', async () => { const result = await getNote(mockTriliumClient, { noteId: '' }); expect(result.isError).toBe(true); expect(result.content).toHaveLength(2); expect(result.content[1].type).toBe('application/json'); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.operation).toBe('get_note'); expect(parsedData.error.type).toBe('ValidationError'); expect(parsedData.timestamp).toBeDefined(); }); }); describe('API error responses include structured data', () => { test('should include structured data in API error response', async () => { const apiError = new TriliumAPIError('Server error', 500); mockTriliumClient.get.mockRejectedValue(apiError); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.isError).toBe(true); expect(result.content).toHaveLength(2); expect(result.content[1].type).toBe('application/json'); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.operation).toBe('get_note'); expect(parsedData.error.type).toBe('TriliumAPIError'); expect(parsedData.error.status).toBe(500); expect(parsedData.timestamp).toBeDefined(); }); }); describe('edge cases', () => { test('should handle empty content', async () => { const mockNote = { noteId: 'note123', title: 'Empty Note', type: 'text' }; const mockContent = ''; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.content[0].text).toBe('Note: "Empty Note" (text, 0 chars)'); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.content.data).toBe(''); expect(parsedData.note.content.length).toBe(0); }); test('should handle Unicode characters in title and content', async () => { const mockNote = { noteId: 'note123', title: '日本語のノート 🌸', type: 'text' }; const mockContent = 'こんにちは世界 🎌'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.content[0].text).toContain('日本語のノート 🌸'); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.title).toBe('日本語のノート 🌸'); expect(parsedData.note.content.data).toBe('こんにちは世界 🎌'); }); test('should handle very long note title', async () => { const longTitle = 'A'.repeat(200); const mockNote = { noteId: 'note123', title: longTitle, type: 'text' }; const mockContent = 'Content'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); expect(result.content[0].text).toContain(longTitle); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.title).toBe(longTitle); }); test('should handle note with all optional fields missing', async () => { const mockNote = { noteId: 'note123' // All other fields missing }; const mockContent = 'Basic content'; mockTriliumClient.get .mockResolvedValueOnce(mockNote) .mockResolvedValueOnce(mockContent); const result = await getNote(mockTriliumClient, { noteId: 'note123' }); const parsedData = JSON.parse(result.content[1].text); expect(parsedData.note.title).toBe('Untitled'); expect(parsedData.note.type).toBe('text'); expect(parsedData.note.isProtected).toBe(false); expect(parsedData.note.isDeleted).toBe(false); expect(parsedData.note.parentNoteIds).toEqual([]); expect(parsedData.note.childNoteIds).toEqual([]); expect(parsedData.note.attributes).toEqual([]); }); }); });

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/RadonX/mcp-trilium'

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