mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-23 10:53:07 +00:00
Merge pull request #223 from czlonkowski/feat/improve-update-partial-workflow
feat: Remove unnecessary 5-operation limit from n8n_update_partial_workflow
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp)
|
[](https://github.com/czlonkowski/n8n-mcp)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp)
|
|
||||||
[](https://www.npmjs.com/package/n8n-mcp)
|
[](https://www.npmjs.com/package/n8n-mcp)
|
||||||
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
[](https://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2.13.1] - 2025-01-24
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Removed 5-operation limit from n8n_update_partial_workflow**: The workflow diff engine now supports unlimited operations per request
|
||||||
|
- Previously limited to 5 operations for "transactional integrity"
|
||||||
|
- Analysis revealed the limit was unnecessary - the clone-validate-apply pattern already ensures atomicity
|
||||||
|
- All operations are validated before any are applied, maintaining data integrity
|
||||||
|
- Enables complex workflow refactoring in single API calls
|
||||||
|
- Updated documentation and examples to demonstrate large batch operations (26+ operations)
|
||||||
|
|
||||||
## [2.13.0] - 2025-01-24
|
## [2.13.0] - 2025-01-24
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -296,6 +296,193 @@ The `n8n_update_partial_workflow` tool allows you to make targeted changes to wo
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Example 5: Large Batch Workflow Refactoring
|
||||||
|
Demonstrates handling many operations in a single request - no longer limited to 5 operations!
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "workflow-batch",
|
||||||
|
"operations": [
|
||||||
|
// Add 10 processing nodes
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Filter Active Users",
|
||||||
|
"type": "n8n-nodes-base.filter",
|
||||||
|
"position": [400, 200],
|
||||||
|
"parameters": { "conditions": { "boolean": [{ "value1": "={{$json.active}}", "value2": true }] } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Transform User Data",
|
||||||
|
"type": "n8n-nodes-base.set",
|
||||||
|
"position": [600, 200],
|
||||||
|
"parameters": { "values": { "string": [{ "name": "formatted_name", "value": "={{$json.firstName}} {{$json.lastName}}" }] } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Validate Email",
|
||||||
|
"type": "n8n-nodes-base.if",
|
||||||
|
"position": [800, 200],
|
||||||
|
"parameters": { "conditions": { "string": [{ "value1": "={{$json.email}}", "operation": "contains", "value2": "@" }] } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Enrich with API",
|
||||||
|
"type": "n8n-nodes-base.httpRequest",
|
||||||
|
"position": [1000, 150],
|
||||||
|
"parameters": { "url": "https://api.example.com/enrich", "method": "POST" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Log Invalid Emails",
|
||||||
|
"type": "n8n-nodes-base.code",
|
||||||
|
"position": [1000, 350],
|
||||||
|
"parameters": { "jsCode": "console.log('Invalid email:', $json.email);\nreturn $json;" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Merge Results",
|
||||||
|
"type": "n8n-nodes-base.merge",
|
||||||
|
"position": [1200, 250]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Deduplicate",
|
||||||
|
"type": "n8n-nodes-base.removeDuplicates",
|
||||||
|
"position": [1400, 250],
|
||||||
|
"parameters": { "propertyName": "id" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Sort by Date",
|
||||||
|
"type": "n8n-nodes-base.sort",
|
||||||
|
"position": [1600, 250],
|
||||||
|
"parameters": { "sortFieldsUi": { "sortField": [{ "fieldName": "created_at", "order": "descending" }] } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Batch for DB",
|
||||||
|
"type": "n8n-nodes-base.splitInBatches",
|
||||||
|
"position": [1800, 250],
|
||||||
|
"parameters": { "batchSize": 100 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addNode",
|
||||||
|
"node": {
|
||||||
|
"name": "Save to Database",
|
||||||
|
"type": "n8n-nodes-base.postgres",
|
||||||
|
"position": [2000, 250],
|
||||||
|
"parameters": { "operation": "insert", "table": "processed_users" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Connect all the nodes
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Get Users",
|
||||||
|
"target": "Filter Active Users"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Filter Active Users",
|
||||||
|
"target": "Transform User Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Transform User Data",
|
||||||
|
"target": "Validate Email"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Validate Email",
|
||||||
|
"sourceOutput": "true",
|
||||||
|
"target": "Enrich with API"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Validate Email",
|
||||||
|
"sourceOutput": "false",
|
||||||
|
"target": "Log Invalid Emails"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Enrich with API",
|
||||||
|
"target": "Merge Results"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Log Invalid Emails",
|
||||||
|
"target": "Merge Results",
|
||||||
|
"targetInput": "input2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Merge Results",
|
||||||
|
"target": "Deduplicate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Deduplicate",
|
||||||
|
"target": "Sort by Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Sort by Date",
|
||||||
|
"target": "Batch for DB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addConnection",
|
||||||
|
"source": "Batch for DB",
|
||||||
|
"target": "Save to Database"
|
||||||
|
},
|
||||||
|
// Update workflow metadata
|
||||||
|
{
|
||||||
|
"type": "updateName",
|
||||||
|
"name": "User Processing Pipeline v2"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "updateSettings",
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1",
|
||||||
|
"timezone": "UTC",
|
||||||
|
"saveDataSuccessExecution": "all"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addTag",
|
||||||
|
"tag": "production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addTag",
|
||||||
|
"tag": "user-processing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "addTag",
|
||||||
|
"tag": "v2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This example shows 26 operations in a single request, creating a complete data processing pipeline with proper error handling, validation, and batch processing.
|
||||||
|
|
||||||
## Best Practices
|
## Best Practices
|
||||||
|
|
||||||
1. **Use Descriptive Names**: Always provide clear node names and descriptions for operations
|
1. **Use Descriptive Names**: Always provide clear node names and descriptions for operations
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "n8n-mcp",
|
"name": "n8n-mcp",
|
||||||
"version": "2.13.0",
|
"version": "2.13.1",
|
||||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -76,6 +76,6 @@ export const validateWorkflowDoc: ToolDocumentation = {
|
|||||||
'Validation cannot catch all runtime errors (e.g., API failures)',
|
'Validation cannot catch all runtime errors (e.g., API failures)',
|
||||||
'Profile setting only affects node validation, not connection/expression checks'
|
'Profile setting only affects node validation, not connection/expression checks'
|
||||||
],
|
],
|
||||||
relatedTools: ['validate_workflow_connections', 'validate_workflow_expressions', 'validate_node_operation', 'n8n_create_workflow', 'n8n_update_partial_workflow']
|
relatedTools: ['validate_workflow_connections', 'validate_workflow_expressions', 'validate_node_operation', 'n8n_create_workflow', 'n8n_update_partial_workflow', 'n8n_autofix_workflow']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -4,18 +4,18 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
|||||||
name: 'n8n_update_partial_workflow',
|
name: 'n8n_update_partial_workflow',
|
||||||
category: 'workflow_management',
|
category: 'workflow_management',
|
||||||
essentials: {
|
essentials: {
|
||||||
description: 'Update workflow incrementally with diff operations. Max 5 ops. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag.',
|
description: 'Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag.',
|
||||||
keyParameters: ['id', 'operations'],
|
keyParameters: ['id', 'operations'],
|
||||||
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "updateNode", ...}]})',
|
example: 'n8n_update_partial_workflow({id: "wf_123", operations: [{type: "updateNode", ...}]})',
|
||||||
performance: 'Fast (50-200ms)',
|
performance: 'Fast (50-200ms)',
|
||||||
tips: [
|
tips: [
|
||||||
'Use for targeted changes',
|
'Use for targeted changes',
|
||||||
'Supports up to 5 operations',
|
'Supports multiple operations in one call',
|
||||||
'Validate with validateOnly first'
|
'Validate with validateOnly first'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
full: {
|
full: {
|
||||||
description: `Updates workflows using surgical diff operations instead of full replacement. Supports 13 operation types for precise modifications. Operations are validated and applied atomically - all succeed or none are applied. Maximum 5 operations per call for safety.
|
description: `Updates workflows using surgical diff operations instead of full replacement. Supports 13 operation types for precise modifications. Operations are validated and applied atomically - all succeed or none are applied.
|
||||||
|
|
||||||
## Available Operations:
|
## Available Operations:
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
|||||||
operations: {
|
operations: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'Array of diff operations. Each must have "type" field and operation-specific properties. Max 5 operations. Nodes can be referenced by ID or name.'
|
description: 'Array of diff operations. Each must have "type" field and operation-specific properties. Nodes can be referenced by ID or name.'
|
||||||
},
|
},
|
||||||
validateOnly: { type: 'boolean', description: 'If true, only validate operations without applying them' }
|
validateOnly: { type: 'boolean', description: 'If true, only validate operations without applying them' }
|
||||||
},
|
},
|
||||||
@@ -64,12 +64,10 @@ export const n8nUpdatePartialWorkflowDoc: ToolDocumentation = {
|
|||||||
bestPractices: [
|
bestPractices: [
|
||||||
'Use validateOnly to test operations',
|
'Use validateOnly to test operations',
|
||||||
'Group related changes in one call',
|
'Group related changes in one call',
|
||||||
'Keep operations under 5 for clarity',
|
|
||||||
'Check operation order for dependencies'
|
'Check operation order for dependencies'
|
||||||
],
|
],
|
||||||
pitfalls: [
|
pitfalls: [
|
||||||
'**REQUIRES N8N_API_URL and N8N_API_KEY environment variables** - will not work without n8n API access',
|
'**REQUIRES N8N_API_URL and N8N_API_KEY environment variables** - will not work without n8n API access',
|
||||||
'Maximum 5 operations per call - split larger updates',
|
|
||||||
'Operations validated together - all must be valid',
|
'Operations validated together - all must be valid',
|
||||||
'Order matters for dependent operations (e.g., must add node before connecting to it)',
|
'Order matters for dependent operations (e.g., must add node before connecting to it)',
|
||||||
'Node references accept ID or name, but name must be unique',
|
'Node references accept ID or name, but name must be unique',
|
||||||
|
|||||||
@@ -66,6 +66,6 @@ Requires N8N_API_URL and N8N_API_KEY environment variables to be configured.`,
|
|||||||
'Profile affects validation time - strict is slower but more thorough',
|
'Profile affects validation time - strict is slower but more thorough',
|
||||||
'Expression validation may flag working but non-standard syntax'
|
'Expression validation may flag working but non-standard syntax'
|
||||||
],
|
],
|
||||||
relatedTools: ['validate_workflow', 'n8n_get_workflow', 'validate_workflow_expressions', 'n8n_health_check']
|
relatedTools: ['validate_workflow', 'n8n_get_workflow', 'validate_workflow_expressions', 'n8n_health_check', 'n8n_autofix_workflow']
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -160,7 +160,7 @@ export const n8nManagementTools: ToolDefinition[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'n8n_update_partial_workflow',
|
name: 'n8n_update_partial_workflow',
|
||||||
description: `Update workflow incrementally with diff operations. Max 5 ops. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
description: `Update workflow incrementally with diff operations. Types: addNode, removeNode, updateNode, moveNode, enable/disableNode, addConnection, removeConnection, updateSettings, updateName, add/removeTag. See tools_documentation("n8n_update_partial_workflow", "full") for details.`,
|
||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: true, // Allow any extra properties Claude Desktop might add
|
additionalProperties: true, // Allow any extra properties Claude Desktop might add
|
||||||
|
|||||||
121
src/scripts/test-autofix-documentation.ts
Normal file
121
src/scripts/test-autofix-documentation.ts
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test script to verify n8n_autofix_workflow documentation is properly integrated
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { toolsDocumentation } from '../mcp/tool-docs';
|
||||||
|
import { getToolDocumentation } from '../mcp/tools-documentation';
|
||||||
|
import { Logger } from '../utils/logger';
|
||||||
|
|
||||||
|
const logger = new Logger({ prefix: '[AutofixDoc Test]' });
|
||||||
|
|
||||||
|
async function testAutofixDocumentation() {
|
||||||
|
logger.info('Testing n8n_autofix_workflow documentation...\n');
|
||||||
|
|
||||||
|
// Test 1: Check if documentation exists in the registry
|
||||||
|
logger.info('Test 1: Checking documentation registry');
|
||||||
|
const hasDoc = 'n8n_autofix_workflow' in toolsDocumentation;
|
||||||
|
if (hasDoc) {
|
||||||
|
logger.info('✅ Documentation found in registry');
|
||||||
|
} else {
|
||||||
|
logger.error('❌ Documentation NOT found in registry');
|
||||||
|
logger.info('Available tools:', Object.keys(toolsDocumentation).filter(k => k.includes('autofix')));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Check documentation structure
|
||||||
|
if (hasDoc) {
|
||||||
|
logger.info('\nTest 2: Checking documentation structure');
|
||||||
|
const doc = toolsDocumentation['n8n_autofix_workflow'];
|
||||||
|
|
||||||
|
const hasEssentials = doc.essentials &&
|
||||||
|
doc.essentials.description &&
|
||||||
|
doc.essentials.keyParameters &&
|
||||||
|
doc.essentials.example;
|
||||||
|
|
||||||
|
const hasFull = doc.full &&
|
||||||
|
doc.full.description &&
|
||||||
|
doc.full.parameters &&
|
||||||
|
doc.full.examples;
|
||||||
|
|
||||||
|
if (hasEssentials) {
|
||||||
|
logger.info('✅ Essentials documentation complete');
|
||||||
|
logger.info(` Description: ${doc.essentials.description.substring(0, 80)}...`);
|
||||||
|
logger.info(` Key params: ${doc.essentials.keyParameters.join(', ')}`);
|
||||||
|
} else {
|
||||||
|
logger.error('❌ Essentials documentation incomplete');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFull) {
|
||||||
|
logger.info('✅ Full documentation complete');
|
||||||
|
logger.info(` Parameters: ${Object.keys(doc.full.parameters).join(', ')}`);
|
||||||
|
logger.info(` Examples: ${doc.full.examples.length} provided`);
|
||||||
|
} else {
|
||||||
|
logger.error('❌ Full documentation incomplete');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Test getToolDocumentation function
|
||||||
|
logger.info('\nTest 3: Testing getToolDocumentation function');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const essentialsDoc = getToolDocumentation('n8n_autofix_workflow', 'essentials');
|
||||||
|
if (essentialsDoc.includes("Tool 'n8n_autofix_workflow' not found")) {
|
||||||
|
logger.error('❌ Essentials documentation retrieval failed');
|
||||||
|
} else {
|
||||||
|
logger.info('✅ Essentials documentation retrieved');
|
||||||
|
const lines = essentialsDoc.split('\n').slice(0, 3);
|
||||||
|
lines.forEach(line => logger.info(` ${line}`));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Error retrieving essentials documentation:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const fullDoc = getToolDocumentation('n8n_autofix_workflow', 'full');
|
||||||
|
if (fullDoc.includes("Tool 'n8n_autofix_workflow' not found")) {
|
||||||
|
logger.error('❌ Full documentation retrieval failed');
|
||||||
|
} else {
|
||||||
|
logger.info('✅ Full documentation retrieved');
|
||||||
|
const lines = fullDoc.split('\n').slice(0, 3);
|
||||||
|
lines.forEach(line => logger.info(` ${line}`));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Error retrieving full documentation:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Check if tool is listed in workflow management tools
|
||||||
|
logger.info('\nTest 4: Checking workflow management tools listing');
|
||||||
|
const workflowTools = Object.keys(toolsDocumentation).filter(k => k.startsWith('n8n_'));
|
||||||
|
const hasAutofix = workflowTools.includes('n8n_autofix_workflow');
|
||||||
|
|
||||||
|
if (hasAutofix) {
|
||||||
|
logger.info('✅ n8n_autofix_workflow is listed in workflow management tools');
|
||||||
|
logger.info(` Total workflow tools: ${workflowTools.length}`);
|
||||||
|
|
||||||
|
// Show related tools
|
||||||
|
const relatedTools = workflowTools.filter(t =>
|
||||||
|
t.includes('validate') || t.includes('update') || t.includes('fix')
|
||||||
|
);
|
||||||
|
logger.info(` Related tools: ${relatedTools.join(', ')}`);
|
||||||
|
} else {
|
||||||
|
logger.error('❌ n8n_autofix_workflow NOT listed in workflow management tools');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
logger.info('\n' + '='.repeat(60));
|
||||||
|
logger.info('Summary:');
|
||||||
|
|
||||||
|
if (hasDoc && hasAutofix) {
|
||||||
|
logger.info('✨ Documentation integration successful!');
|
||||||
|
logger.info('The n8n_autofix_workflow tool documentation is properly integrated.');
|
||||||
|
logger.info('\nTo use in MCP:');
|
||||||
|
logger.info(' - Essentials: tools_documentation({topic: "n8n_autofix_workflow"})');
|
||||||
|
logger.info(' - Full: tools_documentation({topic: "n8n_autofix_workflow", depth: "full"})');
|
||||||
|
} else {
|
||||||
|
logger.error('⚠️ Documentation integration incomplete');
|
||||||
|
logger.info('Please check the implementation and rebuild the project.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testAutofixDocumentation().catch(console.error);
|
||||||
149
src/scripts/test-webhook-autofix.ts
Normal file
149
src/scripts/test-webhook-autofix.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test script for webhook path autofixer functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NodeRepository } from '../database/node-repository';
|
||||||
|
import { createDatabaseAdapter } from '../database/database-adapter';
|
||||||
|
import { WorkflowAutoFixer } from '../services/workflow-auto-fixer';
|
||||||
|
import { WorkflowValidator } from '../services/workflow-validator';
|
||||||
|
import { EnhancedConfigValidator } from '../services/enhanced-config-validator';
|
||||||
|
import { Workflow } from '../types/n8n-api';
|
||||||
|
import { Logger } from '../utils/logger';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
const logger = new Logger({ prefix: '[TestWebhookAutofix]' });
|
||||||
|
|
||||||
|
// Test workflow with webhook missing path
|
||||||
|
const testWorkflow: Workflow = {
|
||||||
|
id: 'test_webhook_fix',
|
||||||
|
name: 'Test Webhook Autofix',
|
||||||
|
active: false,
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'Webhook',
|
||||||
|
type: 'n8n-nodes-base.webhook',
|
||||||
|
typeVersion: 2.1,
|
||||||
|
position: [250, 300],
|
||||||
|
parameters: {}, // Empty parameters - missing path
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: '2',
|
||||||
|
name: 'HTTP Request',
|
||||||
|
type: 'n8n-nodes-base.httpRequest',
|
||||||
|
typeVersion: 4.2,
|
||||||
|
position: [450, 300],
|
||||||
|
parameters: {
|
||||||
|
url: 'https://api.example.com/data',
|
||||||
|
method: 'GET'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
connections: {
|
||||||
|
'Webhook': {
|
||||||
|
main: [[{
|
||||||
|
node: 'HTTP Request',
|
||||||
|
type: 'main',
|
||||||
|
index: 0
|
||||||
|
}]]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
settings: {
|
||||||
|
executionOrder: 'v1'
|
||||||
|
},
|
||||||
|
staticData: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
async function testWebhookAutofix() {
|
||||||
|
logger.info('Testing webhook path autofixer...');
|
||||||
|
|
||||||
|
// Initialize database and repository
|
||||||
|
const dbPath = join(process.cwd(), 'data', 'nodes.db');
|
||||||
|
const adapter = await createDatabaseAdapter(dbPath);
|
||||||
|
const repository = new NodeRepository(adapter);
|
||||||
|
|
||||||
|
// Create validators
|
||||||
|
const validator = new WorkflowValidator(repository, EnhancedConfigValidator);
|
||||||
|
const autoFixer = new WorkflowAutoFixer(repository);
|
||||||
|
|
||||||
|
// Step 1: Validate workflow to identify issues
|
||||||
|
logger.info('Step 1: Validating workflow to identify issues...');
|
||||||
|
const validationResult = await validator.validateWorkflow(testWorkflow);
|
||||||
|
|
||||||
|
console.log('\n📋 Validation Summary:');
|
||||||
|
console.log(`- Valid: ${validationResult.valid}`);
|
||||||
|
console.log(`- Errors: ${validationResult.errors.length}`);
|
||||||
|
console.log(`- Warnings: ${validationResult.warnings.length}`);
|
||||||
|
|
||||||
|
if (validationResult.errors.length > 0) {
|
||||||
|
console.log('\n❌ Errors found:');
|
||||||
|
validationResult.errors.forEach(error => {
|
||||||
|
console.log(` - [${error.nodeName || error.nodeId}] ${error.message}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 2: Generate fixes (preview mode)
|
||||||
|
logger.info('\nStep 2: Generating fixes in preview mode...');
|
||||||
|
|
||||||
|
const fixResult = autoFixer.generateFixes(
|
||||||
|
testWorkflow,
|
||||||
|
validationResult,
|
||||||
|
[], // No expression format issues to pass
|
||||||
|
{
|
||||||
|
applyFixes: false, // Preview mode
|
||||||
|
fixTypes: ['webhook-missing-path'] // Only test webhook fixes
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('\n🔧 Fix Results:');
|
||||||
|
console.log(`- Summary: ${fixResult.summary}`);
|
||||||
|
console.log(`- Total fixes: ${fixResult.stats.total}`);
|
||||||
|
console.log(`- Webhook path fixes: ${fixResult.stats.byType['webhook-missing-path']}`);
|
||||||
|
|
||||||
|
if (fixResult.fixes.length > 0) {
|
||||||
|
console.log('\n📝 Detailed Fixes:');
|
||||||
|
fixResult.fixes.forEach(fix => {
|
||||||
|
console.log(` - Node: ${fix.node}`);
|
||||||
|
console.log(` Field: ${fix.field}`);
|
||||||
|
console.log(` Type: ${fix.type}`);
|
||||||
|
console.log(` Before: ${fix.before || 'undefined'}`);
|
||||||
|
console.log(` After: ${fix.after}`);
|
||||||
|
console.log(` Confidence: ${fix.confidence}`);
|
||||||
|
console.log(` Description: ${fix.description}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fixResult.operations.length > 0) {
|
||||||
|
console.log('\n🔄 Operations to Apply:');
|
||||||
|
fixResult.operations.forEach(op => {
|
||||||
|
if (op.type === 'updateNode') {
|
||||||
|
console.log(` - Update Node: ${op.nodeId}`);
|
||||||
|
console.log(` Updates: ${JSON.stringify(op.updates, null, 2)}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Verify UUID format
|
||||||
|
if (fixResult.fixes.length > 0) {
|
||||||
|
const webhookFix = fixResult.fixes.find(f => f.type === 'webhook-missing-path');
|
||||||
|
if (webhookFix) {
|
||||||
|
const uuid = webhookFix.after as string;
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
|
const isValidUUID = uuidRegex.test(uuid);
|
||||||
|
|
||||||
|
console.log('\n✅ UUID Validation:');
|
||||||
|
console.log(` - Generated UUID: ${uuid}`);
|
||||||
|
console.log(` - Valid format: ${isValidUUID ? 'Yes' : 'No'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('\n✨ Webhook autofix test completed successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run test
|
||||||
|
testWebhookAutofix().catch(error => {
|
||||||
|
logger.error('Test failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -41,17 +41,6 @@ export class WorkflowDiffEngine {
|
|||||||
request: WorkflowDiffRequest
|
request: WorkflowDiffRequest
|
||||||
): Promise<WorkflowDiffResult> {
|
): Promise<WorkflowDiffResult> {
|
||||||
try {
|
try {
|
||||||
// Limit operations to keep complexity manageable
|
|
||||||
if (request.operations.length > 5) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
errors: [{
|
|
||||||
operation: -1,
|
|
||||||
message: 'Too many operations. Maximum 5 operations allowed per request to ensure transactional integrity.'
|
|
||||||
}]
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone workflow to avoid modifying original
|
// Clone workflow to avoid modifying original
|
||||||
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
const workflowCopy = JSON.parse(JSON.stringify(workflow));
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { WorkflowDiffEngine } from '@/services/workflow-diff-engine';
|
|||||||
import { createWorkflow, WorkflowBuilder } from '@tests/utils/builders/workflow.builder';
|
import { createWorkflow, WorkflowBuilder } from '@tests/utils/builders/workflow.builder';
|
||||||
import {
|
import {
|
||||||
WorkflowDiffRequest,
|
WorkflowDiffRequest,
|
||||||
|
WorkflowDiffOperation,
|
||||||
AddNodeOperation,
|
AddNodeOperation,
|
||||||
RemoveNodeOperation,
|
RemoveNodeOperation,
|
||||||
UpdateNodeOperation,
|
UpdateNodeOperation,
|
||||||
@@ -60,9 +61,10 @@ describe('WorkflowDiffEngine', () => {
|
|||||||
baseWorkflow.connections = newConnections;
|
baseWorkflow.connections = newConnections;
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Operation Limits', () => {
|
describe('Large Operation Batches', () => {
|
||||||
it('should reject more than 5 operations', async () => {
|
it('should handle many operations successfully', async () => {
|
||||||
const operations = Array(6).fill(null).map((_: any, i: number) => ({
|
// Test with 50 operations
|
||||||
|
const operations = Array(50).fill(null).map((_: any, i: number) => ({
|
||||||
type: 'updateName',
|
type: 'updateName',
|
||||||
name: `Name ${i}`
|
name: `Name ${i}`
|
||||||
} as UpdateNameOperation));
|
} as UpdateNameOperation));
|
||||||
@@ -74,9 +76,46 @@ describe('WorkflowDiffEngine', () => {
|
|||||||
|
|
||||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||||
|
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(true);
|
||||||
expect(result.errors).toHaveLength(1);
|
expect(result.operationsApplied).toBe(50);
|
||||||
expect(result.errors![0].message).toContain('Too many operations');
|
expect(result.workflow!.name).toBe('Name 49'); // Last operation wins
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle 100+ mixed operations', async () => {
|
||||||
|
const operations: WorkflowDiffOperation[] = [
|
||||||
|
// Add 30 nodes
|
||||||
|
...Array(30).fill(null).map((_: any, i: number) => ({
|
||||||
|
type: 'addNode',
|
||||||
|
node: {
|
||||||
|
name: `Node${i}`,
|
||||||
|
type: 'n8n-nodes-base.code',
|
||||||
|
position: [i * 100, 300],
|
||||||
|
parameters: {}
|
||||||
|
}
|
||||||
|
} as AddNodeOperation)),
|
||||||
|
// Update names 30 times
|
||||||
|
...Array(30).fill(null).map((_: any, i: number) => ({
|
||||||
|
type: 'updateName',
|
||||||
|
name: `Workflow Version ${i}`
|
||||||
|
} as UpdateNameOperation)),
|
||||||
|
// Add 40 tags
|
||||||
|
...Array(40).fill(null).map((_: any, i: number) => ({
|
||||||
|
type: 'addTag',
|
||||||
|
tag: `tag${i}`
|
||||||
|
} as AddTagOperation))
|
||||||
|
];
|
||||||
|
|
||||||
|
const request: WorkflowDiffRequest = {
|
||||||
|
id: 'test-workflow',
|
||||||
|
operations
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||||
|
|
||||||
|
expect(result.success).toBe(true);
|
||||||
|
expect(result.operationsApplied).toBe(100);
|
||||||
|
expect(result.workflow!.nodes.length).toBeGreaterThan(30);
|
||||||
|
expect(result.workflow!.name).toBe('Workflow Version 29');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user