Skip to main content
Glama
bulkOperations.test.ts7.58 kB
/** * Tests for bulkOperations utility */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { executeBulkOperation, processBulkQueries, createErrorResult, } from '../../src/utils/bulkOperations.js'; import { ERROR_CODES, type ErrorCode } from '../../src/errors/errorCodes.js'; interface TestQuery { path: string; pattern?: string; } interface TestResult { status: 'hasResults' | 'empty' | 'error'; path?: string; data?: string; errorCode?: ErrorCode; hints?: readonly string[]; } describe('bulkOperations', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('processBulkQueries', () => { it('should process all queries in parallel', async () => { const queries: TestQuery[] = [ { path: '/path1' }, { path: '/path2' }, { path: '/path3' }, ]; const processor = vi.fn( async (query: TestQuery): Promise<TestResult> => ({ status: 'hasResults', path: query.path, data: `data for ${query.path}`, }) ); const results = await processBulkQueries(queries, processor); expect(processor).toHaveBeenCalledTimes(3); expect(results).toHaveLength(3); expect(results[0].status).toBe('hasResults'); expect(results[1].status).toBe('hasResults'); expect(results[2].status).toBe('hasResults'); }); it('should continue processing on error by default', async () => { const queries: TestQuery[] = [ { path: '/path1' }, { path: '/fail' }, { path: '/path3' }, ]; const processor = vi.fn(async (query: TestQuery): Promise<TestResult> => { if (query.path === '/fail') { throw new Error('Processing failed'); } return { status: 'hasResults', path: query.path, }; }); const results = await processBulkQueries(queries, processor); expect(results).toHaveLength(3); expect(results[0].status).toBe('hasResults'); expect(results[1].status).toBe('error'); expect(results[2].status).toBe('hasResults'); }); it('should throw on error when continueOnError is false', async () => { const queries: TestQuery[] = [ { path: '/path1' }, { path: '/fail' }, { path: '/path3' }, ]; const processor = vi.fn(async (query: TestQuery): Promise<TestResult> => { if (query.path === '/fail') { throw new Error('Processing failed'); } return { status: 'hasResults', path: query.path, }; }); await expect( processBulkQueries(queries, processor, { continueOnError: false }) ).rejects.toThrow('Processing failed'); }); it('should handle empty query array', async () => { const processor = vi.fn( async (_query: TestQuery): Promise<TestResult> => ({ status: 'hasResults', }) ); const results = await processBulkQueries([], processor); expect(results).toHaveLength(0); expect(processor).not.toHaveBeenCalled(); }); it('should preserve query order in results', async () => { const queries: TestQuery[] = [ { path: '/a' }, { path: '/b' }, { path: '/c' }, ]; const processor = vi.fn( async (query: TestQuery): Promise<TestResult> => ({ status: 'hasResults', path: query.path, }) ); const results = await processBulkQueries(queries, processor); expect(results[0].path).toBe('/a'); expect(results[1].path).toBe('/b'); expect(results[2].path).toBe('/c'); }); }); describe('createErrorResult', () => { it('should create error result with default error code', () => { const result = createErrorResult<TestResult>(new Error('Test error')); expect(result.status).toBe('error'); expect(result.errorCode).toBe(ERROR_CODES.QUERY_EXECUTION_FAILED); }); it('should include base data in error result', () => { const baseData = { path: '/test/path' }; const result = createErrorResult<TestResult>( new Error('Test error'), baseData ); expect(result.status).toBe('error'); expect(result.path).toBe('/test/path'); }); it('should handle string errors', () => { const result = createErrorResult<TestResult>('String error'); expect(result.status).toBe('error'); expect(result.errorCode).toBe(ERROR_CODES.QUERY_EXECUTION_FAILED); }); it('should handle undefined error', () => { const result = createErrorResult<TestResult>(undefined); expect(result.status).toBe('error'); }); }); describe('executeBulkOperation', () => { it('should execute bulk operation and return CallToolResult', async () => { const queries: TestQuery[] = [{ path: '/path1' }, { path: '/path2' }]; const processor = vi.fn( async (query: TestQuery): Promise<TestResult> => ({ status: 'hasResults', path: query.path, data: 'test data', }) ); const result = await executeBulkOperation(queries, processor, { toolName: 'local_ripgrep', }); expect(result.isError).toBe(false); expect(result.content).toHaveLength(1); expect(result.content[0].type).toBe('text'); }); it('should handle mixed success and error results', async () => { const queries: TestQuery[] = [{ path: '/success' }, { path: '/fail' }]; const processor = vi.fn(async (query: TestQuery): Promise<TestResult> => { if (query.path === '/fail') { throw new Error('Failed'); } return { status: 'hasResults', path: query.path, }; }); const result = await executeBulkOperation(queries, processor, { toolName: 'local_ripgrep', }); // Should not be an error overall (individual queries can fail) expect(result.content).toHaveLength(1); const content = result.content[0] as { type: 'text'; text: string }; expect(content.text).toContain('results:'); }); it('should format results as YAML', async () => { const queries: TestQuery[] = [{ path: '/test' }]; const processor = vi.fn( async (): Promise<TestResult> => ({ status: 'hasResults', path: '/test', hints: ['hint1', 'hint2'], }) ); const result = await executeBulkOperation(queries, processor, { toolName: 'local_ripgrep', }); const content = result.content[0] as { type: 'text'; text: string }; expect(content.text).toContain('results:'); expect(content.text).toContain('status:'); expect(content.text).toContain('hasResults'); }); it('should return error when response exceeds token limit', async () => { const queries: TestQuery[] = [{ path: '/test' }]; // Create a processor that returns a very large result const processor = vi.fn( async (): Promise<TestResult> => ({ status: 'hasResults', path: '/test', // Generate huge data that exceeds token limit data: 'x'.repeat(500000), }) ); const result = await executeBulkOperation(queries, processor, { toolName: 'local_ripgrep', }); expect(result.isError).toBe(true); const content = result.content[0] as { type: 'text'; text: string }; expect(content.text).toContain(ERROR_CODES.RESPONSE_TOO_LARGE); }); }); });

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/bgauryy/octocode-mcp'

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