Skip to main content
Glama

n8n-MCP

by 88-888
ai-node-connection-validation.test.tsโ€ข20.9 kB
/** * Integration tests for AI node connection validation in workflow diff operations * Tests that AI nodes with AI-specific connection types (ai_languageModel, ai_memory, etc.) * are properly validated without requiring main connections * * Related to issue #357 */ import { describe, test, expect } from 'vitest'; import { WorkflowDiffEngine } from '../../../src/services/workflow-diff-engine'; describe('AI Node Connection Validation', () => { describe('AI-specific connection types', () => { test('should accept workflow with ai_languageModel connections', async () => { const workflow = { id: 'test-workflow', name: 'AI Language Model Test', nodes: [ { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'llm-node', name: 'OpenAI Chat Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { 'OpenAI Chat Model': { ai_languageModel: [ [{ node: 'AI Agent', type: 'ai_languageModel', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); }); test('should accept workflow with ai_memory connections', async () => { const workflow = { id: 'test-workflow', name: 'AI Memory Test', nodes: [ { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'memory-node', name: 'Postgres Chat Memory', type: '@n8n/n8n-nodes-langchain.memoryPostgresChat', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { 'Postgres Chat Memory': { ai_memory: [ [{ node: 'AI Agent', type: 'ai_memory', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); }); test('should accept workflow with ai_embedding connections', async () => { const workflow = { id: 'test-workflow', name: 'AI Embedding Test', nodes: [ { id: 'vectorstore-node', name: 'Vector Store', type: '@n8n/n8n-nodes-langchain.vectorStoreSupabase', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'embedding-node', name: 'Embeddings OpenAI', type: '@n8n/n8n-nodes-langchain.embeddingsOpenAi', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { 'Embeddings OpenAI': { ai_embedding: [ [{ node: 'Vector Store', type: 'ai_embedding', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); }); test('should accept workflow with ai_tool connections', async () => { const workflow = { id: 'test-workflow', name: 'AI Tool Test', nodes: [ { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'vectorstore-node', name: 'Vector Store Tool', type: '@n8n/n8n-nodes-langchain.vectorStoreSupabase', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { 'Vector Store Tool': { ai_tool: [ [{ node: 'AI Agent', type: 'ai_tool', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); }); test('should accept workflow with ai_vectorStore connections', async () => { const workflow = { id: 'test-workflow', name: 'AI Vector Store Test', nodes: [ { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'vectorstore-node', name: 'Supabase Vector Store', type: '@n8n/n8n-nodes-langchain.vectorStoreSupabase', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { 'Supabase Vector Store': { ai_vectorStore: [ [{ node: 'AI Agent', type: 'ai_vectorStore', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); }); }); describe('Mixed connection types', () => { test('should accept workflow mixing main and AI connections', async () => { const workflow = { id: 'test-workflow', name: 'Mixed Connections Test', nodes: [ { id: 'webhook-node', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [200, 0], parameters: {} }, { id: 'llm-node', name: 'OpenAI Chat Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', typeVersion: 1, position: [200, 200], parameters: {} }, { id: 'respond-node', name: 'Respond to Webhook', type: 'n8n-nodes-base.respondToWebhook', typeVersion: 1, position: [400, 0], parameters: {} } ], connections: { 'Webhook': { main: [ [{ node: 'AI Agent', type: 'main', index: 0 }] ] }, 'AI Agent': { main: [ [{ node: 'Respond to Webhook', type: 'main', index: 0 }] ] }, 'OpenAI Chat Model': { ai_languageModel: [ [{ node: 'AI Agent', type: 'ai_languageModel', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); }); test('should accept workflow with error connections alongside AI connections', async () => { const workflow = { id: 'test-workflow', name: 'Error + AI Connections Test', nodes: [ { id: 'webhook-node', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [200, 0], parameters: {} }, { id: 'llm-node', name: 'OpenAI Chat Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', typeVersion: 1, position: [200, 200], parameters: {} }, { id: 'error-handler', name: 'Error Handler', type: 'n8n-nodes-base.set', typeVersion: 1, position: [200, -200], parameters: {} } ], connections: { 'Webhook': { main: [ [{ node: 'AI Agent', type: 'main', index: 0 }] ] }, 'AI Agent': { error: [ [{ node: 'Error Handler', type: 'main', index: 0 }] ] }, 'OpenAI Chat Model': { ai_languageModel: [ [{ node: 'AI Agent', type: 'ai_languageModel', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); }); }); describe('Complex AI workflow (Issue #357 scenario)', () => { test('should accept full AI agent workflow with RAG components', async () => { // Simplified version of the workflow from issue #357 const workflow = { id: 'test-workflow', name: 'AI Agent with RAG', nodes: [ { id: 'webhook-node', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [0, 0], parameters: {} }, { id: 'code-node', name: 'Prepare Inputs', type: 'n8n-nodes-base.code', typeVersion: 2, position: [200, 0], parameters: {} }, { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1.7, position: [400, 0], parameters: {} }, { id: 'llm-node', name: 'OpenAI Chat Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', typeVersion: 1, position: [400, 200], parameters: {} }, { id: 'memory-node', name: 'Postgres Chat Memory', type: '@n8n/n8n-nodes-langchain.memoryPostgresChat', typeVersion: 1.1, position: [500, 200], parameters: {} }, { id: 'embedding-node', name: 'Embeddings OpenAI', type: '@n8n/n8n-nodes-langchain.embeddingsOpenAi', typeVersion: 1, position: [600, 400], parameters: {} }, { id: 'vectorstore-node', name: 'Supabase Vector Store', type: '@n8n/n8n-nodes-langchain.vectorStoreSupabase', typeVersion: 1.3, position: [600, 200], parameters: {} }, { id: 'respond-node', name: 'Respond to Webhook', type: 'n8n-nodes-base.respondToWebhook', typeVersion: 1.1, position: [600, 0], parameters: {} } ], connections: { 'Webhook': { main: [ [{ node: 'Prepare Inputs', type: 'main', index: 0 }] ] }, 'Prepare Inputs': { main: [ [{ node: 'AI Agent', type: 'main', index: 0 }] ] }, 'AI Agent': { main: [ [{ node: 'Respond to Webhook', type: 'main', index: 0 }] ] }, 'OpenAI Chat Model': { ai_languageModel: [ [{ node: 'AI Agent', type: 'ai_languageModel', index: 0 }] ] }, 'Postgres Chat Memory': { ai_memory: [ [{ node: 'AI Agent', type: 'ai_memory', index: 0 }] ] }, 'Embeddings OpenAI': { ai_embedding: [ [{ node: 'Supabase Vector Store', type: 'ai_embedding', index: 0 }] ] }, 'Supabase Vector Store': { ai_tool: [ [{ node: 'AI Agent', type: 'ai_tool', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); expect(result.errors || []).toHaveLength(0); }); test('should successfully update AI workflow nodes without connection errors', async () => { // Test that we can update nodes in an AI workflow without triggering validation errors const workflow = { id: 'test-workflow', name: 'AI Workflow Update Test', nodes: [ { id: 'webhook-node', name: 'Webhook', type: 'n8n-nodes-base.webhook', typeVersion: 2, position: [0, 0], parameters: { path: 'test' } }, { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [200, 0], parameters: {} }, { id: 'llm-node', name: 'OpenAI Chat Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', typeVersion: 1, position: [200, 200], parameters: {} } ], connections: { 'Webhook': { main: [ [{ node: 'AI Agent', type: 'main', index: 0 }] ] }, 'OpenAI Chat Model': { ai_languageModel: [ [{ node: 'AI Agent', type: 'ai_languageModel', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); // Update the webhook node (unrelated to AI nodes) const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [ { type: 'updateNode', nodeId: 'webhook-node', updates: { notes: 'Updated webhook configuration' } } ] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); expect(result.errors || []).toHaveLength(0); // Verify the update was applied const updatedNode = result.workflow.nodes.find((n: any) => n.id === 'webhook-node'); expect(updatedNode?.notes).toBe('Updated webhook configuration'); }); }); describe('Node-only AI nodes (no main connections)', () => { test('should accept AI nodes with ONLY ai_languageModel connections', async () => { const workflow = { id: 'test-workflow', name: 'AI Node Without Main', nodes: [ { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'llm-node', name: 'OpenAI Chat Model', type: '@n8n/n8n-nodes-langchain.lmChatOpenAi', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { // OpenAI Chat Model has NO main connections, ONLY ai_languageModel 'OpenAI Chat Model': { ai_languageModel: [ [{ node: 'AI Agent', type: 'ai_languageModel', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); expect(result.errors || []).toHaveLength(0); }); test('should accept AI nodes with ONLY ai_memory connections', async () => { const workflow = { id: 'test-workflow', name: 'Memory Node Without Main', nodes: [ { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'memory-node', name: 'Postgres Chat Memory', type: '@n8n/n8n-nodes-langchain.memoryPostgresChat', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { // Memory node has NO main connections, ONLY ai_memory 'Postgres Chat Memory': { ai_memory: [ [{ node: 'AI Agent', type: 'ai_memory', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); expect(result.errors || []).toHaveLength(0); }); test('should accept embedding nodes with ONLY ai_embedding connections', async () => { const workflow = { id: 'test-workflow', name: 'Embedding Node Without Main', nodes: [ { id: 'vectorstore-node', name: 'Vector Store', type: '@n8n/n8n-nodes-langchain.vectorStoreSupabase', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'embedding-node', name: 'Embeddings OpenAI', type: '@n8n/n8n-nodes-langchain.embeddingsOpenAi', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { // Embedding node has NO main connections, ONLY ai_embedding 'Embeddings OpenAI': { ai_embedding: [ [{ node: 'Vector Store', type: 'ai_embedding', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); expect(result.errors || []).toHaveLength(0); }); test('should accept vector store nodes with ONLY ai_tool connections', async () => { const workflow = { id: 'test-workflow', name: 'Vector Store Node Without Main', nodes: [ { id: 'agent-node', name: 'AI Agent', type: '@n8n/n8n-nodes-langchain.agent', typeVersion: 1, position: [0, 0], parameters: {} }, { id: 'vectorstore-node', name: 'Supabase Vector Store', type: '@n8n/n8n-nodes-langchain.vectorStoreSupabase', typeVersion: 1, position: [200, 0], parameters: {} } ], connections: { // Vector store has NO main connections, ONLY ai_tool 'Supabase Vector Store': { ai_tool: [ [{ node: 'AI Agent', type: 'ai_tool', index: 0 }] ] } } }; const engine = new WorkflowDiffEngine(); const result = await engine.applyDiff(workflow as any, { id: workflow.id, operations: [] }); expect(result.success).toBe(true); expect(result.workflow).toBeDefined(); expect(result.errors || []).toHaveLength(0); }); }); });

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