Skip to main content
Glama
note-retrieval.test.ts26.6 kB
/** * Note Retrieval Tests * * Comprehensive tests for note retrieval functionality including * content parsing, metadata extraction, error handling, and edge cases. */ import { test, describe, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert'; import { promises as fs } from 'node:fs'; import { createTestWorkspace, cleanupTestWorkspace, createTestNotes, createTestNotesWithMetadata, createTestNoteTypes, TEST_CONSTANTS, type TestContext } from './helpers/test-utils.ts'; describe('Note Retrieval', () => { let context: TestContext; beforeEach(async () => { context = await createTestWorkspace('note-retrieval-test'); await createTestNoteTypes(context); await createTestNotes(context); await createTestNotesWithMetadata(context); }); afterEach(async () => { await cleanupTestWorkspace(context); }); describe('Basic Note Retrieval', () => { test('should retrieve a note with basic content', async () => { // Create a simple note first const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Simple Retrieval Test', 'This is a simple note for retrieval testing.' ); // Retrieve the note const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Should retrieve the note'); assert.strictEqual(retrievedNote.id, noteInfo.id, 'Should have correct ID'); assert.strictEqual( retrievedNote.title, 'Simple Retrieval Test', 'Should have correct title' ); assert.strictEqual( retrievedNote.type, TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Should have correct type' ); assert.ok( retrievedNote.content.includes('simple note for retrieval'), 'Should have correct content' ); assert.ok(retrievedNote.created, 'Should have created timestamp'); assert.ok(retrievedNote.updated, 'Should have updated timestamp'); }); test('should retrieve note by file path', async () => { // Create a note in a specific type const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.PROJECT, 'Project Note', 'This is a project note.' ); // Retrieve by path const retrievedNote = await context.noteManager.getNoteByPath(noteInfo.path); assert.ok(retrievedNote, 'Should retrieve note by path'); assert.strictEqual( retrievedNote.title, 'Project Note', 'Should have correct title' ); assert.strictEqual( retrievedNote.type, TEST_CONSTANTS.NOTE_TYPES.PROJECT, 'Should have correct type' ); }); test('should return null for non-existent note', async () => { const nonExistentId = 'general/non-existent-note.md'; const result = await context.noteManager.getNote(nonExistentId); assert.strictEqual(result, null, 'Should return null for non-existent note'); }); }); describe('Metadata Extraction', () => { test('should parse YAML frontmatter correctly', async () => { const noteWithMetadata = `--- title: "Test Note with Metadata" author: "Test Author" tags: ["test", "metadata", "parsing"] priority: 1 published: true custom_field: "custom value" --- # Test Content This note has comprehensive metadata.`; // Write note directly to test parsing const notePath = context.workspace.getNotePath( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'metadata-test.md' ); await fs.writeFile(notePath, noteWithMetadata, 'utf8'); const noteId = `${TEST_CONSTANTS.NOTE_TYPES.DEFAULT}/metadata-test.md`; const retrievedNote = await context.noteManager.getNote(noteId); assert.ok(retrievedNote, 'Should retrieve note with metadata'); assert.strictEqual( retrievedNote.metadata?.author, 'Test Author', 'Should parse author' ); assert.deepStrictEqual( retrievedNote.metadata?.tags, ['test', 'metadata', 'parsing'], 'Should parse tags array' ); assert.strictEqual( retrievedNote.metadata?.priority, 1, 'Should parse numeric priority' ); assert.strictEqual(retrievedNote.metadata?.published, true, 'Should parse boolean'); assert.strictEqual( retrievedNote.metadata?.custom_field, 'custom value', 'Should parse custom fields' ); }); test('should handle malformed YAML frontmatter gracefully', async () => { const noteWithBadYaml = `--- title: "Unclosed quote invalid: yaml: structure: here bad_list: - item 1 - item 2 - nested without proper indentation --- # Content This note has malformed YAML.`; const notePath = context.workspace.getNotePath( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'bad-yaml.md' ); await fs.writeFile(notePath, noteWithBadYaml, 'utf8'); const noteId = `${TEST_CONSTANTS.NOTE_TYPES.DEFAULT}/bad-yaml.md`; const retrievedNote = await context.noteManager.getNote(noteId); assert.ok(retrievedNote, 'Should retrieve note despite bad YAML'); assert.ok( retrievedNote.content.includes('This note has malformed YAML'), 'Should preserve content' ); // Metadata might be empty or partial due to parsing errors assert.ok( typeof retrievedNote.metadata === 'object', 'Should provide metadata object' ); }); test('should handle notes without frontmatter', async () => { const noteWithoutFrontmatter = `# Simple Note This note has no frontmatter at all. Just plain markdown content.`; const notePath = context.workspace.getNotePath( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'no-frontmatter.md' ); await fs.writeFile(notePath, noteWithoutFrontmatter, 'utf8'); const noteId = `${TEST_CONSTANTS.NOTE_TYPES.DEFAULT}/no-frontmatter.md`; const retrievedNote = await context.noteManager.getNote(noteId); assert.ok(retrievedNote, 'Should retrieve note without frontmatter'); assert.ok(retrievedNote.content.includes('Simple Note'), 'Should have content'); assert.ok(retrievedNote.metadata, 'Should provide empty metadata object'); }); test('should extract title from content if not in frontmatter', async () => { const noteWithContentTitle = `# Content Title This note's title comes from the H1 header, not frontmatter.`; const notePath = context.workspace.getNotePath( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'content-title.md' ); await fs.writeFile(notePath, noteWithContentTitle, 'utf8'); const noteId = `${TEST_CONSTANTS.NOTE_TYPES.DEFAULT}/content-title.md`; const retrievedNote = await context.noteManager.getNote(noteId); assert.ok(retrievedNote, 'Should retrieve note'); assert.strictEqual( retrievedNote.title, 'Content Title', 'Should extract title from content' ); }); }); describe('Content Processing', () => { test('should preserve markdown formatting', async () => { const markdownContent = `# Main Title ## Subsection This note contains: - **Bold text** - *Italic text* - [Links](https://example.com) - \`inline code\` ### Code Block \`\`\`javascript function example() { return "code block"; } \`\`\` ### Lists 1. Numbered item 2. Another item - Nested bullet - Another bullet ### Blockquote > This is a blockquote > with multiple lines ### Table | Column 1 | Column 2 | |----------|----------| | Cell 1 | Cell 2 | | Cell 3 | Cell 4 | `; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Markdown Test', markdownContent ); const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Should retrieve markdown note'); assert.ok( retrievedNote.content.includes('**Bold text**'), 'Should preserve bold formatting' ); assert.ok( retrievedNote.content.includes('*Italic text*'), 'Should preserve italic formatting' ); assert.ok( retrievedNote.content.includes('[Links](https://example.com)'), 'Should preserve links' ); assert.ok( retrievedNote.content.includes('```javascript'), 'Should preserve code blocks' ); assert.ok( retrievedNote.content.includes('> This is a blockquote'), 'Should preserve blockquotes' ); assert.ok( retrievedNote.content.includes('| Column 1 | Column 2 |'), 'Should preserve tables' ); }); test('should handle empty content', async () => { const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Empty Content Note', '' ); const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Should retrieve note with empty content'); assert.strictEqual(retrievedNote.title, 'Empty Content Note', 'Should have title'); assert.ok(typeof retrievedNote.content === 'string', 'Content should be string'); }); test('should handle very large content', async () => { const largeContent = 'A'.repeat(10000) + '\n\n' + 'B'.repeat(10000); const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Large Content Note', largeContent ); const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Should retrieve note with large content'); assert.ok(retrievedNote.content.length > 20000, 'Should preserve large content'); assert.ok( retrievedNote.content.includes('A'.repeat(100)), 'Should contain expected content' ); }); }); describe('Multiple Note Types', () => { test('should retrieve notes from different types', async () => { const noteTypes = [ TEST_CONSTANTS.NOTE_TYPES.DEFAULT, TEST_CONSTANTS.NOTE_TYPES.PROJECT, TEST_CONSTANTS.NOTE_TYPES.MEETING, TEST_CONSTANTS.NOTE_TYPES.BOOK_REVIEW ]; const createdNotes = []; // Create notes in different types for (const type of noteTypes) { let metadata = {}; // Provide required metadata for book-reviews type if (type === 'book-reviews') { metadata = { author: 'Test Author', rating: 4, status: 'completed' }; } const noteInfo = await context.noteManager.createNote( type, `${type} Note`, `This is a note in ${type} type.`, metadata ); createdNotes.push(noteInfo); } // Retrieve all notes for (let i = 0; i < createdNotes.length; i++) { const retrievedNote = await context.noteManager.getNote(createdNotes[i].id); assert.ok(retrievedNote, `Should retrieve ${noteTypes[i]} note`); assert.strictEqual( retrievedNote.type, noteTypes[i], `Should have correct type: ${noteTypes[i]}` ); assert.ok( retrievedNote.content.includes(noteTypes[i]), 'Should have type-specific content' ); } }); test('should handle notes with same filename in different types', async () => { const _filename = 'same-name'; const types = [ TEST_CONSTANTS.NOTE_TYPES.DEFAULT, TEST_CONSTANTS.NOTE_TYPES.PROJECT ]; const notes = []; for (const type of types) { const noteInfo = await context.noteManager.createNote( type, 'Same Name', `Content for ${type} note.` ); notes.push(noteInfo); } // Retrieve both notes for (let i = 0; i < notes.length; i++) { const retrievedNote = await context.noteManager.getNote(notes[i].id); assert.ok(retrievedNote, `Should retrieve note from ${types[i]}`); assert.strictEqual(retrievedNote.type, types[i], 'Should have correct type'); assert.ok( retrievedNote.content.includes(types[i]), 'Should have correct content' ); } // Verify IDs are different assert.notStrictEqual(notes[0].id, notes[1].id, 'Should have different IDs'); }); }); describe('File System Integration', () => { test('should handle file modifications outside the system', async () => { // Create a note normally const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'External Modification Test', 'Original content.' ); // Modify the file directly const modifiedContent = `--- title: "Externally Modified Note" author: "External Editor" --- # Modified Content This was modified outside the system.`; await fs.writeFile(noteInfo.path, modifiedContent, 'utf8'); // Retrieve the note const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Should retrieve externally modified note'); assert.strictEqual( retrievedNote.title, 'Externally Modified Note', 'Should reflect external changes' ); assert.strictEqual( retrievedNote.metadata?.author, 'External Editor', 'Should parse new metadata' ); assert.ok( retrievedNote.content.includes('Modified Content'), 'Should have modified content' ); }); test('should handle corrupted files gracefully', async () => { // Create a note normally const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Corruption Test', 'Original content.' ); // Corrupt the file with binary data const corruptedContent = Buffer.from([0x00, 0x01, 0x02, 0xff, 0xfe, 0xfd]); await fs.writeFile(noteInfo.path, corruptedContent); // Try to retrieve the corrupted note const retrievedNote = await context.noteManager.getNote(noteInfo.id); // Should handle gracefully - either return null or return with error indication if (retrievedNote) { assert.ok( typeof retrievedNote.content === 'string', 'Content should be string even if corrupted' ); } else { assert.strictEqual(retrievedNote, null, 'Should return null for corrupted file'); } }); test('should respect file permissions', async () => { // Create a note normally const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Permissions Test', 'Original content.' ); // Make file unreadable (skip on Windows) if (process.platform !== 'win32') { await fs.chmod(noteInfo.path, 0o000); // Try to retrieve the unreadable note await assert.rejects( () => context.noteManager.getNote(noteInfo.id), /permission denied|EACCES/i, 'Should throw permission error' ); // Restore permissions for cleanup await fs.chmod(noteInfo.path, 0o644); } }); }); describe('Performance and Edge Cases', () => { test('should handle concurrent retrieval requests', async () => { // Create a note const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Concurrent Test', 'Content for concurrent testing.' ); // Make multiple concurrent retrieval requests const promises = []; const concurrentCount = 10; for (let i = 0; i < concurrentCount; i++) { promises.push(context.noteManager.getNote(noteInfo.id)); } const results = await Promise.all(promises); // All requests should succeed for (let i = 0; i < concurrentCount; i++) { assert.ok(results[i], `Request ${i} should succeed`); assert.strictEqual( results[i]?.title, 'Concurrent Test', `Request ${i} should have correct title` ); } }); test('should cache repeated requests efficiently', async () => { // Create a note const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Cache Test', 'Content for cache testing.' ); // Make repeated requests const startTime = Date.now(); const firstRetrieval = await context.noteManager.getNote(noteInfo.id); const secondRetrieval = await context.noteManager.getNote(noteInfo.id); const thirdRetrieval = await context.noteManager.getNote(noteInfo.id); const endTime = Date.now(); // All should return the same data assert.strictEqual( firstRetrieval?.title, 'Cache Test', 'First retrieval should work' ); assert.strictEqual( secondRetrieval?.title, 'Cache Test', 'Second retrieval should work' ); assert.strictEqual( thirdRetrieval?.title, 'Cache Test', 'Third retrieval should work' ); // Should complete reasonably quickly assert.ok(endTime - startTime < 1000, 'Multiple retrievals should be fast'); }); test('should handle special characters in file content', async () => { const specialContent = `# Special Characters Test Unicode characters: 🚀 💫 ⭐ Emoji: 😀 😃 😄 😁 😆 😅 Non-Latin: 你好 世界 こんにちは العالم Mathematical: ∑ ∫ ∂ ∇ ∞ ≈ ≠ ≤ ≥ Special symbols: © ® ™ § ¶ † ‡ Combining: é ñ ü ø å æ œ ß Binary-like content: \x00\x01\x02 Control chars: Quotes: "double" 'single' "smart" 'smart' Backslashes: \\n \\t \\r \\ `; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Special Characters Test', specialContent ); const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Should retrieve note with special characters'); assert.ok(retrievedNote.content.includes('🚀'), 'Should preserve emoji'); assert.ok(retrievedNote.content.includes('你好'), 'Should preserve Unicode'); assert.ok( retrievedNote.content.includes('∑'), 'Should preserve mathematical symbols' ); assert.ok( retrievedNote.content.includes('"smart"'), 'Should preserve smart quotes' ); }); }); describe('Note ID Format Compatibility', () => { test('should retrieve note using ID without .md extension', async () => { // Create a note const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'ID Format Test', 'Testing ID format compatibility.' ); // Extract the ID without .md extension const idWithoutExtension = noteInfo.id.replace(/\.md$/, ''); // Retrieve using ID without .md extension const retrievedNote = await context.noteManager.getNote(idWithoutExtension); assert.ok(retrievedNote, 'Should retrieve note using ID without .md extension'); assert.strictEqual( retrievedNote.id, idWithoutExtension, 'Should return ID as provided' ); assert.strictEqual( retrievedNote.title, 'ID Format Test', 'Should have correct title' ); assert.ok( retrievedNote.content.includes('Testing ID format compatibility'), 'Should have correct content' ); }); test('should retrieve note using ID with .md extension', async () => { // Create a note const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.PROJECT, 'ID Format With Extension Test', 'Testing ID format with extension.' ); // Retrieve using the full ID (which includes .md extension) const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Should retrieve note using ID with .md extension'); assert.strictEqual(retrievedNote.id, noteInfo.id, 'Should have correct ID'); assert.strictEqual( retrievedNote.title, 'ID Format With Extension Test', 'Should have correct title' ); assert.ok( retrievedNote.content.includes('Testing ID format with extension'), 'Should have correct content' ); }); test('should handle both ID formats for the same note', async () => { // Create a note in a specific type const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.MEETING, 'Dual Format Test', 'Testing both ID formats for same note.' ); // Extract ID without extension const idWithoutExtension = noteInfo.id.replace(/\.md$/, ''); const idWithExtension = noteInfo.id; // Retrieve using both formats const [noteWithoutExt, noteWithExt] = await Promise.all([ context.noteManager.getNote(idWithoutExtension), context.noteManager.getNote(idWithExtension) ]); // Both should succeed and return the same note content assert.ok(noteWithoutExt, 'Should retrieve note using ID without extension'); assert.ok(noteWithExt, 'Should retrieve note using ID with extension'); // IDs should match what was requested assert.strictEqual( noteWithoutExt.id, idWithoutExtension, 'Should return ID without extension as provided' ); assert.strictEqual( noteWithExt.id, idWithExtension, 'Should return ID with extension as provided' ); // Content should be identical assert.strictEqual( noteWithoutExt.title, noteWithExt.title, 'Both formats should return same title' ); assert.strictEqual( noteWithoutExt.content, noteWithExt.content, 'Both formats should return same content' ); assert.strictEqual( noteWithoutExt.type, noteWithExt.type, 'Both formats should return same type' ); assert.strictEqual( noteWithoutExt.path, noteWithExt.path, 'Both formats should return same path' ); }); test('should handle mixed ID formats in different note types', async () => { const testCases = [ { type: TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title: 'Default Type Test' }, { type: TEST_CONSTANTS.NOTE_TYPES.PROJECT, title: 'Project Type Test' }, { type: TEST_CONSTANTS.NOTE_TYPES.MEETING, title: 'Meeting Type Test' } ]; const createdNotes = []; // Create notes in different types for (const testCase of testCases) { const noteInfo = await context.noteManager.createNote( testCase.type, testCase.title, `Content for ${testCase.type} note.` ); createdNotes.push({ ...noteInfo, expectedTitle: testCase.title }); } // Test each note with both ID formats for (const note of createdNotes) { const idWithoutExtension = note.id.replace(/\.md$/, ''); const idWithExtension = note.id; // Test both formats const [noteWithoutExt, noteWithExt] = await Promise.all([ context.noteManager.getNote(idWithoutExtension), context.noteManager.getNote(idWithExtension) ]); assert.ok(noteWithoutExt, `Should retrieve ${note.type} note without extension`); assert.ok(noteWithExt, `Should retrieve ${note.type} note with extension`); assert.strictEqual( noteWithoutExt.title, note.expectedTitle, `Should have correct title for ${note.type} without extension` ); assert.strictEqual( noteWithExt.title, note.expectedTitle, `Should have correct title for ${note.type} with extension` ); } }); }); describe('Error Handling and Recovery', () => { test('should provide helpful error messages for invalid IDs', async () => { const invalidIds = [ '', 'invalid-format', 'missing/extension', 'too/many/slashes/here.md', '../parent-directory.md', 'absolute/path.md' ]; for (const invalidId of invalidIds) { const result = await context.noteManager.getNote(invalidId); assert.strictEqual( result, null, `Should return null for invalid ID: ${invalidId}` ); } }); test('should handle workspace path traversal attempts', async () => { const maliciousIds = [ '../../../etc/passwd', 'general/../../../etc/passwd', 'general/../../outside-workspace.md', '..\\..\\windows-path.md' ]; for (const maliciousId of maliciousIds) { const result = await context.noteManager.getNote(maliciousId); assert.strictEqual(result, null, `Should reject malicious path: ${maliciousId}`); } }); test('should recover from temporary file system issues', async () => { // Create a note const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, 'Recovery Test', 'Test recovery functionality.' ); // Verify it can be retrieved initially const initialRetrieval = await context.noteManager.getNote(noteInfo.id); assert.ok(initialRetrieval, 'Should retrieve note initially'); // Temporarily move the file to simulate file system issue const tempPath = noteInfo.path + '.temp'; await fs.rename(noteInfo.path, tempPath); // Should return null when file is missing const missingRetrieval = await context.noteManager.getNote(noteInfo.id); assert.strictEqual(missingRetrieval, null, 'Should return null when file missing'); // Restore the file await fs.rename(tempPath, noteInfo.path); // Should work again after recovery const recoveredRetrieval = await context.noteManager.getNote(noteInfo.id); assert.ok(recoveredRetrieval, 'Should retrieve note after recovery'); assert.strictEqual( recoveredRetrieval.title, 'Recovery Test', 'Should have correct data after recovery' ); }); }); });

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/disnet/flint-note'

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