Skip to main content
Glama

n8n-MCP

by 88-888
workflow-validator-error-outputs.test.ts22.8 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { WorkflowValidator } from '@/services/workflow-validator'; import { NodeRepository } from '@/database/node-repository'; import { EnhancedConfigValidator } from '@/services/enhanced-config-validator'; vi.mock('@/utils/logger'); describe('WorkflowValidator - Error Output Validation', () => { let validator: WorkflowValidator; let mockNodeRepository: any; beforeEach(() => { vi.clearAllMocks(); // Create mock repository mockNodeRepository = { getNode: vi.fn((type: string) => { // Return mock node info for common node types if (type.includes('httpRequest') || type.includes('webhook') || type.includes('set')) { return { node_type: type, display_name: 'Mock Node', isVersioned: true, version: 1 }; } return null; }) }; validator = new WorkflowValidator(mockNodeRepository, EnhancedConfigValidator); }); describe('Error Output Configuration', () => { it('should detect incorrect configuration - multiple nodes in same array', async () => { const workflow = { nodes: [ { id: '1', name: 'Validate Input', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [-400, 64], parameters: {} }, { id: '2', name: 'Filter URLs', type: 'n8n-nodes-base.filter', typeVersion: 2.2, position: [-176, 64], parameters: {} }, { id: '3', name: 'Error Response1', type: 'n8n-nodes-base.respondToWebhook', typeVersion: 1.5, position: [-160, 240], parameters: {} } ], connections: { 'Validate Input': { main: [ [ { node: 'Filter URLs', type: 'main', index: 0 }, { node: 'Error Response1', type: 'main', index: 0 } // WRONG! Both in main[0] ] ] } } }; const result = await validator.validateWorkflow(workflow as any); expect(result.valid).toBe(false); expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') && e.message.includes('Error Response1') && e.message.includes('appear to be error handlers but are in main[0]') )).toBe(true); // Check that the error message includes the fix const errorMsg = result.errors.find(e => e.message.includes('Incorrect error output configuration')); expect(errorMsg?.message).toContain('INCORRECT (current)'); expect(errorMsg?.message).toContain('CORRECT (should be)'); expect(errorMsg?.message).toContain('main[1] = error output'); }); it('should validate correct configuration - separate arrays', async () => { const workflow = { nodes: [ { id: '1', name: 'Validate Input', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [-400, 64], parameters: {}, onError: 'continueErrorOutput' }, { id: '2', name: 'Filter URLs', type: 'n8n-nodes-base.filter', typeVersion: 2.2, position: [-176, 64], parameters: {} }, { id: '3', name: 'Error Response1', type: 'n8n-nodes-base.respondToWebhook', typeVersion: 1.5, position: [-160, 240], parameters: {} } ], connections: { 'Validate Input': { main: [ [ { node: 'Filter URLs', type: 'main', index: 0 } ], [ { node: 'Error Response1', type: 'main', index: 0 } // Correctly in main[1] ] ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should not have the specific error about incorrect configuration expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); }); it('should detect onError without error connections', async () => { const workflow = { nodes: [ { id: '1', name: 'HTTP Request', type: 'n8n-nodes-base.httpRequest', typeVersion: 4, position: [100, 100], parameters: {}, onError: 'continueErrorOutput' // Has onError }, { id: '2', name: 'Process Data', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} } ], connections: { 'HTTP Request': { main: [ [ { node: 'Process Data', type: 'main', index: 0 } ] // No main[1] for error output ] } } }; const result = await validator.validateWorkflow(workflow as any); expect(result.errors.some(e => e.nodeName === 'HTTP Request' && e.message.includes("has onError: 'continueErrorOutput' but no error output connections") )).toBe(true); }); it('should warn about error connections without onError', async () => { const workflow = { nodes: [ { id: '1', name: 'HTTP Request', type: 'n8n-nodes-base.httpRequest', typeVersion: 4, position: [100, 100], parameters: {} // Missing onError property }, { id: '2', name: 'Process Data', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Error Handler', type: 'n8n-nodes-base.set', position: [300, 300], parameters: {} } ], connections: { 'HTTP Request': { main: [ [ { node: 'Process Data', type: 'main', index: 0 } ], [ { node: 'Error Handler', type: 'main', index: 0 } // Has error connection ] ] } } }; const result = await validator.validateWorkflow(workflow as any); expect(result.warnings.some(w => w.nodeName === 'HTTP Request' && w.message.includes('error output connections in main[1] but missing onError') )).toBe(true); }); }); describe('Error Handler Detection', () => { it('should detect error handler nodes by name', async () => { const workflow = { nodes: [ { id: '1', name: 'API Call', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {} }, { id: '2', name: 'Process Success', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Handle Error', // Contains 'error' type: 'n8n-nodes-base.set', position: [300, 300], parameters: {} } ], connections: { 'API Call': { main: [ [ { node: 'Process Success', type: 'main', index: 0 }, { node: 'Handle Error', type: 'main', index: 0 } // Wrong placement ] ] } } }; const result = await validator.validateWorkflow(workflow as any); expect(result.errors.some(e => e.message.includes('Handle Error') && e.message.includes('appear to be error handlers') )).toBe(true); }); it('should detect error handler nodes by type', async () => { const workflow = { nodes: [ { id: '1', name: 'Webhook', type: 'n8n-nodes-base.webhook', position: [100, 100], parameters: {} }, { id: '2', name: 'Process', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Respond', type: 'n8n-nodes-base.respondToWebhook', // Common error handler type position: [300, 300], parameters: {} } ], connections: { 'Webhook': { main: [ [ { node: 'Process', type: 'main', index: 0 }, { node: 'Respond', type: 'main', index: 0 } // Wrong placement ] ] } } }; const result = await validator.validateWorkflow(workflow as any); expect(result.errors.some(e => e.message.includes('Respond') && e.message.includes('appear to be error handlers') )).toBe(true); }); it('should not flag non-error nodes in main[0]', async () => { const workflow = { nodes: [ { id: '1', name: 'Start', type: 'n8n-nodes-base.manualTrigger', position: [100, 100], parameters: {} }, { id: '2', name: 'First Process', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Second Process', type: 'n8n-nodes-base.set', position: [300, 200], parameters: {} } ], connections: { 'Start': { main: [ [ { node: 'First Process', type: 'main', index: 0 }, { node: 'Second Process', type: 'main', index: 0 } // Both are valid success paths ] ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should not have error about incorrect error configuration expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); }); }); describe('Complex Error Patterns', () => { it('should handle multiple error handlers correctly', async () => { const workflow = { nodes: [ { id: '1', name: 'HTTP Request', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {}, onError: 'continueErrorOutput' }, { id: '2', name: 'Process', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Log Error', type: 'n8n-nodes-base.set', position: [300, 200], parameters: {} }, { id: '4', name: 'Send Error Email', type: 'n8n-nodes-base.emailSend', position: [300, 300], parameters: {} } ], connections: { 'HTTP Request': { main: [ [ { node: 'Process', type: 'main', index: 0 } ], [ { node: 'Log Error', type: 'main', index: 0 }, { node: 'Send Error Email', type: 'main', index: 0 } // Multiple error handlers OK in main[1] ] ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should not have errors about the configuration expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); }); it('should detect mixed success and error handlers in main[0]', async () => { const workflow = { nodes: [ { id: '1', name: 'API Request', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {} }, { id: '2', name: 'Transform Data', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Store Data', type: 'n8n-nodes-base.set', position: [500, 100], parameters: {} }, { id: '4', name: 'Error Notification', type: 'n8n-nodes-base.emailSend', position: [300, 300], parameters: {} } ], connections: { 'API Request': { main: [ [ { node: 'Transform Data', type: 'main', index: 0 }, { node: 'Store Data', type: 'main', index: 0 }, { node: 'Error Notification', type: 'main', index: 0 } // Error handler mixed with success nodes ] ] } } }; const result = await validator.validateWorkflow(workflow as any); expect(result.errors.some(e => e.message.includes('Error Notification') && e.message.includes('appear to be error handlers but are in main[0]') )).toBe(true); }); it('should handle nested error handling (error handlers with their own errors)', async () => { const workflow = { nodes: [ { id: '1', name: 'Primary API', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {}, onError: 'continueErrorOutput' }, { id: '2', name: 'Success Handler', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Error Logger', type: 'n8n-nodes-base.httpRequest', position: [300, 200], parameters: {}, onError: 'continueErrorOutput' }, { id: '4', name: 'Fallback Error', type: 'n8n-nodes-base.set', position: [500, 250], parameters: {} } ], connections: { 'Primary API': { main: [ [ { node: 'Success Handler', type: 'main', index: 0 } ], [ { node: 'Error Logger', type: 'main', index: 0 } ] ] }, 'Error Logger': { main: [ [], [ { node: 'Fallback Error', type: 'main', index: 0 } ] ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should not have errors about incorrect configuration expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); }); }); describe('Edge Cases', () => { it('should handle workflows with no connections at all', async () => { const workflow = { nodes: [ { id: '1', name: 'Isolated Node', type: 'n8n-nodes-base.set', position: [100, 100], parameters: {}, onError: 'continueErrorOutput' } ], connections: {} }; const result = await validator.validateWorkflow(workflow as any); // Should have warning about orphaned node but not error about connections expect(result.warnings.some(w => w.nodeName === 'Isolated Node' && w.message.includes('not connected to any other nodes') )).toBe(true); // Should not have error about error output configuration expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); }); it('should handle nodes with empty main arrays', async () => { const workflow = { nodes: [ { id: '1', name: 'Source Node', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {}, onError: 'continueErrorOutput' }, { id: '2', name: 'Target Node', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} } ], connections: { 'Source Node': { main: [ [], // Empty success array [] // Empty error array ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should detect that onError is set but no error connections exist expect(result.errors.some(e => e.nodeName === 'Source Node' && e.message.includes("has onError: 'continueErrorOutput' but no error output connections") )).toBe(true); }); it('should handle workflows with only error outputs (no success path)', async () => { const workflow = { nodes: [ { id: '1', name: 'Risky Operation', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {}, onError: 'continueErrorOutput' }, { id: '2', name: 'Error Handler Only', type: 'n8n-nodes-base.set', position: [300, 200], parameters: {} } ], connections: { 'Risky Operation': { main: [ [], // No success connections [ { node: 'Error Handler Only', type: 'main', index: 0 } ] ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should not have errors about incorrect configuration - this is valid expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); // Should not have errors about missing error connections expect(result.errors.some(e => e.message.includes("has onError: 'continueErrorOutput' but no error output connections") )).toBe(false); }); it('should handle undefined or null connection arrays gracefully', async () => { const workflow = { nodes: [ { id: '1', name: 'Source Node', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {} } ], connections: { 'Source Node': { main: [ null, // Null array undefined // Undefined array ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should not crash and should not have configuration errors expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); }); it('should detect all variations of error-related node names', async () => { const workflow = { nodes: [ { id: '1', name: 'Source', type: 'n8n-nodes-base.httpRequest', position: [100, 100], parameters: {} }, { id: '2', name: 'Handle Failure', type: 'n8n-nodes-base.set', position: [300, 100], parameters: {} }, { id: '3', name: 'Catch Exception', type: 'n8n-nodes-base.set', position: [300, 200], parameters: {} }, { id: '4', name: 'Success Path', type: 'n8n-nodes-base.set', position: [500, 100], parameters: {} } ], connections: { 'Source': { main: [ [ { node: 'Handle Failure', type: 'main', index: 0 }, { node: 'Catch Exception', type: 'main', index: 0 }, { node: 'Success Path', type: 'main', index: 0 } ] ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should detect both 'Handle Failure' and 'Catch Exception' as error handlers expect(result.errors.some(e => e.message.includes('Handle Failure') && e.message.includes('Catch Exception') && e.message.includes('appear to be error handlers but are in main[0]') )).toBe(true); }); it('should not flag legitimate parallel processing nodes', async () => { const workflow = { nodes: [ { id: '1', name: 'Data Source', type: 'n8n-nodes-base.webhook', position: [100, 100], parameters: {} }, { id: '2', name: 'Process A', type: 'n8n-nodes-base.set', position: [300, 50], parameters: {} }, { id: '3', name: 'Process B', type: 'n8n-nodes-base.set', position: [300, 150], parameters: {} }, { id: '4', name: 'Transform Data', type: 'n8n-nodes-base.set', position: [300, 250], parameters: {} } ], connections: { 'Data Source': { main: [ [ { node: 'Process A', type: 'main', index: 0 }, { node: 'Process B', type: 'main', index: 0 }, { node: 'Transform Data', type: 'main', index: 0 } ] ] } } }; const result = await validator.validateWorkflow(workflow as any); // Should not flag these as error configuration issues expect(result.errors.some(e => e.message.includes('Incorrect error output configuration') )).toBe(false); }); }); });

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/88-888/n8n-mcp'

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