mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-01-30 22:42:04 +00:00
* perf: optimize workflow tool responses for token efficiency (v2.29.0)
Reduce response sizes by 75-90% for 4 workflow management tools:
- n8n_update_partial_workflow: Returns {id, name, active, operationsApplied}
- n8n_create_workflow: Returns {id, name, active, nodeCount}
- n8n_update_full_workflow: Returns {id, name, active, nodeCount}
- n8n_delete_workflow: Returns {id, name, deleted: true}
AI agents can use n8n_get_workflow with mode 'structure' if they need
to verify the current workflow state after operations.
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: update tests and add nodeCount to partial update response
- Fix handleCreateWorkflow test to expect minimal response
- Fix handleDeleteWorkflow test to expect minimal response
- Add nodeCount to n8n_update_partial_workflow response for consistency
- Update documentation and CHANGELOG
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: update handlers-workflow-diff tests for minimal response
Update 3 more tests that expected full workflow in response:
- should apply diff operations successfully
- should activate workflow after successful update
- should deactivate workflow after successful update
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
* fix: update integration tests to use minimal response format
Integration tests now verify minimal response format and use
client.getWorkflow() to fetch actual workflow state for verification.
Conceived by Romuald Czlonkowski - www.aiadvisors.pl/en
* fix: update create/update workflow integration tests for minimal response
Integration tests now verify minimal response and use client.getWorkflow()
to fetch actual workflow state for detailed verification.
Conceived by Romuald Czlonkowski - www.aiadvisors.pl/en
* fix: add type assertions to fix TypeScript errors in tests
Conceived by Romuald Czlonkowski - www.aiadvisors.pl/en
---------
Co-authored-by: Romuald Członkowski <romualdczlonkowski@MacBook-Pro-Romuald.local>
Co-authored-by: Claude <noreply@anthropic.com>
582 lines
21 KiB
TypeScript
582 lines
21 KiB
TypeScript
/**
|
|
* Integration Tests: handleCreateWorkflow
|
|
*
|
|
* Tests workflow creation against a real n8n instance.
|
|
* Verifies the P0 bug fix (FULL vs SHORT node type formats)
|
|
* and covers all major workflow creation scenarios.
|
|
*/
|
|
|
|
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 { Workflow } from '../../../../src/types/n8n-api';
|
|
import {
|
|
SIMPLE_WEBHOOK_WORKFLOW,
|
|
SIMPLE_HTTP_WORKFLOW,
|
|
MULTI_NODE_WORKFLOW,
|
|
ERROR_HANDLING_WORKFLOW,
|
|
AI_AGENT_WORKFLOW,
|
|
EXPRESSION_WORKFLOW,
|
|
getFixture
|
|
} from '../utils/fixtures';
|
|
import { cleanupOrphanedWorkflows } from '../utils/cleanup-helpers';
|
|
import { createMcpContext } from '../utils/mcp-context';
|
|
import { InstanceContext } from '../../../../src/types/instance-context';
|
|
import { handleCreateWorkflow } from '../../../../src/mcp/handlers-n8n-manager';
|
|
|
|
describe('Integration: handleCreateWorkflow', () => {
|
|
let context: TestContext;
|
|
let client: N8nApiClient;
|
|
let mcpContext: InstanceContext;
|
|
|
|
beforeEach(() => {
|
|
context = createTestContext();
|
|
client = getTestN8nClient();
|
|
mcpContext = createMcpContext();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await context.cleanup();
|
|
});
|
|
|
|
// Global cleanup after all tests to catch any orphaned workflows
|
|
// (e.g., from test retries or failures)
|
|
// IMPORTANT: Skip cleanup in CI to preserve shared n8n instance workflows
|
|
afterAll(async () => {
|
|
if (!process.env.CI) {
|
|
await cleanupOrphanedWorkflows();
|
|
}
|
|
});
|
|
|
|
// ======================================================================
|
|
// P0: Critical Bug Verification
|
|
// ======================================================================
|
|
|
|
describe('P0: Node Type Format Bug Fix', () => {
|
|
it('should create workflow with webhook node using FULL node type format', async () => {
|
|
// This test verifies the P0 bug fix where SHORT node type format
|
|
// (e.g., "webhook") was incorrectly normalized to FULL format
|
|
// causing workflow creation failures.
|
|
//
|
|
// The fix ensures FULL format (e.g., "n8n-nodes-base.webhook")
|
|
// is preserved and passed to n8n API correctly.
|
|
|
|
const workflowName = createTestWorkflowName('P0 Bug Verification - Webhook Node');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('simple-webhook')
|
|
};
|
|
|
|
// Create workflow using MCP handler
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Verify workflow created successfully
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
expect(result.name).toBe(workflowName);
|
|
expect((result as any).nodeCount).toBe(1);
|
|
|
|
// Fetch actual workflow to verify details
|
|
const workflow2 = await client.getWorkflow(result.id);
|
|
expect(workflow2.nodes).toHaveLength(1);
|
|
// Critical: Verify FULL node type format is preserved
|
|
expect(workflow2.nodes[0].type).toBe('n8n-nodes-base.webhook');
|
|
expect(workflow2.nodes[0].name).toBe('Webhook');
|
|
expect(workflow2.nodes[0].parameters).toBeDefined();
|
|
});
|
|
});
|
|
|
|
// ======================================================================
|
|
// P1: Base Nodes (High Priority)
|
|
// ======================================================================
|
|
|
|
describe('P1: Base n8n Nodes', () => {
|
|
it('should create workflow with HTTP Request node', async () => {
|
|
const workflowName = createTestWorkflowName('HTTP Request Node');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('simple-http')
|
|
};
|
|
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
expect(result.name).toBe(workflowName);
|
|
expect((result as any).nodeCount).toBe(2);
|
|
|
|
// Fetch actual workflow to verify details
|
|
const actual = await client.getWorkflow(result.id);
|
|
expect(actual.nodes).toHaveLength(2);
|
|
|
|
// Verify both nodes created with FULL type format
|
|
const webhookNode = actual.nodes.find((n: any) => n.name === 'Webhook');
|
|
const httpNode = actual.nodes.find((n: any) => n.name === 'HTTP Request');
|
|
|
|
expect(webhookNode).toBeDefined();
|
|
expect(webhookNode!.type).toBe('n8n-nodes-base.webhook');
|
|
|
|
expect(httpNode).toBeDefined();
|
|
expect(httpNode!.type).toBe('n8n-nodes-base.httpRequest');
|
|
|
|
// Verify connections
|
|
expect(actual.connections).toBeDefined();
|
|
expect(actual.connections.Webhook).toBeDefined();
|
|
});
|
|
|
|
it('should create workflow with langchain agent node', async () => {
|
|
const workflowName = createTestWorkflowName('Langchain Agent Node');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('ai-agent')
|
|
};
|
|
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
expect(result.name).toBe(workflowName);
|
|
expect((result as any).nodeCount).toBe(2);
|
|
|
|
// Fetch actual workflow to verify details
|
|
const actual = await client.getWorkflow(result.id);
|
|
expect(actual.nodes).toHaveLength(2);
|
|
|
|
// Verify langchain node type format
|
|
const agentNode = actual.nodes.find((n: any) => n.name === 'AI Agent');
|
|
expect(agentNode).toBeDefined();
|
|
expect(agentNode!.type).toBe('@n8n/n8n-nodes-langchain.agent');
|
|
});
|
|
|
|
it('should create complex multi-node workflow', async () => {
|
|
const workflowName = createTestWorkflowName('Multi-Node Workflow');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('multi-node')
|
|
};
|
|
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
expect(result.name).toBe(workflowName);
|
|
expect((result as any).nodeCount).toBe(4);
|
|
|
|
// Fetch actual workflow to verify details
|
|
const actual = await client.getWorkflow(result.id);
|
|
expect(actual.nodes).toHaveLength(4);
|
|
|
|
// Verify all node types preserved
|
|
const nodeTypes = actual.nodes.map((n: any) => n.type);
|
|
expect(nodeTypes).toContain('n8n-nodes-base.webhook');
|
|
expect(nodeTypes).toContain('n8n-nodes-base.set');
|
|
expect(nodeTypes).toContain('n8n-nodes-base.merge');
|
|
|
|
// Verify complex connections
|
|
expect(actual.connections.Webhook.main[0]).toHaveLength(2); // Branches to 2 nodes
|
|
});
|
|
});
|
|
|
|
// ======================================================================
|
|
// P2: Advanced Features (Medium Priority)
|
|
// ======================================================================
|
|
|
|
describe('P2: Advanced Workflow Features', () => {
|
|
it('should create workflow with complex connections and branching', async () => {
|
|
const workflowName = createTestWorkflowName('Complex Connections');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('multi-node')
|
|
};
|
|
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
|
|
// Fetch actual workflow to verify connections
|
|
const actual = await client.getWorkflow(result.id);
|
|
expect(actual.connections).toBeDefined();
|
|
|
|
// Verify branching: Webhook -> Set 1 and Set 2
|
|
const webhookConnections = actual.connections.Webhook.main[0];
|
|
expect(webhookConnections).toHaveLength(2);
|
|
|
|
// Verify merging: Set 1 -> Merge (port 0), Set 2 -> Merge (port 1)
|
|
const set1Connections = actual.connections['Set 1'].main[0];
|
|
const set2Connections = actual.connections['Set 2'].main[0];
|
|
|
|
expect(set1Connections[0].node).toBe('Merge');
|
|
expect(set1Connections[0].index).toBe(0);
|
|
|
|
expect(set2Connections[0].node).toBe('Merge');
|
|
expect(set2Connections[0].index).toBe(1);
|
|
});
|
|
|
|
it('should create workflow with custom settings', async () => {
|
|
const workflowName = createTestWorkflowName('Custom Settings');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('error-handling'),
|
|
settings: {
|
|
executionOrder: 'v1' as const,
|
|
timezone: 'America/New_York',
|
|
saveDataErrorExecution: 'all' as const,
|
|
saveDataSuccessExecution: 'all' as const,
|
|
saveExecutionProgress: true
|
|
}
|
|
};
|
|
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
|
|
// Fetch actual workflow to verify settings
|
|
const actual = await client.getWorkflow(result.id);
|
|
expect(actual.settings).toBeDefined();
|
|
expect(actual.settings!.executionOrder).toBe('v1');
|
|
});
|
|
|
|
it('should create workflow with n8n expressions', async () => {
|
|
const workflowName = createTestWorkflowName('n8n Expressions');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('expression')
|
|
};
|
|
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
expect((result as any).nodeCount).toBe(2);
|
|
|
|
// Fetch actual workflow to verify expressions
|
|
const actual = await client.getWorkflow(result.id);
|
|
expect(actual.nodes).toHaveLength(2);
|
|
|
|
// Verify Set node with expressions
|
|
const setNode = actual.nodes.find((n: any) => n.name === 'Set Variables');
|
|
expect(setNode).toBeDefined();
|
|
expect(setNode!.parameters.assignments).toBeDefined();
|
|
|
|
// Verify expressions are preserved
|
|
const assignmentsData = setNode!.parameters.assignments as { assignments: Array<{ value: string }> };
|
|
expect(assignmentsData.assignments).toHaveLength(3);
|
|
expect(assignmentsData.assignments[0].value).toContain('$now');
|
|
expect(assignmentsData.assignments[1].value).toContain('$json');
|
|
expect(assignmentsData.assignments[2].value).toContain('$node');
|
|
});
|
|
|
|
it('should create workflow with error handling configuration', async () => {
|
|
const workflowName = createTestWorkflowName('Error Handling');
|
|
const workflow = {
|
|
name: workflowName,
|
|
...getFixture('error-handling')
|
|
};
|
|
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(true);
|
|
const result = response.data as Workflow;
|
|
|
|
// Response now returns minimal data
|
|
expect(result).toBeDefined();
|
|
expect(result.id).toBeTruthy();
|
|
if (!result.id) throw new Error('Workflow ID is missing');
|
|
context.trackWorkflow(result.id);
|
|
expect((result as any).nodeCount).toBe(3);
|
|
|
|
// Fetch actual workflow to verify error handling
|
|
const actual = await client.getWorkflow(result.id);
|
|
expect(actual.nodes).toHaveLength(3);
|
|
|
|
// Verify HTTP node with error handling
|
|
const httpNode = actual.nodes.find((n: any) => n.name === 'HTTP Request');
|
|
expect(httpNode).toBeDefined();
|
|
expect(httpNode!.continueOnFail).toBe(true);
|
|
expect(httpNode!.onError).toBe('continueErrorOutput');
|
|
|
|
// Verify error connection
|
|
expect(actual.connections['HTTP Request'].error).toBeDefined();
|
|
expect(actual.connections['HTTP Request'].error[0][0].node).toBe('Handle Error');
|
|
});
|
|
});
|
|
|
|
// ======================================================================
|
|
// Error Scenarios (P1 Priority)
|
|
// ======================================================================
|
|
|
|
describe('Error Scenarios', () => {
|
|
it('should reject workflow with invalid node type (MCP validation)', async () => {
|
|
// MCP handler correctly validates workflows before sending to n8n API.
|
|
// Invalid node types are caught during MCP validation.
|
|
//
|
|
// Note: Raw n8n API would accept this and only fail at execution time,
|
|
// but MCP handler does proper pre-validation (correct behavior).
|
|
|
|
const workflowName = createTestWorkflowName('Invalid Node Type');
|
|
const workflow = {
|
|
name: workflowName,
|
|
nodes: [
|
|
{
|
|
id: 'invalid-1',
|
|
name: 'Invalid Node',
|
|
type: 'n8n-nodes-base.nonexistentnode',
|
|
typeVersion: 1,
|
|
position: [250, 300] as [number, number],
|
|
parameters: {}
|
|
}
|
|
],
|
|
connections: {},
|
|
settings: { executionOrder: 'v1' as const }
|
|
};
|
|
|
|
// MCP handler rejects invalid workflows (correct behavior)
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBeDefined();
|
|
expect(response.error).toContain('validation');
|
|
});
|
|
|
|
it('should reject workflow with missing required node parameters (MCP validation)', async () => {
|
|
// MCP handler validates required parameters before sending to n8n API.
|
|
//
|
|
// Note: Raw n8n API would accept this and only fail at execution time,
|
|
// but MCP handler does proper pre-validation (correct behavior).
|
|
|
|
const workflowName = createTestWorkflowName('Missing Parameters');
|
|
const workflow = {
|
|
name: workflowName,
|
|
nodes: [
|
|
{
|
|
id: 'http-1',
|
|
name: 'HTTP Request',
|
|
type: 'n8n-nodes-base.httpRequest',
|
|
typeVersion: 4.2,
|
|
position: [250, 300] as [number, number],
|
|
parameters: {
|
|
// Missing required 'url' parameter
|
|
method: 'GET'
|
|
}
|
|
}
|
|
],
|
|
connections: {},
|
|
settings: { executionOrder: 'v1' as const }
|
|
};
|
|
|
|
// MCP handler rejects workflows with validation errors (correct behavior)
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBeDefined();
|
|
});
|
|
|
|
it('should reject workflow with duplicate node names (MCP validation)', async () => {
|
|
// MCP handler validates that node names are unique.
|
|
//
|
|
// Note: Raw n8n API might auto-rename duplicates, but MCP handler
|
|
// enforces unique names upfront (correct behavior).
|
|
|
|
const workflowName = createTestWorkflowName('Duplicate Node Names');
|
|
const workflow = {
|
|
name: workflowName,
|
|
nodes: [
|
|
{
|
|
id: 'set-1',
|
|
name: 'Set',
|
|
type: 'n8n-nodes-base.set',
|
|
typeVersion: 3.4,
|
|
position: [250, 300] as [number, number],
|
|
parameters: {
|
|
assignments: { assignments: [] },
|
|
options: {}
|
|
}
|
|
},
|
|
{
|
|
id: 'set-2',
|
|
name: 'Set', // Duplicate name
|
|
type: 'n8n-nodes-base.set',
|
|
typeVersion: 3.4,
|
|
position: [450, 300] as [number, number],
|
|
parameters: {
|
|
assignments: { assignments: [] },
|
|
options: {}
|
|
}
|
|
}
|
|
],
|
|
connections: {},
|
|
settings: { executionOrder: 'v1' as const }
|
|
};
|
|
|
|
// MCP handler rejects workflows with validation errors (correct behavior)
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBeDefined();
|
|
});
|
|
|
|
it('should reject workflow with invalid connection references (MCP validation)', async () => {
|
|
// MCP handler validates that connection references point to existing nodes.
|
|
//
|
|
// Note: Raw n8n API would accept this and only fail at execution time,
|
|
// but MCP handler does proper connection validation (correct behavior).
|
|
|
|
const workflowName = createTestWorkflowName('Invalid Connections');
|
|
const workflow = {
|
|
name: workflowName,
|
|
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: {
|
|
// Connection references non-existent node
|
|
Webhook: {
|
|
main: [[{ node: 'NonExistent', type: 'main', index: 0 }]]
|
|
}
|
|
},
|
|
settings: { executionOrder: 'v1' as const }
|
|
};
|
|
|
|
// MCP handler rejects workflows with invalid connections (correct behavior)
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBeDefined();
|
|
expect(response.error).toContain('validation');
|
|
});
|
|
});
|
|
|
|
// ======================================================================
|
|
// Additional Edge Cases
|
|
// ======================================================================
|
|
|
|
describe('Edge Cases', () => {
|
|
it('should reject single-node non-webhook workflow (MCP validation)', async () => {
|
|
// MCP handler enforces that single-node workflows are only valid for webhooks.
|
|
// This is a best practice validation.
|
|
|
|
const workflowName = createTestWorkflowName('Minimal Single Node');
|
|
const workflow = {
|
|
name: workflowName,
|
|
nodes: [
|
|
{
|
|
id: 'manual-1',
|
|
name: 'Manual Trigger',
|
|
type: 'n8n-nodes-base.manualTrigger',
|
|
typeVersion: 1,
|
|
position: [250, 300] as [number, number],
|
|
parameters: {}
|
|
}
|
|
],
|
|
connections: {},
|
|
settings: { executionOrder: 'v1' as const }
|
|
};
|
|
|
|
// MCP handler rejects single-node non-webhook workflows (correct behavior)
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBeDefined();
|
|
expect(response.error).toContain('validation');
|
|
});
|
|
|
|
it('should reject single-node non-trigger workflow (MCP validation)', async () => {
|
|
// MCP handler enforces workflow best practices.
|
|
// Single isolated nodes without connections are rejected.
|
|
|
|
const workflowName = createTestWorkflowName('Empty Connections');
|
|
const workflow = {
|
|
name: workflowName,
|
|
nodes: [
|
|
{
|
|
id: 'set-1',
|
|
name: 'Set',
|
|
type: 'n8n-nodes-base.set',
|
|
typeVersion: 3.4,
|
|
position: [250, 300] as [number, number],
|
|
parameters: {
|
|
assignments: { assignments: [] },
|
|
options: {}
|
|
}
|
|
}
|
|
],
|
|
connections: {}, // Explicitly empty
|
|
settings: { executionOrder: 'v1' as const }
|
|
};
|
|
|
|
// MCP handler rejects single-node workflows (correct behavior)
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBeDefined();
|
|
});
|
|
|
|
it('should reject single-node workflow without settings (MCP validation)', async () => {
|
|
// MCP handler enforces workflow best practices.
|
|
// Single-node non-webhook workflows are rejected.
|
|
|
|
const workflowName = createTestWorkflowName('No Settings');
|
|
const workflow = {
|
|
name: workflowName,
|
|
nodes: [
|
|
{
|
|
id: 'manual-1',
|
|
name: 'Manual Trigger',
|
|
type: 'n8n-nodes-base.manualTrigger',
|
|
typeVersion: 1,
|
|
position: [250, 300] as [number, number],
|
|
parameters: {}
|
|
}
|
|
],
|
|
connections: {}
|
|
// No settings property
|
|
};
|
|
|
|
// MCP handler rejects single-node workflows (correct behavior)
|
|
const response = await handleCreateWorkflow({ ...workflow }, mcpContext);
|
|
expect(response.success).toBe(false);
|
|
expect(response.error).toBeDefined();
|
|
});
|
|
});
|
|
});
|