Files
n8n-mcp/tests/error-handler.test.ts
czlonkowski 1f8140c45c Implement n8n-MCP integration
This commit adds a complete integration between n8n workflow automation and the Model Context Protocol (MCP):

Features:
- MCP server that exposes n8n workflows as tools, resources, and prompts
- Custom n8n node for connecting to MCP servers from workflows
- Bidirectional bridge for data format conversion
- Token-based authentication and credential management
- Comprehensive error handling and logging
- Full test coverage for core components

Infrastructure:
- TypeScript/Node.js project setup with proper build configuration
- Docker support with multi-stage builds
- Development and production docker-compose configurations
- Installation script for n8n custom node deployment

Documentation:
- Detailed README with usage examples and API reference
- Environment configuration templates
- Troubleshooting guide

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-07 15:43:02 +00:00

188 lines
5.5 KiB
TypeScript

import {
MCPError,
N8NConnectionError,
AuthenticationError,
ValidationError,
ToolNotFoundError,
ResourceNotFoundError,
handleError,
withErrorHandling,
} from '../src/utils/error-handler';
import { logger } from '../src/utils/logger';
// Mock the logger
jest.mock('../src/utils/logger', () => ({
logger: {
error: jest.fn(),
},
}));
describe('Error Classes', () => {
describe('MCPError', () => {
it('should create error with all properties', () => {
const error = new MCPError('Test error', 'TEST_CODE', 400, { field: 'value' });
expect(error.message).toBe('Test error');
expect(error.code).toBe('TEST_CODE');
expect(error.statusCode).toBe(400);
expect(error.data).toEqual({ field: 'value' });
expect(error.name).toBe('MCPError');
});
});
describe('N8NConnectionError', () => {
it('should create connection error with correct code', () => {
const error = new N8NConnectionError('Connection failed');
expect(error.message).toBe('Connection failed');
expect(error.code).toBe('N8N_CONNECTION_ERROR');
expect(error.statusCode).toBe(503);
expect(error.name).toBe('N8NConnectionError');
});
});
describe('AuthenticationError', () => {
it('should create auth error with default message', () => {
const error = new AuthenticationError();
expect(error.message).toBe('Authentication failed');
expect(error.code).toBe('AUTH_ERROR');
expect(error.statusCode).toBe(401);
});
it('should accept custom message', () => {
const error = new AuthenticationError('Invalid token');
expect(error.message).toBe('Invalid token');
});
});
describe('ValidationError', () => {
it('should create validation error', () => {
const error = new ValidationError('Invalid input', { field: 'email' });
expect(error.message).toBe('Invalid input');
expect(error.code).toBe('VALIDATION_ERROR');
expect(error.statusCode).toBe(400);
expect(error.data).toEqual({ field: 'email' });
});
});
describe('ToolNotFoundError', () => {
it('should create tool not found error', () => {
const error = new ToolNotFoundError('myTool');
expect(error.message).toBe("Tool 'myTool' not found");
expect(error.code).toBe('TOOL_NOT_FOUND');
expect(error.statusCode).toBe(404);
});
});
describe('ResourceNotFoundError', () => {
it('should create resource not found error', () => {
const error = new ResourceNotFoundError('workflow://123');
expect(error.message).toBe("Resource 'workflow://123' not found");
expect(error.code).toBe('RESOURCE_NOT_FOUND');
expect(error.statusCode).toBe(404);
});
});
});
describe('handleError', () => {
it('should return MCPError instances as-is', () => {
const mcpError = new ValidationError('Test');
const result = handleError(mcpError);
expect(result).toBe(mcpError);
});
it('should handle HTTP 401 errors', () => {
const httpError = {
response: { status: 401, data: { message: 'Unauthorized' } },
};
const result = handleError(httpError);
expect(result).toBeInstanceOf(AuthenticationError);
expect(result.message).toBe('Unauthorized');
});
it('should handle HTTP 404 errors', () => {
const httpError = {
response: { status: 404, data: { message: 'Not found' } },
};
const result = handleError(httpError);
expect(result.code).toBe('NOT_FOUND');
expect(result.statusCode).toBe(404);
});
it('should handle HTTP 5xx errors', () => {
const httpError = {
response: { status: 503, data: { message: 'Service unavailable' } },
};
const result = handleError(httpError);
expect(result).toBeInstanceOf(N8NConnectionError);
});
it('should handle connection refused errors', () => {
const connError = { code: 'ECONNREFUSED' };
const result = handleError(connError);
expect(result).toBeInstanceOf(N8NConnectionError);
expect(result.message).toBe('Cannot connect to n8n API');
});
it('should handle generic errors', () => {
const error = new Error('Something went wrong');
const result = handleError(error);
expect(result.message).toBe('Something went wrong');
expect(result.code).toBe('UNKNOWN_ERROR');
expect(result.statusCode).toBe(500);
});
it('should handle errors without message', () => {
const error = {};
const result = handleError(error);
expect(result.message).toBe('An unexpected error occurred');
});
});
describe('withErrorHandling', () => {
it('should execute operation successfully', async () => {
const operation = jest.fn().mockResolvedValue('success');
const result = await withErrorHandling(operation, 'test operation');
expect(result).toBe('success');
expect(logger.error).not.toHaveBeenCalled();
});
it('should handle and log errors', async () => {
const error = new Error('Operation failed');
const operation = jest.fn().mockRejectedValue(error);
await expect(withErrorHandling(operation, 'test operation')).rejects.toThrow();
expect(logger.error).toHaveBeenCalledWith('Error in test operation:', error);
});
it('should transform errors using handleError', async () => {
const error = { code: 'ECONNREFUSED' };
const operation = jest.fn().mockRejectedValue(error);
try {
await withErrorHandling(operation, 'test operation');
} catch (err) {
expect(err).toBeInstanceOf(N8NConnectionError);
}
});
});