Skip to main content
Glama
DynamicEndpoints

ESPN MCP Server

errors.test.ts7.37 kB
/** * Tests for error handling utilities */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { ESPNAPIError, ValidationError, TimeoutError, isRetryableError, withRetry, CircuitBreaker, getUserFriendlyMessage, } from '../../src/utils/errors.js'; describe('Custom Errors', () => { describe('ESPNAPIError', () => { it('should create error with correct properties', () => { const error = new ESPNAPIError('API failed', 500, '/test', true); expect(error.message).toBe('API failed'); expect(error.statusCode).toBe(500); expect(error.endpoint).toBe('/test'); expect(error.retryable).toBe(true); expect(error.name).toBe('ESPNAPIError'); }); it('should identify retryable status codes', () => { expect(ESPNAPIError.isRetryable(500)).toBe(true); expect(ESPNAPIError.isRetryable(503)).toBe(true); expect(ESPNAPIError.isRetryable(429)).toBe(true); expect(ESPNAPIError.isRetryable(404)).toBe(false); expect(ESPNAPIError.isRetryable(400)).toBe(false); }); it('should serialize to JSON', () => { const error = new ESPNAPIError('API failed', 500); const json = error.toJSON(); expect(json).toHaveProperty('name', 'ESPNAPIError'); expect(json).toHaveProperty('message', 'API failed'); expect(json).toHaveProperty('statusCode', 500); }); }); describe('ValidationError', () => { it('should create validation error with field info', () => { const error = new ValidationError('Invalid date', 'date', '2025-13-01'); expect(error.message).toBe('Invalid date'); expect(error.field).toBe('date'); expect(error.value).toBe('2025-13-01'); expect(error.statusCode).toBe(400); }); }); describe('TimeoutError', () => { it('should create timeout error with duration', () => { const error = new TimeoutError('Request timed out', 5000); expect(error.message).toBe('Request timed out'); expect(error.timeout).toBe(5000); expect(error.statusCode).toBe(408); }); }); }); describe('Error detection', () => { it('should identify retryable errors', () => { const retryableAPIError = new ESPNAPIError('Server error', 500, '/test', true); const nonRetryableAPIError = new ESPNAPIError('Not found', 404, '/test', false); const timeoutError = new TimeoutError('Timeout', 5000); const validationError = new ValidationError('Invalid'); expect(isRetryableError(retryableAPIError)).toBe(true); expect(isRetryableError(nonRetryableAPIError)).toBe(false); expect(isRetryableError(timeoutError)).toBe(true); expect(isRetryableError(validationError)).toBe(false); }); }); describe('withRetry', () => { it('should succeed on first try', async () => { const fn = vi.fn().mockResolvedValue('success'); const result = await withRetry(fn, { maxRetries: 3 }); expect(result).toBe('success'); expect(fn).toHaveBeenCalledTimes(1); }); it('should retry on retryable errors', async () => { const fn = vi.fn() .mockRejectedValueOnce(new TimeoutError('Timeout', 5000)) .mockRejectedValueOnce(new TimeoutError('Timeout', 5000)) .mockResolvedValueOnce('success'); const result = await withRetry(fn, { maxRetries: 3, baseDelay: 10, }); expect(result).toBe('success'); expect(fn).toHaveBeenCalledTimes(3); }); it('should not retry on non-retryable errors', async () => { const fn = vi.fn().mockRejectedValue(new ValidationError('Invalid')); await expect(withRetry(fn, { maxRetries: 3 })).rejects.toThrow('Invalid'); expect(fn).toHaveBeenCalledTimes(1); }); it('should exhaust retries and throw last error', async () => { const fn = vi.fn().mockRejectedValue(new TimeoutError('Timeout', 5000)); await expect(withRetry(fn, { maxRetries: 2, baseDelay: 10, })).rejects.toThrow('Timeout'); expect(fn).toHaveBeenCalledTimes(3); // Initial + 2 retries }); it('should call onRetry callback', async () => { const fn = vi.fn() .mockRejectedValueOnce(new TimeoutError('Timeout', 5000)) .mockResolvedValueOnce('success'); const onRetry = vi.fn(); await withRetry(fn, { maxRetries: 2, baseDelay: 10, onRetry, }); expect(onRetry).toHaveBeenCalledTimes(1); expect(onRetry).toHaveBeenCalledWith(expect.any(TimeoutError), 1); }); }); describe('CircuitBreaker', () => { it('should start in closed state', () => { const cb = new CircuitBreaker('test'); expect(cb.getState()).toBe('closed'); }); it('should execute function when closed', async () => { const cb = new CircuitBreaker('test'); const fn = vi.fn().mockResolvedValue('success'); const result = await cb.execute(fn); expect(result).toBe('success'); expect(fn).toHaveBeenCalledTimes(1); }); it('should open after threshold failures', async () => { const cb = new CircuitBreaker('test', { failureThreshold: 3 }); const fn = vi.fn().mockRejectedValue(new Error('fail')); // Fail 3 times for (let i = 0; i < 3; i++) { try { await cb.execute(fn); } catch (error) { // Expected } } expect(cb.getState()).toBe('open'); }); it('should reject immediately when open', async () => { const cb = new CircuitBreaker('test', { failureThreshold: 1, timeout: 1000, }); const fn = vi.fn().mockRejectedValue(new Error('fail')); // Trigger open try { await cb.execute(fn); } catch (error) { // Expected } // Should reject immediately await expect(cb.execute(fn)).rejects.toThrow('Circuit breaker'); expect(fn).toHaveBeenCalledTimes(1); // Only first call }); it('should reset stats on success', async () => { const cb = new CircuitBreaker('test', { failureThreshold: 3 }); const fn = vi.fn() .mockRejectedValueOnce(new Error('fail')) .mockResolvedValue('success'); // One failure try { await cb.execute(fn); } catch (error) { // Expected } // Success should reset await cb.execute(fn); expect(cb.getStats().failureCount).toBe(0); }); }); describe('getUserFriendlyMessage', () => { it('should return friendly message for API errors', () => { const error404 = new ESPNAPIError('Not found', 404); const error429 = new ESPNAPIError('Rate limit', 429); const error500 = new ESPNAPIError('Server error', 500); expect(getUserFriendlyMessage(error404)).toContain('could not be found'); expect(getUserFriendlyMessage(error429)).toContain('Rate limit exceeded'); expect(getUserFriendlyMessage(error500)).toContain('experiencing issues'); }); it('should return friendly message for timeout errors', () => { const error = new TimeoutError('Timeout', 5000); expect(getUserFriendlyMessage(error)).toContain('timed out'); }); it('should return friendly message for validation errors', () => { const error = new ValidationError('Invalid date'); expect(getUserFriendlyMessage(error)).toContain('Invalid input'); }); });

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/DynamicEndpoints/espn-mcp'

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