Skip to main content
Glama
workflow-auto-fixer-tool-variants.test.ts20.8 kB
/** * Tests for WorkflowAutoFixer - Tool Variant Fixes * * Tests the processToolVariantFixes() method which generates fix operations * to replace base node types with their Tool variant equivalents when * incorrectly used with ai_tool connections. * * Coverage: * - tool-variant-correction fixes are generated from validation errors * - Fix changes node type from base to Tool variant * - Fixes have high confidence * - Multiple tool variant fixes in same workflow * - Fix operations are correctly structured */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { WorkflowAutoFixer } from '@/services/workflow-auto-fixer'; import { NodeRepository } from '@/database/node-repository'; import type { WorkflowValidationResult } from '@/services/workflow-validator'; import type { Workflow, WorkflowNode } from '@/types/n8n-api'; vi.mock('@/database/node-repository'); vi.mock('@/utils/logger'); describe('WorkflowAutoFixer - Tool Variant Fixes', () => { let autoFixer: WorkflowAutoFixer; let mockRepository: NodeRepository; const createMockWorkflow = (nodes: WorkflowNode[]): Workflow => ({ id: 'test-workflow', name: 'Test Workflow', active: false, nodes, connections: {}, settings: {}, createdAt: '', updatedAt: '' }); const createMockNode = ( id: string, name: string, type: string, parameters: any = {} ): WorkflowNode => ({ id, name, type, typeVersion: 1, position: [0, 0], parameters }); beforeEach(() => { vi.clearAllMocks(); mockRepository = new NodeRepository({} as any); // Mock getNodeVersions to return empty array (prevent version upgrade processing) vi.spyOn(mockRepository, 'getNodeVersions').mockReturnValue([]); autoFixer = new WorkflowAutoFixer(mockRepository); }); describe('processToolVariantFixes - Basic functionality', () => { it('should generate fix for base node incorrectly used as AI tool', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Node "Supabase" uses "n8n-nodes-base.supabase" which cannot output ai_tool connections. Use the Tool variant "n8n-nodes-base.supabaseTool" instead for AI Agent integration.', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Change node type from "n8n-nodes-base.supabase" to "n8n-nodes-base.supabaseTool"' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); expect(result.fixes).toHaveLength(1); expect(result.fixes[0].type).toBe('tool-variant-correction'); expect(result.fixes[0].node).toBe('Supabase'); expect(result.fixes[0].field).toBe('type'); expect(result.fixes[0].before).toBe('n8n-nodes-base.supabase'); expect(result.fixes[0].after).toBe('n8n-nodes-base.supabaseTool'); }); it('should generate fix with high confidence', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Node uses wrong type for AI tool', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix tool variant' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); expect(result.fixes).toHaveLength(1); expect(result.fixes[0].confidence).toBe('high'); }); it('should generate correct update operation', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', { resource: 'database', operation: 'query' }) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type for AI tool', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix tool variant' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); expect(result.operations).toHaveLength(1); expect(result.operations[0].type).toBe('updateNode'); expect((result.operations[0] as any).nodeId).toBe('Supabase'); expect((result.operations[0] as any).updates.type).toBe('n8n-nodes-base.supabaseTool'); }); }); describe('processToolVariantFixes - Multiple fixes', () => { it('should generate fixes for multiple nodes', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}), createMockNode('postgres-1', 'Postgres', 'n8n-nodes-base.postgres', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix supabase tool variant' } }, { type: 'error', nodeId: 'postgres-1', nodeName: 'Postgres', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.postgres', suggestedType: 'n8n-nodes-base.postgresTool', description: 'Fix postgres tool variant' } } ], warnings: [], statistics: { totalNodes: 2, enabledNodes: 2, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); expect(result.fixes).toHaveLength(2); expect(result.operations).toHaveLength(2); const supabaseFix = result.fixes.find(f => f.node === 'Supabase'); expect(supabaseFix).toBeDefined(); expect(supabaseFix!.after).toBe('n8n-nodes-base.supabaseTool'); const postgresFix = result.fixes.find(f => f.node === 'Postgres'); expect(postgresFix).toBeDefined(); expect(postgresFix!.after).toBe('n8n-nodes-base.postgresTool'); }); }); describe('processToolVariantFixes - Error handling', () => { it('should skip errors without WRONG_NODE_TYPE_FOR_AI_TOOL code', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Different error', code: 'DIFFERENT_ERROR' } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction'); expect(toolVariantFixes).toHaveLength(0); }); it('should skip errors without fix metadata', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL' // No fix metadata } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction'); expect(toolVariantFixes).toHaveLength(0); }); it('should skip errors with wrong fix type', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'different-fix-type', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction'); expect(toolVariantFixes).toHaveLength(0); }); it('should skip errors without node name or ID', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction'); expect(toolVariantFixes).toHaveLength(0); }); it('should skip errors when node not found in workflow', async () => { const workflow = createMockWorkflow([ createMockNode('other-1', 'Other', 'n8n-nodes-base.set', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); const toolVariantFixes = result.fixes.filter(f => f.type === 'tool-variant-correction'); expect(toolVariantFixes).toHaveLength(0); }); }); describe('processToolVariantFixes - Integration with other fixes', () => { it('should work alongside expression format fixes', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', { url: '{{ $json.url }}' // Missing = prefix }) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix tool variant' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 1 }, suggestions: [] }; const formatIssues = [ { fieldPath: 'url', currentValue: '{{ $json.url }}', correctedValue: '={{ $json.url }}', issueType: 'missing-prefix' as const, severity: 'error' as const, explanation: 'Missing = prefix', nodeName: 'Supabase', nodeId: 'supabase-1' } ]; const result = await autoFixer.generateFixes(workflow, validationResult, formatIssues); // Should have both tool variant and expression fixes expect(result.fixes.length).toBeGreaterThanOrEqual(2); const toolVariantFix = result.fixes.find(f => f.type === 'tool-variant-correction'); const expressionFix = result.fixes.find(f => f.type === 'expression-format'); expect(toolVariantFix).toBeDefined(); expect(expressionFix).toBeDefined(); }); }); describe('processToolVariantFixes - Description and summary', () => { it('should include fix description from error', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Change node type from "n8n-nodes-base.supabase" to "n8n-nodes-base.supabaseTool"' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); expect(result.fixes[0].description).toBe( 'Change node type from "n8n-nodes-base.supabase" to "n8n-nodes-base.supabaseTool"' ); }); it('should include tool variant corrections in summary', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix tool variant' } } ], warnings: [], statistics: { totalNodes: 1, enabledNodes: 1, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); expect(result.summary).toContain('tool variant'); expect(result.stats.byType['tool-variant-correction']).toBe(1); }); it('should pluralize summary correctly', async () => { const workflow = createMockWorkflow([ createMockNode('supabase-1', 'Supabase', 'n8n-nodes-base.supabase', {}), createMockNode('postgres-1', 'Postgres', 'n8n-nodes-base.postgres', {}) ]); const validationResult: WorkflowValidationResult = { valid: false, errors: [ { type: 'error', nodeId: 'supabase-1', nodeName: 'Supabase', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.supabase', suggestedType: 'n8n-nodes-base.supabaseTool', description: 'Fix supabase' } }, { type: 'error', nodeId: 'postgres-1', nodeName: 'Postgres', message: 'Wrong node type', code: 'WRONG_NODE_TYPE_FOR_AI_TOOL', fix: { type: 'tool-variant-correction', currentType: 'n8n-nodes-base.postgres', suggestedType: 'n8n-nodes-base.postgresTool', description: 'Fix postgres' } } ], warnings: [], statistics: { totalNodes: 2, enabledNodes: 2, triggerNodes: 0, validConnections: 0, invalidConnections: 0, expressionsValidated: 0 }, suggestions: [] }; const result = await autoFixer.generateFixes(workflow, validationResult, []); expect(result.summary).toContain('tool variant corrections'); expect(result.stats.byType['tool-variant-correction']).toBe(2); }); }); });

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

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