Skip to main content
Glama

n8n-MCP

by 88-888
autofix-workflow.test.ts25.5 kB
/** * Integration Tests: handleAutofixWorkflow * * Tests workflow autofix against a real n8n instance. * Covers fix types, confidence levels, preview/apply modes, and error handling. */ import { describe, it, expect, beforeEach, afterEach, afterAll } from 'vitest'; import { createTestContext, TestContext, createTestWorkflowName } from '../utils/test-context'; import { getTestN8nClient } from '../utils/n8n-client'; import { N8nApiClient } from '../../../../src/services/n8n-api-client'; import { cleanupOrphanedWorkflows } from '../utils/cleanup-helpers'; import { createMcpContext } from '../utils/mcp-context'; import { InstanceContext } from '../../../../src/types/instance-context'; import { handleAutofixWorkflow } from '../../../../src/mcp/handlers-n8n-manager'; import { getNodeRepository, closeNodeRepository } from '../utils/node-repository'; import { NodeRepository } from '../../../../src/database/node-repository'; import { AutofixResponse } from '../types/mcp-responses'; describe('Integration: handleAutofixWorkflow', () => { let context: TestContext; let client: N8nApiClient; let mcpContext: InstanceContext; let repository: NodeRepository; beforeEach(async () => { context = createTestContext(); client = getTestN8nClient(); mcpContext = createMcpContext(); repository = await getNodeRepository(); }); afterEach(async () => { await context.cleanup(); }); afterAll(async () => { await closeNodeRepository(); if (!process.env.CI) { await cleanupOrphanedWorkflows(); } }); // ====================================================================== // Preview Mode (applyFixes: false) // ====================================================================== describe('Preview Mode', () => { it('should preview fixes without applying them (expression-format)', async () => { // Create workflow with expression format issues const workflow = { name: createTestWorkflowName('Autofix - Preview Expression'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } }, { id: 'set-1', name: 'Set', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [450, 300] as [number, number], parameters: { // Bad expression format (missing {{}}) assignments: { assignments: [ { id: '1', name: 'value', value: '$json.data', // Should be {{ $json.data }} type: 'string' } ] } } } ], connections: { Webhook: { main: [[{ node: 'Set', type: 'main', index: 0 }]] } }, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); // Preview fixes (applyFixes: false) const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as AutofixResponse; // If fixes are available, should be in preview mode if (data.fixesAvailable && data.fixesAvailable > 0) { expect(data.preview).toBe(true); expect(data.fixes).toBeDefined(); expect(Array.isArray(data.fixes)).toBe(true); expect(data.summary).toBeDefined(); expect(data.stats).toBeDefined(); // Verify workflow not modified (fetch it back) const fetched = await client.getWorkflow(created.id!); const params = fetched.nodes[1].parameters as { assignments: { assignments: Array<{ value: string }> } }; expect(params.assignments.assignments[0].value).toBe('$json.data'); } else { // No fixes available - that's also a valid result expect(data.message).toContain('No automatic fixes available'); } }); it('should preview multiple fix types', async () => { // Create workflow with multiple issues const workflow = { name: createTestWorkflowName('Autofix - Preview Multiple'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, // Old typeVersion position: [250, 300] as [number, number], parameters: { httpMethod: 'GET' // Missing path parameter } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; expect(data.preview).toBe(true); expect(data.fixesAvailable).toBeGreaterThan(0); }); }); // ====================================================================== // Apply Mode (applyFixes: true) // ====================================================================== describe('Apply Mode', () => { it('should apply expression-format fixes', async () => { const workflow = { name: createTestWorkflowName('Autofix - Apply Expression'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } }, { id: 'set-1', name: 'Set', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [450, 300] as [number, number], parameters: { assignments: { assignments: [ { id: '1', name: 'value', value: '$json.data', // Bad format type: 'string' } ] } } } ], connections: { Webhook: { main: [[{ node: 'Set', type: 'main', index: 0 }]] } }, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); // Apply fixes const response = await handleAutofixWorkflow( { id: created.id, applyFixes: true, fixTypes: ['expression-format'] }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; // If fixes were applied if (data.fixesApplied && data.fixesApplied > 0) { expect(data.fixes).toBeDefined(); expect(data.preview).toBeUndefined(); // Verify workflow was actually modified const fetched = await client.getWorkflow(created.id!); const params = fetched.nodes[1].parameters as { assignments: { assignments: Array<{ value: unknown }> } }; const setValue = params.assignments.assignments[0].value; // Expression format should be fixed (depends on what fixes were available) expect(setValue).toBeDefined(); } else { // No fixes available or applied - that's also valid expect(data.message).toBeDefined(); } }); it('should apply webhook-missing-path fixes', async () => { const workflow = { name: createTestWorkflowName('Autofix - Apply Webhook Path'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET' // Missing path } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: true, fixTypes: ['webhook-missing-path'] }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; if (data.fixesApplied > 0) { // Verify path was added const fetched = await client.getWorkflow(created.id!); expect(fetched.nodes[0].parameters.path).toBeDefined(); expect(fetched.nodes[0].parameters.path).toBeTruthy(); } }); }); // ====================================================================== // Fix Type Filtering // ====================================================================== describe('Fix Type Filtering', () => { it('should only apply specified fix types', async () => { const workflow = { name: createTestWorkflowName('Autofix - Filter Fix Types'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, // Old typeVersion position: [250, 300] as [number, number], parameters: { httpMethod: 'GET' // Missing path } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); // Only request webhook-missing-path fixes (ignore typeversion issues) const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, fixTypes: ['webhook-missing-path'] }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; // Should only show webhook-missing-path fixes if (data.fixes && data.fixes.length > 0) { data.fixes.forEach((fix: any) => { expect(fix.type).toBe('webhook-missing-path'); }); } }); it('should handle multiple fix types filter', async () => { const workflow = { name: createTestWorkflowName('Autofix - Multiple Filter'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, fixTypes: ['expression-format', 'webhook-missing-path'] }, repository, mcpContext ); expect(response.success).toBe(true); }); }); // ====================================================================== // Confidence Threshold // ====================================================================== describe('Confidence Threshold', () => { it('should filter fixes by high confidence threshold', async () => { const workflow = { name: createTestWorkflowName('Autofix - High Confidence'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, confidenceThreshold: 'high' }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; // All fixes should be high confidence if (data.fixes && data.fixes.length > 0) { data.fixes.forEach((fix: any) => { expect(fix.confidence).toBe('high'); }); } }); it('should include medium and high confidence with medium threshold', async () => { const workflow = { name: createTestWorkflowName('Autofix - Medium Confidence'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, confidenceThreshold: 'medium' }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; // Fixes should be medium or high confidence if (data.fixes && data.fixes.length > 0) { data.fixes.forEach((fix: any) => { expect(['high', 'medium']).toContain(fix.confidence); }); } }); it('should include all confidence levels with low threshold', async () => { const workflow = { name: createTestWorkflowName('Autofix - Low Confidence'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, confidenceThreshold: 'low' }, repository, mcpContext ); expect(response.success).toBe(true); }); }); // ====================================================================== // Max Fixes Parameter // ====================================================================== describe('Max Fixes Parameter', () => { it('should limit fixes to maxFixes parameter', async () => { // Create workflow with multiple issues const workflow = { name: createTestWorkflowName('Autofix - Max Fixes'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } }, { id: 'set-1', name: 'Set 1', type: 'n8n-nodes-base.set', typeVersion: 3.4, position: [450, 300] as [number, number], parameters: { assignments: { assignments: [ { id: '1', name: 'val1', value: '$json.a', type: 'string' }, { id: '2', name: 'val2', value: '$json.b', type: 'string' }, { id: '3', name: 'val3', value: '$json.c', type: 'string' } ] } } } ], connections: { Webhook: { main: [[{ node: 'Set 1', type: 'main', index: 0 }]] } }, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); // Limit to 1 fix const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, maxFixes: 1 }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; // Should have at most 1 fix if (data.fixes) { expect(data.fixes.length).toBeLessThanOrEqual(1); } }); }); // ====================================================================== // No Fixes Available // ====================================================================== describe('No Fixes Available', () => { it('should handle workflow with no fixable issues', async () => { // Create valid workflow const workflow = { name: createTestWorkflowName('Autofix - No Issues'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test-webhook' } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, // Exclude version upgrade fixes to test "no fixes" scenario fixTypes: ['expression-format', 'typeversion-correction', 'error-output-config', 'node-type-correction', 'webhook-missing-path'] }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; expect(data.message).toContain('No automatic fixes available'); expect(data.validationSummary).toBeDefined(); }); }); // ====================================================================== // Error Handling // ====================================================================== describe('Error Handling', () => { it('should handle non-existent workflow ID', async () => { const response = await handleAutofixWorkflow( { id: '99999999', applyFixes: false }, repository, mcpContext ); expect(response.success).toBe(false); expect(response.error).toBeDefined(); }); it('should handle invalid fixTypes parameter', async () => { const workflow = { name: createTestWorkflowName('Autofix - Invalid Param'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, fixTypes: ['invalid-fix-type'] as any }, repository, mcpContext ); // Should either fail validation or ignore invalid type expect(response.success).toBe(false); }); it('should handle invalid confidence threshold', async () => { const workflow = { name: createTestWorkflowName('Autofix - Invalid Confidence'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET', path: 'test' } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false, confidenceThreshold: 'invalid' as any }, repository, mcpContext ); expect(response.success).toBe(false); }); }); // ====================================================================== // Response Format Verification // ====================================================================== describe('Response Format', () => { it('should return complete autofix response structure (preview)', async () => { const workflow = { name: createTestWorkflowName('Autofix - Response Format Preview'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET' // Missing path to trigger fixes } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: false }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; // Verify required fields expect(data).toHaveProperty('workflowId'); expect(data).toHaveProperty('workflowName'); // Preview mode specific fields if (data.fixesAvailable > 0) { expect(data).toHaveProperty('preview'); expect(data.preview).toBe(true); expect(data).toHaveProperty('fixesAvailable'); expect(data).toHaveProperty('fixes'); expect(data).toHaveProperty('summary'); expect(data).toHaveProperty('stats'); expect(data).toHaveProperty('message'); // Verify fixes structure expect(Array.isArray(data.fixes)).toBe(true); if (data.fixes.length > 0) { const fix = data.fixes[0]; expect(fix).toHaveProperty('type'); expect(fix).toHaveProperty('confidence'); expect(fix).toHaveProperty('description'); } } }); it('should return complete autofix response structure (apply)', async () => { const workflow = { name: createTestWorkflowName('Autofix - Response Format Apply'), nodes: [ { id: 'webhook-1', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [250, 300] as [number, number], parameters: { httpMethod: 'GET' // Missing path } } ], connections: {}, settings: {}, tags: ['mcp-integration-test'] }; const created = await client.createWorkflow(workflow); context.trackWorkflow(created.id!); const response = await handleAutofixWorkflow( { id: created.id, applyFixes: true }, repository, mcpContext ); expect(response.success).toBe(true); const data = response.data as any; expect(data).toHaveProperty('workflowId'); expect(data).toHaveProperty('workflowName'); // Apply mode specific fields if (data.fixesApplied > 0) { expect(data).toHaveProperty('fixesApplied'); expect(data).toHaveProperty('fixes'); expect(data).toHaveProperty('summary'); expect(data).toHaveProperty('stats'); expect(data).toHaveProperty('message'); expect(data.preview).toBeUndefined(); // Verify types expect(typeof data.fixesApplied).toBe('number'); expect(Array.isArray(data.fixes)).toBe(true); } }); }); });

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