/**
* Unit tests for errors utility
*/
import { describe, it, expect } from 'vitest';
import {
formatError,
formatHttpError,
isMCPError,
toMCPError,
formatToolError,
} from '../../../src/utils/errors.js';
import { MCPError, ErrorCode } from '../../../src/types/mcp-tools.js';
describe('errors utility', () => {
describe('formatError', () => {
it('should create error with default message', () => {
const error = formatError('MISSING_API_KEY');
expect(error.code).toBe('MISSING_API_KEY');
expect(error.message).toContain('Missing API key');
expect(error.suggestion).toBeDefined();
});
it('should use custom message when provided', () => {
const error = formatError('INVALID_URL', 'Custom error message');
expect(error.code).toBe('INVALID_URL');
expect(error.message).toBe('Custom error message');
});
it('should add retry_after when provided', () => {
const error = formatError('RATE_LIMITED', undefined, 30);
expect(error.code).toBe('RATE_LIMITED');
expect(error.retry_after).toBe(30);
expect(error.message).toContain('30 seconds');
});
it('should not add retry_after when zero or negative', () => {
const error = formatError('RATE_LIMITED', undefined, 0);
expect(error.retry_after).toBeUndefined();
});
it('should format all error codes correctly', () => {
const errorCodes: ErrorCode[] = [
'MISSING_API_KEY',
'INVALID_API_KEY',
'INVALID_URL',
'UNSUPPORTED_FORMAT',
'FILE_TOO_LARGE',
'RATE_LIMITED',
'SERVICE_UNAVAILABLE',
'JOB_NOT_FOUND',
'JOB_FAILED',
'UNKNOWN_ERROR',
];
errorCodes.forEach((code) => {
const error = formatError(code);
expect(error.code).toBe(code);
expect(error.message).toBeTruthy();
expect(error.suggestion).toBeTruthy();
});
});
});
describe('formatHttpError', () => {
it('should map 401 to INVALID_API_KEY', () => {
const error = formatHttpError(401);
expect(error.code).toBe('INVALID_API_KEY');
});
it('should map 400 to INVALID_URL by default', () => {
const error = formatHttpError(400);
expect(error.code).toBe('INVALID_URL');
});
it('should map 400 with format message to UNSUPPORTED_FORMAT', () => {
const error = formatHttpError(400, 'Unsupported format');
expect(error.code).toBe('UNSUPPORTED_FORMAT');
});
it('should map 413 to FILE_TOO_LARGE', () => {
const error = formatHttpError(413);
expect(error.code).toBe('FILE_TOO_LARGE');
});
it('should map 429 to RATE_LIMITED', () => {
const error = formatHttpError(429, undefined, 60);
expect(error.code).toBe('RATE_LIMITED');
expect(error.retry_after).toBe(60);
});
it('should map 404 to JOB_NOT_FOUND', () => {
const error = formatHttpError(404);
expect(error.code).toBe('JOB_NOT_FOUND');
});
it('should map 500 to JOB_FAILED', () => {
const error = formatHttpError(500);
expect(error.code).toBe('JOB_FAILED');
});
it('should map 502, 503, 504 to SERVICE_UNAVAILABLE', () => {
expect(formatHttpError(502).code).toBe('SERVICE_UNAVAILABLE');
expect(formatHttpError(503).code).toBe('SERVICE_UNAVAILABLE');
expect(formatHttpError(504).code).toBe('SERVICE_UNAVAILABLE');
});
it('should map unknown status to UNKNOWN_ERROR', () => {
const error = formatHttpError(418);
expect(error.code).toBe('UNKNOWN_ERROR');
});
});
describe('isMCPError', () => {
it('should return true for valid MCPError', () => {
const error: MCPError = {
code: 'INVALID_URL',
message: 'Invalid URL',
suggestion: 'Check your URL',
};
expect(isMCPError(error)).toBe(true);
});
it('should return true for minimal MCPError', () => {
const error = {
code: 'UNKNOWN_ERROR',
message: 'Something went wrong',
};
expect(isMCPError(error)).toBe(true);
});
it('should return false for null', () => {
expect(isMCPError(null)).toBe(false);
});
it('should return false for undefined', () => {
expect(isMCPError(undefined)).toBe(false);
});
it('should return false for string', () => {
expect(isMCPError('error')).toBe(false);
});
it('should return false for object without code', () => {
expect(isMCPError({ message: 'error' })).toBe(false);
});
it('should return false for object without message', () => {
expect(isMCPError({ code: 'ERROR' })).toBe(false);
});
it('should return false for object with non-string code', () => {
expect(isMCPError({ code: 123, message: 'error' })).toBe(false);
});
it('should return false for object with non-string message', () => {
expect(isMCPError({ code: 'ERROR', message: 123 })).toBe(false);
});
});
describe('toMCPError', () => {
it('should return same error if already MCPError', () => {
const original: MCPError = {
code: 'INVALID_URL',
message: 'Invalid URL',
};
const result = toMCPError(original);
expect(result).toBe(original);
});
it('should convert Error to MCPError', () => {
const error = new Error('Something went wrong');
const result = toMCPError(error);
expect(result.code).toBe('UNKNOWN_ERROR');
expect(result.message).toBe('Something went wrong');
});
it('should convert string to MCPError', () => {
const result = toMCPError('Error message');
expect(result.code).toBe('UNKNOWN_ERROR');
expect(result.message).toBe('Error message');
});
it('should convert number to MCPError', () => {
const result = toMCPError(42);
expect(result.code).toBe('UNKNOWN_ERROR');
expect(result.message).toBe('42');
});
it('should convert object to MCPError', () => {
const result = toMCPError({ foo: 'bar' });
expect(result.code).toBe('UNKNOWN_ERROR');
});
});
describe('formatToolError', () => {
it('should format error for MCP response', () => {
const error: MCPError = {
code: 'INVALID_URL',
message: 'Invalid URL provided',
suggestion: 'Use a valid HTTP(S) URL',
};
const result = formatToolError(error);
expect(result.isError).toBe(true);
expect(result.content).toHaveLength(1);
expect(result.content[0].type).toBe('text');
expect(result.content[0].text).toContain('Invalid URL provided');
expect(result.content[0].text).toContain('Use a valid HTTP(S) URL');
});
it('should include retry_after in formatted output', () => {
const error: MCPError = {
code: 'RATE_LIMITED',
message: 'Rate limited',
suggestion: 'Wait and retry',
retry_after: 60,
};
const result = formatToolError(error);
expect(result.content[0].text).toContain('60 seconds');
});
it('should handle error without suggestion', () => {
const error: MCPError = {
code: 'UNKNOWN_ERROR',
message: 'Unknown error',
};
const result = formatToolError(error);
expect(result.isError).toBe(true);
expect(result.content[0].text).toContain('Unknown error');
});
});
});