409e92d6ae
Build and Push Docker Images / Build Docker Image (push) Has been cancelled
Build and Push Docker Images / Build Railway Docker Image (push) Has been cancelled
Build and Publish n8n Docker Image / test-image (push) Has been cancelled
Dependency Compatibility Check / Fresh Install Dependency Check (push) Has been cancelled
Build and Publish n8n Docker Image / build-and-push (push) Has been cancelled
Build and Publish n8n Docker Image / create-release (push) Has been cancelled
Automated Release / Detect Version Change (push) Has been cancelled
Automated Release / Generate Release Notes (push) Has been cancelled
Automated Release / Create GitHub Release (push) Has been cancelled
Automated Release / Package MCPB Bundle (push) Has been cancelled
Automated Release / Build and Verify (push) Has been cancelled
Automated Release / Publish to NPM (push) Has been cancelled
Automated Release / Build and Push Docker Images (push) Has been cancelled
Automated Release / Update Documentation (push) Has been cancelled
Automated Release / Notify Release Completion (push) Has been cancelled
Secret Scan / secretlint (push) Has been cancelled
Test Suite / test (push) Has been cancelled
Test Suite / cjs-runtime (push) Has been cancelled
Test Suite / publish-results (push) Has been cancelled
723 lines
20 KiB
TypeScript
723 lines
20 KiB
TypeScript
/**
|
|
* 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);
|
|
});
|
|
});
|
|
});
|