Skip to main content
Glama
batch-operations.test.ts21.7 kB
/** * Note Operations Unit Tests * * Tests for both single and batch note creation and update functionality */ import { test, describe, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert'; import { createTestWorkspace, cleanupTestWorkspace, createTestNoteTypes, type TestContext } from './helpers/test-utils.ts'; import type { BatchCreateNoteInput, BatchUpdateNoteInput, BatchCreateResult, BatchUpdateResult } from '../../src/types/index.js'; describe('Note Operations (Single and Batch)', () => { let context: TestContext; beforeEach(async () => { context = await createTestWorkspace('note-ops'); await createTestNoteTypes(context); }); afterEach(async () => { await cleanupTestWorkspace(context); }); describe('Single Note Creation', () => { test('should create a single note', async () => { const noteInfo = await context.noteManager.createNote( 'general', 'Single Test Note', 'Content for single test note' ); assert(noteInfo); assert.strictEqual(noteInfo.title, 'Single Test Note'); assert.strictEqual(noteInfo.type, 'general'); assert(noteInfo.id); assert(noteInfo.created); }); test('should create a single note with metadata', async () => { const noteInfo = await context.noteManager.createNote( 'book-reviews', 'Single Book Review', 'Great book review content', { author: 'Test Author', rating: 5, status: 'completed' } ); assert(noteInfo); assert.strictEqual(noteInfo.title, 'Single Book Review'); // Verify the note was created with metadata const retrievedNote = await context.noteManager.getNote(noteInfo.id); assert(retrievedNote, 'Retrieved note should not be null'); assert.strictEqual(retrievedNote.metadata?.author, 'Test Author'); assert.strictEqual(retrievedNote.metadata?.rating, 5); }); }); describe('Batch Note Creation', () => { test('should create multiple notes successfully', async () => { const notes: BatchCreateNoteInput[] = [ { type: 'general', title: 'Batch Note 1', content: 'Content for batch note 1' }, { type: 'general', title: 'Batch Note 2', content: 'Content for batch note 2' }, { type: 'projects', title: 'Project Alpha', content: 'Project planning notes' } ]; const result: BatchCreateResult = await context.noteManager.batchCreateNotes(notes); assert.strictEqual(result.total, 3); assert.strictEqual(result.successful, 3); assert.strictEqual(result.failed, 0); assert.strictEqual(result.results.length, 3); // Check each result for (let i = 0; i < result.results.length; i++) { const itemResult = result.results[i]; assert.strictEqual(itemResult.success, true); assert.strictEqual(itemResult.input, notes[i]); assert(itemResult.result); assert.strictEqual(itemResult.result.title, notes[i].title); assert.strictEqual(itemResult.result.type, notes[i].type); assert(!itemResult.error); } }); test('should create notes with metadata', async () => { const notes: BatchCreateNoteInput[] = [ { type: 'book-reviews', title: 'The Pragmatic Programmer', content: 'Excellent book on software development practices', metadata: { author: 'Andy Hunt', rating: 5, status: 'completed', genre: 'programming' } }, { type: 'book-reviews', title: 'Clean Code', content: 'Guide to writing maintainable code', metadata: { author: 'Robert Martin', rating: 4, status: 'completed', genre: 'programming' } } ]; const result: BatchCreateResult = await context.noteManager.batchCreateNotes(notes); assert.strictEqual(result.successful, 2); assert.strictEqual(result.failed, 0); // Verify notes were created with metadata for (const itemResult of result.results) { assert.strictEqual(itemResult.success, true); assert(itemResult.result); // Read the actual note to verify metadata const note = await context.noteManager.getNote(itemResult.result.id); assert(note, 'Note should not be null'); assert(note.metadata); assert.strictEqual(note.metadata.author, itemResult.input.metadata?.author); assert.strictEqual(note.metadata.rating, itemResult.input.metadata?.rating); } }); test('should handle partial failures gracefully', async () => { const notes: BatchCreateNoteInput[] = [ { type: 'general', title: 'Valid Note', content: 'This should succeed' }, { type: 'invalid/type', title: 'Invalid Note', content: 'This should fail due to invalid type' }, { type: 'general', title: '', // Empty title should fail content: 'This should fail due to empty title' }, { type: 'general', title: 'Another Valid Note', content: 'This should succeed' } ]; const result: BatchCreateResult = await context.noteManager.batchCreateNotes(notes); assert.strictEqual(result.total, 4); assert.strictEqual(result.successful, 2); assert.strictEqual(result.failed, 2); // Check successful notes const successfulResults = result.results.filter(r => r.success); assert.strictEqual(successfulResults.length, 2); assert.strictEqual(successfulResults[0].input.title, 'Valid Note'); assert.strictEqual(successfulResults[1].input.title, 'Another Valid Note'); // Check failed notes const failedResults = result.results.filter(r => !r.success); assert.strictEqual(failedResults.length, 2); assert(failedResults[0].error); assert(failedResults[1].error); assert(failedResults[0].error!.includes('Invalid note type name')); assert(failedResults[1].error!.includes('Note title is required')); }); test('should handle empty batch', async () => { const result: BatchCreateResult = await context.noteManager.batchCreateNotes([]); assert.strictEqual(result.total, 0); assert.strictEqual(result.successful, 0); assert.strictEqual(result.failed, 0); assert.strictEqual(result.results.length, 0); }); test('should handle filename conflicts', async () => { // Create a note first await context.noteManager.createNote( 'general', 'Duplicate Title', 'Original content' ); const notes: BatchCreateNoteInput[] = [ { type: 'general', title: 'Duplicate Title', content: 'This should fail due to duplicate filename' }, { type: 'general', title: 'Unique Title', content: 'This should succeed' } ]; const result: BatchCreateResult = await context.noteManager.batchCreateNotes(notes); assert.strictEqual(result.total, 2); assert.strictEqual(result.successful, 1); assert.strictEqual(result.failed, 1); const failedResult = result.results.find(r => !r.success); assert(failedResult); assert(failedResult.error); assert(failedResult.error.includes('already exists')); }); }); describe('Single Note Updates', () => { test('should update a single note', async () => { // Create a note first const noteInfo = await context.noteManager.createNote( 'general', 'Update Test', 'Original content' ); // 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 it const result = await context.noteManager.updateNote( noteInfo.id, 'Updated content', currentNote.content_hash ); assert.strictEqual(result.updated, true); assert(result.timestamp); // Verify the update const updatedNote = await context.noteManager.getNote(noteInfo.id); assert(updatedNote, 'Updated note should not be null'); assert(updatedNote.content.includes('Updated content')); }); test('should update note with metadata', async () => { // Create a note with initial metadata const noteInfo = await context.noteManager.createNote( 'book-reviews', 'Metadata Update Test', 'Original content', { author: 'Original Author', rating: 3, status: 'reading' } ); // 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 with new metadata const result = await context.noteManager.updateNoteWithMetadata( noteInfo.id, 'Updated content', { author: 'Updated Author', rating: 5, status: 'completed', notes: 'Added some notes' }, currentNote.content_hash ); assert.strictEqual(result.updated, true); // Verify the update const updatedNote = await context.noteManager.getNote(noteInfo.id); assert(updatedNote, 'Updated note should not be null'); assert(updatedNote.content.includes('Updated content')); assert.strictEqual(updatedNote.metadata?.author, 'Updated Author'); assert.strictEqual(updatedNote.metadata?.rating, 5); assert.strictEqual(updatedNote.metadata?.status, 'completed'); }); }); describe('Batch Note Updates', () => { test('should update multiple notes successfully', async () => { // Create test notes first const note1 = await context.noteManager.createNote( 'general', 'Update Test 1', 'Original content 1' ); const note2 = await context.noteManager.createNote( 'general', 'Update Test 2', 'Original content 2' ); const note3 = await context.noteManager.createNote( 'projects', 'Update Test 3', 'Original content 3' ); // Get current notes to obtain content hashes const currentNote1 = await context.noteManager.getNote(note1.id); const currentNote2 = await context.noteManager.getNote(note2.id); const currentNote3 = await context.noteManager.getNote(note3.id); assert(currentNote1?.content_hash, 'Note 1 should have content hash'); assert(currentNote2?.content_hash, 'Note 2 should have content hash'); assert(currentNote3?.content_hash, 'Note 3 should have content hash'); const updates: BatchUpdateNoteInput[] = [ { identifier: note1.id, content: 'Updated content 1', content_hash: currentNote1.content_hash }, { identifier: note2.id, content: 'Updated content 2', content_hash: currentNote2.content_hash }, { identifier: note3.id, content: 'Updated content 3', content_hash: currentNote3.content_hash } ]; const result: BatchUpdateResult = await context.noteManager.batchUpdateNotes(updates); assert.strictEqual(result.total, 3); assert.strictEqual(result.successful, 3); assert.strictEqual(result.failed, 0); // Verify updates for (let i = 0; i < result.results.length; i++) { const itemResult = result.results[i]; assert.strictEqual(itemResult.success, true); assert(itemResult.result); assert.strictEqual(itemResult.result.updated, true); assert(!itemResult.error); // Verify actual content was updated const updatedNote = await context.noteManager.getNote(updates[i].identifier); assert(updatedNote, 'Updated note should not be null'); assert(updatedNote.content.includes(`Updated content ${i + 1}`)); } }); test('should update notes with metadata only', async () => { // Create a test note with initial metadata const note = await context.noteManager.createNote( 'book-reviews', 'Test Book', 'Original review content', { author: 'Original Author', rating: 3, status: 'reading' } ); // Get current note to obtain content hash const currentNote = await context.noteManager.getNote(note.id); assert(currentNote?.content_hash, 'Note should have content hash'); const updates: BatchUpdateNoteInput[] = [ { identifier: note.id, metadata: { author: 'Updated Author', rating: 5, status: 'completed', genre: 'fiction' }, content_hash: currentNote.content_hash } ]; const result: BatchUpdateResult = await context.noteManager.batchUpdateNotes(updates); assert.strictEqual(result.successful, 1); assert.strictEqual(result.failed, 0); // Verify metadata was updated while content remained the same const updatedNote = await context.noteManager.getNote(note.id); assert(updatedNote, 'Updated note should not be null'); assert(updatedNote.content.includes('Original review content')); assert.strictEqual(updatedNote.metadata?.author, 'Updated Author'); assert.strictEqual(updatedNote.metadata?.rating, 5); assert.strictEqual(updatedNote.metadata?.status, 'completed'); assert.strictEqual(updatedNote.metadata?.genre, 'fiction'); }); test('should update notes with both content and metadata', async () => { const note = await context.noteManager.createNote( 'book-reviews', 'Mixed Update Test', 'Original content', { author: 'Original Author', rating: 3, status: 'reading' } ); // Get current note to obtain content hash const currentNote = await context.noteManager.getNote(note.id); assert(currentNote?.content_hash, 'Note should have content hash'); const updates: BatchUpdateNoteInput[] = [ { identifier: note.id, content: 'Updated content with both changes', metadata: { author: 'Updated Author', rating: 5, status: 'completed' }, content_hash: currentNote.content_hash } ]; const result: BatchUpdateResult = await context.noteManager.batchUpdateNotes(updates); assert.strictEqual(result.successful, 1); // Verify both content and metadata were updated const updatedNote = await context.noteManager.getNote(note.id); assert(updatedNote, 'Updated note should not be null'); assert(updatedNote.content.includes('Updated content with both changes')); assert.strictEqual(updatedNote.metadata?.author, 'Updated Author'); assert.strictEqual(updatedNote.metadata?.rating, 5); assert.strictEqual(updatedNote.metadata?.status, 'completed'); }); test('should handle partial failures in updates', async () => { // Create one valid note const validNote = await context.noteManager.createNote( 'general', 'Valid Update Target', 'Content to update' ); // Get current note to obtain content hash const currentNote = await context.noteManager.getNote(validNote.id); assert(currentNote?.content_hash, 'Valid note should have content hash'); const updates: BatchUpdateNoteInput[] = [ { identifier: validNote.id, content: 'Successfully updated content', content_hash: currentNote.content_hash }, { identifier: 'nonexistent/note.md', content: 'This should fail - note does not exist', content_hash: 'dummy-hash' }, { identifier: 'general/another-nonexistent.md', content: 'This should also fail', content_hash: 'dummy-hash' } ]; const result: BatchUpdateResult = await context.noteManager.batchUpdateNotes(updates); assert.strictEqual(result.total, 3); assert.strictEqual(result.successful, 1); assert.strictEqual(result.failed, 2); // Check successful update const successfulResult = result.results.find(r => r.success); assert(successfulResult); assert.strictEqual(successfulResult.input.identifier, validNote.id); // Check failed updates const failedResults = result.results.filter(r => !r.success); assert.strictEqual(failedResults.length, 2); for (const failedResult of failedResults) { assert(failedResult.error); assert(failedResult.error.includes('does not exist')); } }); test('should reject updates with no content or metadata', async () => { const note = await context.noteManager.createNote( 'general', 'Empty Update Test', 'Original content' ); // Get current note to obtain content hash const currentNote = await context.noteManager.getNote(note.id); assert(currentNote?.content_hash, 'Note should have content hash'); const updates: BatchUpdateNoteInput[] = [ { identifier: note.id, content_hash: currentNote.content_hash // No content or metadata - should fail } ]; const result: BatchUpdateResult = await context.noteManager.batchUpdateNotes(updates); assert.strictEqual(result.successful, 0); assert.strictEqual(result.failed, 1); const failedResult = result.results[0]; assert(!failedResult.success); assert(failedResult.error); assert(failedResult.error.includes('Either content or metadata must be provided')); }); test('should handle empty batch updates', async () => { const result: BatchUpdateResult = await context.noteManager.batchUpdateNotes([]); assert.strictEqual(result.total, 0); assert.strictEqual(result.successful, 0); assert.strictEqual(result.failed, 0); assert.strictEqual(result.results.length, 0); }); }); describe('Mixed Batch Operations', () => { test('should handle large batches efficiently', async () => { const largeCreateBatch: BatchCreateNoteInput[] = []; for (let i = 1; i <= 50; i++) { largeCreateBatch.push({ type: 'general', title: `Bulk Note ${i}`, content: `Content for note ${i}`, metadata: { sequence: i, batch: 'large-test' } }); } const createResult: BatchCreateResult = await context.noteManager.batchCreateNotes(largeCreateBatch); assert.strictEqual(createResult.total, 50); assert.strictEqual(createResult.successful, 50); assert.strictEqual(createResult.failed, 0); // Now update half of them - first get their content hashes const updateBatch: BatchUpdateNoteInput[] = []; for (let i = 0; i < 25; i++) { const noteId = createResult.results[i].result!.id; const currentNote = await context.noteManager.getNote(noteId); assert(currentNote?.content_hash, `Note ${noteId} should have content hash`); updateBatch.push({ identifier: noteId, content: `Updated content for note ${i + 1}`, metadata: { sequence: i + 1, batch: 'large-test', processed: true }, content_hash: currentNote.content_hash }); } const updateResult: BatchUpdateResult = await context.noteManager.batchUpdateNotes(updateBatch); assert.strictEqual(updateResult.total, 25); assert.strictEqual(updateResult.successful, 25); assert.strictEqual(updateResult.failed, 0); }); test('should maintain transaction-like behavior for individual operations', async () => { // Even if some operations fail, successful ones should complete const mixedBatch: BatchCreateNoteInput[] = [ { type: 'general', title: 'Transaction Test 1', content: 'This should succeed' }, { type: 'invalid/type', title: 'Transaction Test 2', content: 'This should fail' }, { type: 'general', title: 'Transaction Test 3', content: 'This should also succeed' } ]; const result: BatchCreateResult = await context.noteManager.batchCreateNotes(mixedBatch); assert.strictEqual(result.successful, 2); assert.strictEqual(result.failed, 1); // Verify the successful notes actually exist const successfulResults = result.results.filter(r => r.success); for (const successResult of successfulResults) { const note = await context.noteManager.getNote(successResult.result!.id); assert(note); assert(note.title.includes('Transaction Test')); } }); }); });

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