Skip to main content
Glama
error.test.ts7.55 kB
/** * Error handling middleware tests */ import { z } from 'zod'; import { MCPAuthError, MCPNotFoundError, MCPToolError, MCPValidationError, asyncHandler, formatError, retry, safeJsonParse, } from './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'); expect(error.data).toBeUndefined(); }); it('should create error with custom values', () => { const testData = { detail: 'Custom data' }; const error = new MCPToolError('Custom error', 'CUSTOM_CODE', 400, testData); expect(error.message).toBe('Custom error'); expect(error.code).toBe('CUSTOM_CODE'); expect(error.statusCode).toBe(400); expect(error.data).toEqual(testData); }); it('should capture stack trace', () => { const error = new MCPToolError('Test error'); expect(error.stack).toBeDefined(); }); }); describe('MCPValidationError', () => { it('should create validation error', () => { let zodError: z.ZodError; try { z.string().parse(123); 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.name).toBe('MCPValidationError'); expect(error.data).toEqual(zodError.issues); }); }); describe('MCPAuthError', () => { it('should create auth error', () => { const error = new MCPAuthError('Authentication failed'); expect(error.message).toBe('Authentication failed'); 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('User'); expect(error.message).toBe('User not found'); expect(error.code).toBe('NOT_FOUND'); expect(error.statusCode).toBe(404); expect(error.name).toBe('MCPNotFoundError'); }); }); }); describe('formatError', () => { it('should format MCPToolError correctly', () => { const error = new MCPToolError('Tool error', 'CUSTOM_CODE', 400, { key: 'value' }); const result = formatError(error); expect(result).toEqual({ success: false, error: { message: 'Tool error', code: 'CUSTOM_CODE', data: { key: 'value' }, }, }); }); it('should format ZodError correctly', () => { let zodError: z.ZodError; try { z.object({ field: z.string() }).parse({ field: 123 }); 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 correctly', () => { 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 correctly', () => { const result = formatError('string error'); expect(result).toEqual({ success: false, error: { message: 'An unknown error occurred', code: 'UNKNOWN_ERROR', }, }); }); it('should format null/undefined error correctly', () => { expect(formatError(null)).toEqual({ success: false, error: { message: 'An unknown error occurred', code: 'UNKNOWN_ERROR', }, }); expect(formatError(undefined)).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 (...args: unknown[]) => { const value = args[0] as number; return value * 2; }); const result = await handler(5); expect(result).toBe(10); }); it('should catch and re-throw errors', async () => { const testError = new Error('Test error'); const handler = asyncHandler(async () => { throw testError; }); await expect(handler()).rejects.toThrow('Test error'); }); it('should handle non-Error throws', async () => { const handler = asyncHandler(async () => { throw new Error('String error'); }); await expect(handler()).rejects.toThrow('String 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 return typed result', () => { interface TestType { name: string; age: number; } const result = safeJsonParse<TestType>('{"name": "John", "age": 30}'); expect(result).toEqual({ name: 'John', age: 30 }); }); }); describe('retry', () => { it('should succeed on first try', async () => { const fn = jest.fn().mockResolvedValue('success'); const result = await retry(fn); expect(result).toBe('success'); expect(fn).toHaveBeenCalledTimes(1); }); it('should retry on failure and eventually succeed', async () => { const fn = jest .fn() .mockRejectedValueOnce(new Error('First fail')) .mockRejectedValueOnce(new Error('Second fail')) .mockResolvedValue('success'); const result = await retry(fn, { retries: 3, delay: 10 }); expect(result).toBe('success'); expect(fn).toHaveBeenCalledTimes(3); }); it('should throw last error after all retries', async () => { const finalError = new Error('Final error'); const fn = jest.fn().mockRejectedValue(finalError); await expect(retry(fn, { retries: 2, delay: 10 })).rejects.toThrow('Final error'); expect(fn).toHaveBeenCalledTimes(2); }); it('should use exponential backoff', async () => { const fn = jest.fn().mockRejectedValue(new Error('Always fail')); const startTime = Date.now(); try { await retry(fn, { retries: 3, delay: 10, exponentialBackoff: true }); } catch (error) { // Expected to fail } const elapsed = Date.now() - startTime; // Should wait at least 10 + 20 = 30ms (exponential backoff) expect(elapsed).toBeGreaterThanOrEqual(30); expect(fn).toHaveBeenCalledTimes(3); }); it('should use constant delay when exponentialBackoff is false', async () => { const fn = jest.fn().mockRejectedValue(new Error('Always fail')); try { await retry(fn, { retries: 3, delay: 10, exponentialBackoff: false }); } catch (error) { // Expected to fail } expect(fn).toHaveBeenCalledTimes(3); }); });

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