Skip to main content
Glama
error.test.ts7.8 kB
/** * Tests for error handling middleware */ import { z } from 'zod'; import { MCPAuthError, MCPNotFoundError, MCPToolError, MCPValidationError, asyncHandler, formatError, retry, safeJsonParse, } from '@/middleware/error'; describe('Error Classes', () => { describe('MCPToolError', () => { it('should create error with default values', () => { const error = new MCPToolError('Test error'); expect(error.message).toBe('Test error'); expect(error.code).toBe('TOOL_ERROR'); expect(error.statusCode).toBe(500); expect(error.name).toBe('MCPToolError'); }); it('should create error with custom values', () => { const error = new MCPToolError('Custom error', 'CUSTOM_CODE', 400, { foo: 'bar' }); expect(error.message).toBe('Custom error'); expect(error.code).toBe('CUSTOM_CODE'); expect(error.statusCode).toBe(400); expect(error.data).toEqual({ foo: 'bar' }); }); }); describe('MCPValidationError', () => { it('should create validation error', () => { let zodError: z.ZodError; try { z.object({ field: z.string() }).parse({ field: 42 }); throw new Error('Expected schema parsing to fail'); } catch (error) { zodError = error as z.ZodError; } const error = new MCPValidationError('Validation failed', zodError); expect(error.message).toBe('Validation failed'); expect(error.code).toBe('VALIDATION_ERROR'); expect(error.statusCode).toBe(400); expect(error.errors).toBe(zodError); expect(error.name).toBe('MCPValidationError'); }); }); describe('MCPAuthError', () => { it('should create auth error', () => { const error = new MCPAuthError('Unauthorized'); expect(error.message).toBe('Unauthorized'); expect(error.code).toBe('AUTH_ERROR'); expect(error.statusCode).toBe(401); expect(error.name).toBe('MCPAuthError'); }); }); describe('MCPNotFoundError', () => { it('should create not found error', () => { const error = new MCPNotFoundError('Tool'); expect(error.message).toBe('Tool not found'); expect(error.code).toBe('NOT_FOUND'); expect(error.statusCode).toBe(404); expect(error.name).toBe('MCPNotFoundError'); }); }); }); describe('formatError', () => { it('should format MCPToolError', () => { const error = new MCPToolError('Test error', 'TEST_CODE', 500, { detail: 'test' }); const result = formatError(error); expect(result).toEqual({ success: false, error: { message: 'Test error', code: 'TEST_CODE', data: { detail: 'test' }, }, }); }); it('should format ZodError', () => { let zodError: z.ZodError; try { z.object({ field: z.string() }).parse({ field: 42 }); throw new Error('Expected schema parsing to fail'); } catch (error) { zodError = error as z.ZodError; } const result = formatError(zodError); expect(result).toEqual({ success: false, error: { message: 'Validation failed', code: 'VALIDATION_ERROR', data: zodError.issues, }, }); }); it('should format generic Error', () => { const error = new Error('Generic error'); const result = formatError(error); expect(result).toEqual({ success: false, error: { message: 'Generic error', code: 'INTERNAL_ERROR', }, }); }); it('should format unknown error', () => { const result = formatError('string error'); expect(result).toEqual({ success: false, error: { message: 'An unknown error occurred', code: 'UNKNOWN_ERROR', }, }); }); }); describe('asyncHandler', () => { it('should handle successful async function', async () => { const handler = asyncHandler(async () => { return 'success'; }); const result = await handler(); expect(result).toBe('success'); }); it('should handle async function with parameters', async () => { const handler = asyncHandler<number>(async (...args: unknown[]) => { const [a, b] = args as [number, number]; return a + b; }); const result = await handler(2, 3); expect(result).toBe(5); }); it('should propagate errors', async () => { const handler = asyncHandler(async () => { throw new Error('Test error'); }); await expect(handler()).rejects.toThrow('Test error'); }); }); describe('safeJsonParse', () => { it('should parse valid JSON', () => { const result = safeJsonParse('{"key": "value"}'); expect(result).toEqual({ key: 'value' }); }); it('should return null for invalid JSON', () => { const result = safeJsonParse('invalid json'); expect(result).toBeNull(); }); it('should parse complex JSON structures', () => { const json = JSON.stringify({ array: [1, 2, 3], nested: { foo: 'bar' }, bool: true, null: null, }); const result = safeJsonParse(json); expect(result).toEqual({ array: [1, 2, 3], nested: { foo: 'bar' }, bool: true, null: null, }); }); }); describe('retry', () => { it('should succeed on first try', async () => { let attempts = 0; const result = await retry(async () => { attempts++; return 'success'; }); expect(result).toBe('success'); expect(attempts).toBe(1); }); it('should retry on failure and eventually succeed', async () => { let attempts = 0; const result = await retry( async () => { attempts++; if (attempts < 3) { throw new Error('Fail'); } return 'success'; }, { retries: 3, delay: 10 } ); expect(result).toBe('success'); expect(attempts).toBe(3); }); it('should throw after max retries', async () => { let attempts = 0; await expect( retry( async () => { attempts++; throw new Error('Always fails'); }, { retries: 3, delay: 10 } ) ).rejects.toThrow('Always fails'); expect(attempts).toBe(3); }); it('should use exponential backoff', async () => { const delays: number[] = []; let lastTime = Date.now(); await retry( async () => { const now = Date.now(); if (lastTime !== now) { delays.push(now - lastTime); } lastTime = now; if (delays.length < 2) { throw new Error('Retry'); } return 'success'; }, { retries: 3, delay: 10, exponentialBackoff: true } ); // Second retry should have longer delay than first expect(delays.length).toBeGreaterThanOrEqual(2); if (delays.length >= 2) { const first = delays[0] as number; const second = delays[1] as number; const toleranceMs = 10; expect(second + toleranceMs).toBeGreaterThanOrEqual(first); } }); it('should use fixed delay without exponential backoff', async () => { const delays: number[] = []; let lastTime = Date.now(); await retry( async () => { const now = Date.now(); if (lastTime !== now) { delays.push(now - lastTime); } lastTime = now; if (delays.length < 2) { throw new Error('Retry'); } return 'success'; }, { retries: 3, delay: 10, exponentialBackoff: false } ); // All delays should be similar (within reasonable tolerance) expect(delays.length).toBeGreaterThanOrEqual(2); if (delays.length >= 2) { const avgDelay = delays.reduce((a, b) => a + b, 0) / delays.length; delays.forEach((delay) => { expect(Math.abs(delay - avgDelay)).toBeLessThan(50); // Allow 50ms tolerance }); } }); });

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/Daghis/teamcity-mcp'

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