Skip to main content
Glama
frontmatter-metadata.test.ts17.6 kB
/** * Frontmatter Metadata Tests * * Comprehensive tests for metadata handling in frontmatter during note operations * including creation, updates, retrieval, and validation scenarios. */ import { test, describe, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert'; import { promises as fs } from 'node:fs'; import { join as pathJoin } from 'node:path'; import { createTestWorkspace, cleanupTestWorkspace, createTestNoteTypes, TEST_CONSTANTS, type TestContext } from './helpers/test-utils.ts'; describe('Frontmatter Metadata Handling', () => { let context: TestContext; beforeEach(async () => { context = await createTestWorkspace('frontmatter-metadata-test'); await createTestNoteTypes(context); }); afterEach(async () => { await cleanupTestWorkspace(context); }); describe('Note Creation with Metadata', () => { test('should create note with metadata in frontmatter', async () => { const title = 'Book Review: Atomic Habits'; const content = 'This book changed my perspective on habit formation.'; const metadata = { author: 'James Clear', rating: 5, tags: ['self-help', 'productivity'], isbn: '978-0735211292', published: true }; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, content, metadata ); // Verify note was created assert.ok(noteInfo.id); assert.ok(noteInfo.path); // Read the actual file content const fileContent = await fs.readFile(noteInfo.path, 'utf-8'); // Verify frontmatter structure assert.match(fileContent, /^---\n/); assert.match(fileContent, /\n---\n\n/); // Verify required metadata fields are in frontmatter assert.match(fileContent, /^title: "Book Review: Atomic Habits"$/m); assert.match(fileContent, /^type: general$/m); assert.match( fileContent, /^created: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/m ); assert.match( fileContent, /^updated: \d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/m ); // Verify custom metadata fields are in frontmatter assert.match(fileContent, /^author: "James Clear"$/m); assert.match(fileContent, /^rating: 5$/m); assert.match(fileContent, /^tags: \["self-help", "productivity"\]$/m); assert.match(fileContent, /^isbn: "978-0735211292"$/m); assert.match(fileContent, /^published: true$/m); // Verify content is after frontmatter assert.match( fileContent, /---\n\nThis book changed my perspective on habit formation\./ ); // Verify metadata is NOT in the body content const bodyContent = fileContent.split('---\n\n')[1]; assert.ok(bodyContent); assert.ok(!bodyContent.includes('author: "James Clear"')); assert.ok(!bodyContent.includes('rating: 5')); }); test('should create note with complex metadata types in frontmatter', async () => { const title = 'Complex Metadata Test'; const content = 'Testing various metadata types.'; const metadata = { stringValue: 'test string', numberValue: 42, floatValue: 3.14, booleanTrue: true, booleanFalse: false, arrayStrings: ['tag1', 'tag2', 'tag3'], arrayNumbers: [1, 2, 3], arrayMixed: ['string', 42, true], nullValue: null }; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, content, metadata ); const fileContent = await fs.readFile(noteInfo.path, 'utf-8'); // Verify different data types are properly formatted in YAML assert.match(fileContent, /^stringValue: "test string"$/m); assert.match(fileContent, /^numberValue: 42$/m); assert.match(fileContent, /^floatValue: 3\.14$/m); assert.match(fileContent, /^booleanTrue: true$/m); assert.match(fileContent, /^booleanFalse: false$/m); assert.match(fileContent, /^arrayStrings: \["tag1", "tag2", "tag3"\]$/m); assert.match(fileContent, /^arrayNumbers: \[1, 2, 3\]$/m); assert.match(fileContent, /^arrayMixed: \["string", 42, true\]$/m); assert.match(fileContent, /^nullValue: null$/m); }); test('should create note with default tags in frontmatter when not provided', async () => { const title = 'Note Without Tags'; const content = 'This note has no custom tags.'; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, content ); const fileContent = await fs.readFile(noteInfo.path, 'utf-8'); // Verify default empty tags array is added assert.match(fileContent, /^tags: \[\]$/m); }); test('should preserve custom tags in frontmatter', async () => { const title = 'Note With Custom Tags'; const content = 'This note has custom tags.'; const metadata = { tags: ['custom', 'important', 'test'] }; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, content, metadata ); const fileContent = await fs.readFile(noteInfo.path, 'utf-8'); // Verify custom tags are properly set assert.match(fileContent, /^tags: \["custom", "important", "test"\]$/m); // Should not have default empty tags assert.ok(!fileContent.match(/^tags: \[\]$/m)); }); }); describe('Note Retrieval and Parsing', () => { test('should correctly parse metadata from frontmatter', async () => { const title = 'Test Note for Parsing'; const content = 'Content for parsing test.'; const metadata = { author: 'Test Author', rating: 4, tags: ['test', 'parsing'], published: true }; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, content, metadata ); // Retrieve the note const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Note should be retrieved successfully'); // Verify metadata was parsed correctly assert.strictEqual(retrievedNote.metadata.title, title); assert.strictEqual(retrievedNote.metadata.type, TEST_CONSTANTS.NOTE_TYPES.DEFAULT); assert.strictEqual(retrievedNote.metadata.author, 'Test Author'); assert.strictEqual(retrievedNote.metadata.rating, 4); assert.deepStrictEqual(retrievedNote.metadata.tags, ['test', 'parsing']); assert.strictEqual(retrievedNote.metadata.published, true); assert.ok(retrievedNote.metadata.created); assert.ok(retrievedNote.metadata.updated); // Verify content was parsed correctly (without frontmatter) assert.strictEqual(retrievedNote.content.trim(), `Content for parsing test.`); }); test('should handle note with manually created frontmatter', async () => { // Create a note file manually with frontmatter const manualNoteContent = `--- title: "Manual Note" type: general author: "Manual Author" rating: 3 tags: ["manual", "test"] published: false custom_field: "custom value" created: 2024-01-01T00:00:00.000Z updated: 2024-01-01T00:00:00.000Z --- # Manual Note This note was created manually with frontmatter. ## Section 1 Some content here.`; const notePath = pathJoin( context.workspace.getNoteTypePath(TEST_CONSTANTS.NOTE_TYPES.DEFAULT), 'manual-note.md' ); // Ensure the directory exists before writing the file await context.workspace.ensureNoteType(TEST_CONSTANTS.NOTE_TYPES.DEFAULT); await fs.writeFile(notePath, manualNoteContent, 'utf-8'); // Retrieve the note using the note manager const retrievedNote = await context.noteManager.getNoteByPath(notePath); assert.ok(retrievedNote, 'Note should be retrieved successfully'); // Verify metadata was parsed correctly assert.strictEqual(retrievedNote.metadata.title, 'Manual Note'); assert.strictEqual(retrievedNote.metadata.type, 'general'); assert.strictEqual(retrievedNote.metadata.author, 'Manual Author'); assert.strictEqual(retrievedNote.metadata.rating, 3); assert.deepStrictEqual(retrievedNote.metadata.tags, ['manual', 'test']); assert.strictEqual(retrievedNote.metadata.published, false); assert.strictEqual(retrievedNote.metadata.custom_field, 'custom value'); assert.strictEqual(retrievedNote.metadata.created, '2024-01-01T00:00:00.000Z'); assert.strictEqual(retrievedNote.metadata.updated, '2024-01-01T00:00:00.000Z'); // Verify content was parsed correctly (without frontmatter) const expectedContent = `# Manual Note This note was created manually with frontmatter. ## Section 1 Some content here.`; assert.strictEqual(retrievedNote.content, expectedContent); }); }); describe('Note Updates with Metadata', () => { test('should update note content while preserving frontmatter metadata', async () => { const title = 'Note to Update'; const originalContent = 'Original content.'; const metadata = { author: 'Original Author', rating: 3, tags: ['original'] }; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, originalContent, metadata ); const updatedContent = 'Updated content with new information.'; // Get the current note to obtain content hash const currentNote = await context.noteManager.getNote(noteInfo.id); assert(currentNote, 'Current note should not be null'); assert(currentNote.content_hash, 'Current note should have content hash'); // Update the note await context.noteManager.updateNote( noteInfo.id, updatedContent, currentNote.content_hash ); // Read the updated file const fileContent = await fs.readFile(noteInfo.path, 'utf-8'); // Verify frontmatter metadata is preserved assert.match(fileContent, /^title: "Note to Update"$/m); assert.match(fileContent, /^author: "Original Author"$/m); assert.match(fileContent, /^rating: 3$/m); assert.match(fileContent, /^tags: \["original"\]$/m); // Verify updated timestamp changed (now quoted) assert.match( fileContent, /^updated: "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z"$/m ); // Verify content was updated (note: update function doesn't add title header automatically) assert.match(fileContent, /Updated content with new information\./); }); test('should update note with new metadata while preserving existing metadata', async () => { const title = 'Note for Metadata Update'; const content = 'Content for metadata update test.'; const originalMetadata = { author: 'Original Author', rating: 3, tags: ['original'] }; const updatedNote = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, content, originalMetadata ); const newMetadata = { rating: 5, tags: ['updated', 'improved'], new_field: 'new value' }; // Get the current note to obtain content hash const currentNote = await context.noteManager.getNote(updatedNote.id); assert(currentNote, 'Current note should not be null'); assert(currentNote.content_hash, 'Current note should have content hash'); // Update with new metadata await context.noteManager.updateNoteWithMetadata( updatedNote.id, content, newMetadata, currentNote.content_hash ); // Read the updated file const fileContent = await fs.readFile(updatedNote.path, 'utf-8'); // Verify original author is preserved (now that we fixed the merge logic) assert.match(fileContent, /^author: "Original Author"$/m); // Verify updated fields assert.match(fileContent, /^rating: 5$/m); assert.match(fileContent, /^tags: \["updated", "improved"\]$/m); assert.match(fileContent, /^new_field: "new value"$/m); // Verify system fields are preserved assert.match(fileContent, /^title: "Note for Metadata Update"$/m); assert.match(fileContent, /^type: "general"$/m); assert.match( fileContent, /^created: "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z"$/m ); assert.match( fileContent, /^updated: "\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z"$/m ); }); }); describe('Error Handling and Edge Cases', () => { test('should handle note with malformed frontmatter gracefully', async () => { // Create a note with malformed frontmatter const malformedContent = `--- title: "Malformed Note" invalid: yaml: content: [unclosed --- # Malformed Note This note has malformed frontmatter.`; const notePath = pathJoin( context.workspace.getNoteTypePath('general'), 'malformed-note.md' ); // Ensure the directory exists before writing the file await context.workspace.ensureNoteType('general'); await fs.writeFile(notePath, malformedContent, 'utf-8'); // Should still be able to retrieve the note const retrievedNote = await context.noteManager.getNoteByPath(notePath); assert.ok(retrievedNote, 'Note should be retrieved successfully'); // Metadata should be empty due to parsing failure assert.deepStrictEqual(retrievedNote.metadata, {}); // Content should still be available assert.strictEqual( retrievedNote.content, '# Malformed Note\n\nThis note has malformed frontmatter.' ); }); test('should handle note without frontmatter', async () => { // Create a note without frontmatter const plainContent = `# Plain Note This note has no frontmatter. Just plain markdown content.`; const notePath = pathJoin( context.workspace.getNoteTypePath('general'), 'plain-note.md' ); // Ensure the directory exists before writing the file await context.workspace.ensureNoteType('general'); await fs.writeFile(notePath, plainContent, 'utf-8'); // Should be able to retrieve the note const retrievedNote = await context.noteManager.getNoteByPath(notePath); assert.ok(retrievedNote, 'Note should be retrieved successfully'); // Metadata should be empty assert.deepStrictEqual(retrievedNote.metadata, {}); // Content should be the entire file content assert.strictEqual(retrievedNote.content, plainContent); }); test('should handle note with only frontmatter and no content', async () => { const title = 'Empty Content Note'; const metadata = { author: 'Test Author', tags: ['empty'] }; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, '', // Empty content metadata ); const fileContent = await fs.readFile(noteInfo.path, 'utf-8'); // Should have frontmatter assert.match(fileContent, /^---\n/); assert.match(fileContent, /\n---\n\n/); assert.match(fileContent, /^title: "Empty Content Note"$/m); assert.match(fileContent, /^author: "Test Author"$/m); }); test('should handle metadata with special characters and quotes', async () => { const title = 'Special Characters Test'; const content = 'Testing special characters in metadata.'; const metadata = { description: 'A note with "quotes" and special chars: @#$%^&*()', author: "O'Reilly & Associates", tags: ['test-tag', 'special_chars', 'with spaces'], note_with_colon: 'Value: with colon', unicode: '🔥 Unicode characters 你好' }; const noteInfo = await context.noteManager.createNote( TEST_CONSTANTS.NOTE_TYPES.DEFAULT, title, content, metadata ); const fileContent = await fs.readFile(noteInfo.path, 'utf-8'); // Verify special characters are properly handled in YAML (quotes should be escaped) assert.match( fileContent, /^description: "A note with \\"quotes\\" and special chars: @#\$%\^&\*\(\)"$/m ); assert.match(fileContent, /^author: "O'Reilly & Associates"$/m); assert.match( fileContent, /^tags: \["test-tag", "special_chars", "with spaces"\]$/m ); assert.match(fileContent, /^note_with_colon: "Value: with colon"$/m); assert.match(fileContent, /^unicode: "🔥 Unicode characters 你好"$/m); // Verify the note can be retrieved and parsed correctly const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert.ok(retrievedNote, 'Note should be retrieved successfully'); assert.strictEqual( retrievedNote.metadata.description, 'A note with "quotes" and special chars: @#$%^&*()' ); assert.strictEqual(retrievedNote.metadata.author, "O'Reilly & Associates"); assert.strictEqual(retrievedNote.metadata.unicode, '🔥 Unicode characters 你好'); assert.strictEqual(retrievedNote.metadata.note_with_colon, 'Value: with colon'); }); }); });

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