Skip to main content
Glama
concurrency.test.ts8.81 kB
/** * Concurrency and Edge Case Tests * Tests for concurrent tool executions, race conditions, and error edge cases */ import { executeTool } from '../../src/tools/registry.js'; import { createTestContext, cleanupTestContext } from '../test-helpers.js'; import type { ToolExecutionContext } from '../../src/tools/types.js'; describe('Concurrency Tests', () => { let context: ToolExecutionContext; beforeEach(() => { context = createTestContext(); }); afterEach(() => { cleanupTestContext(context); }); describe('Concurrent Note Creation', () => { it('여러 노트를 동시에 생성해도 모든 노트가 고유한 UID를 가져야 함', async () => { const promises = Array.from({ length: 10 }, (_, i) => executeTool('create_note', { title: `Concurrent Note ${i}`, content: `Content ${i}`, category: 'Resources', }, context) ); const results = await Promise.all(promises); // 모든 노트가 성공적으로 생성되어야 함 const uids = results.map(result => { expect(result.isError).toBeFalsy(); return (result.content[0].text as string).match(/\*\*ID\*\*: (\S+)/)?.[1]; }); // 모든 UID가 고유해야 함 const uniqueUids = new Set(uids); expect(uniqueUids.size).toBe(10); }); it('동시 검색 요청이 서로 간섭하지 않아야 함', async () => { // 먼저 노트 생성 await executeTool('create_note', { title: 'Search Test Note', content: 'This is test content for concurrent search', category: 'Resources', tags: ['search', 'test'], }, context); // 동시에 여러 검색 수행 const searchPromises = [ executeTool('search_memory', { query: 'test' }, context), executeTool('search_memory', { query: 'content' }, context), executeTool('search_memory', { query: 'search' }, context), executeTool('search_memory', { query: 'concurrent' }, context), ]; const results = await Promise.all(searchPromises); // 모든 검색이 성공해야 함 results.forEach(result => { expect(result.isError).toBeFalsy(); }); }); it('동시 CRUD 작업이 데이터를 손상시키지 않아야 함', async () => { // 노트 생성 const createPromises = Array.from({ length: 5 }, (_, i) => executeTool('create_note', { title: `CRUD Test ${i}`, content: `Original content ${i}`, category: 'Resources', }, context) ); const createResults = await Promise.all(createPromises); const uids = createResults.map(result => (result.content[0].text as string).match(/\*\*ID\*\*: (\S+)/)?.[1] ); // 동시에 일부 업데이트, 일부 읽기 const mixedPromises = [ executeTool('update_note', { uid: uids[0]!, title: 'Updated Title 0', }, context), executeTool('read_note', { uid: uids[1]! }, context), executeTool('update_note', { uid: uids[2]!, content: 'Updated content 2', }, context), executeTool('read_note', { uid: uids[3]! }, context), executeTool('list_notes', {}, context), ]; const results = await Promise.all(mixedPromises); // 모든 작업이 성공해야 함 results.forEach(result => { expect(result.isError).toBeFalsy(); }); // 업데이트된 노트 확인 const readResult = await executeTool('read_note', { uid: uids[0]! }, context); expect(readResult.content[0].text).toContain('Updated Title 0'); }); }); describe('Error Edge Cases', () => { it('존재하지 않는 노트 업데이트 시 적절한 에러를 반환해야 함', async () => { await expect(executeTool('update_note', { uid: 'non-existent-uid-12345', title: 'New Title', }, context)).rejects.toThrow(); }); it('빈 검색어로 검색 시 스키마 검증 에러를 반환해야 함', async () => { await expect(executeTool('search_memory', { query: '', }, context)).rejects.toThrow(); }); it('잘못된 카테고리로 노트 생성 시 에러를 반환해야 함', async () => { await expect(executeTool('create_note', { title: 'Test', content: 'Test', category: 'InvalidCategory' as any, }, context)).rejects.toThrow(); }); it('이미 삭제된 노트를 다시 삭제할 때 에러를 반환해야 함', async () => { // 노트 생성 const createResult = await executeTool('create_note', { title: 'To Be Deleted', content: 'This note will be deleted', category: 'Resources', }, context); const uid = (createResult.content[0].text as string).match(/\*\*ID\*\*: (\S+)/)?.[1]; // 첫 번째 삭제 await executeTool('delete_note', { uid: uid!, confirm: true, }, context); // 두 번째 삭제 시도 await expect(executeTool('delete_note', { uid: uid!, confirm: true, }, context)).rejects.toThrow(); }); it('limit가 범위를 벗어날 때 에러를 반환해야 함', async () => { await expect(executeTool('search_memory', { query: 'test', limit: 0, }, context)).rejects.toThrow(); await expect(executeTool('search_memory', { query: 'test', limit: 101, }, context)).rejects.toThrow(); }); it('음수 offset이 거부되어야 함', async () => { await expect(executeTool('search_memory', { query: 'test', offset: -1, }, context)).rejects.toThrow(); }); }); describe('Race Condition Handling', () => { it('동일 노트를 동시에 업데이트해도 데이터 손실이 없어야 함', async () => { // 노트 생성 const createResult = await executeTool('create_note', { title: 'Race Test', content: 'Original content', category: 'Resources', tags: ['original'], }, context); const uid = (createResult.content[0].text as string).match(/\*\*ID\*\*: (\S+)/)?.[1]; // 동시에 다른 필드 업데이트 const updatePromises = [ executeTool('update_note', { uid: uid!, title: 'Updated Title', }, context), executeTool('update_note', { uid: uid!, tags: ['updated'], }, context), ]; const results = await Promise.all(updatePromises); // 모든 업데이트가 성공해야 함 (마지막 업데이트가 우선) results.forEach(result => { expect(result.isError).toBeFalsy(); }); // 최종 상태 확인 - 최소한 하나의 업데이트는 반영되어야 함 const readResult = await executeTool('read_note', { uid: uid! }, context); const text = readResult.content[0].text as string; // title이나 tags 중 하나는 업데이트되어야 함 const titleUpdated = text.includes('Updated Title'); const tagsUpdated = text.includes('updated'); expect(titleUpdated || tagsUpdated).toBe(true); }); it('노트 생성 중 동일 제목으로 여러 노트를 생성해도 모두 성공해야 함', async () => { const promises = Array.from({ length: 5 }, () => executeTool('create_note', { title: 'Same Title', content: 'Same content', category: 'Resources', }, context) ); const results = await Promise.all(promises); // 모든 노트가 생성되어야 함 results.forEach(result => { expect(result.isError).toBeFalsy(); }); // list_notes로 확인 - 전체 개수가 5개 이상이어야 함 const listResult = await executeTool('list_notes', {}, context); const noteCount = (listResult.content[0].text as string).match(/\*\*전체\*\*: (\d+)개/)?.[1]; expect(noteCount).toBeDefined(); expect(parseInt(noteCount!)).toBeGreaterThanOrEqual(5); }); }); describe('Stress Testing', () => { it('많은 수의 동시 요청을 처리할 수 있어야 함', async () => { const requests = 50; const promises = Array.from({ length: requests }, (_, i) => executeTool('list_notes', {}, context) ); const start = Date.now(); const results = await Promise.all(promises); const duration = Date.now() - start; // 모든 요청이 성공해야 함 results.forEach(result => { expect(result.isError).toBeFalsy(); }); // 합리적인 시간 내에 완료되어야 함 (50개 요청에 10초 이내) expect(duration).toBeLessThan(10000); }, 15000); }); });

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/inchan/memory-mcp'

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