Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e79b53465 | ||
|
|
8ce7c62299 | ||
|
|
15e6e97fd9 | ||
|
|
984af0a72f | ||
|
|
2df1f1b32b | ||
|
|
45fac6fe5e | ||
|
|
b65a2f8f3d | ||
|
|
f3658a4cab | ||
|
|
182016d932 | ||
|
|
36839a1c30 | ||
|
|
cac43ed384 | ||
|
|
8fd8c082ee | ||
|
|
baab3a02dc | ||
|
|
b2a5cf49f7 | ||
|
|
640e758c24 | ||
|
|
685171e9b7 | ||
|
|
567b54eaf7 | ||
|
|
bb774f8c70 | ||
|
|
fddc363221 |
102
.claude/agents/n8n-mcp-tester.md
Normal file
102
.claude/agents/n8n-mcp-tester.md
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: n8n-mcp-tester
|
||||
description: Use this agent when you need to test n8n-mcp functionality after implementing fixes or new features. This agent should be invoked proactively after code changes to validate that the MCP tools work correctly with real n8n workflows. The agent tests the actual MCP server tools, not just unit tests. Examples:\n\n<example>\nContext: Developer has just fixed a validation bug in the n8n-mcp server\nuser: "I've fixed the validation issue for HTTP Request nodes"\nassistant: "I've completed the fix. Now let me build the project and then test it."\n<builds project>\nassistant: "Project built successfully. Please reload the MCP server in Claude Desktop to apply the changes."\nuser: "Reloaded"\nassistant: "Great! Now let me invoke the n8n-mcp-tester agent to verify the fix works correctly."\n<commentary>\nSince a fix has been implemented and the MCP server reloaded, use the n8n-mcp-tester agent to validate the fix works in real-world usage.\n</commentary>\n</example>\n\n<example>\nContext: Developer has added a new MCP tool for workflow validation\nuser: "I've implemented the new workflow validation tool"\nassistant: "Implementation complete. Let me build and prepare for testing."\n<builds project>\nassistant: "Build successful. Please reload the MCP server to load the new tool."\nuser: "Done"\nassistant: "Perfect! I'll now use the n8n-mcp-tester agent to test the new workflow validation tool."\n<commentary>\nAfter implementing new MCP functionality and reloading the server, invoke n8n-mcp-tester to verify it works correctly.\n</commentary>\n</example>
|
||||
tools: Glob, Grep, LS, Read, WebFetch, TodoWrite, WebSearch, mcp__puppeteer__puppeteer_navigate, mcp__puppeteer__puppeteer_screenshot, mcp__puppeteer__puppeteer_click, mcp__puppeteer__puppeteer_fill, mcp__puppeteer__puppeteer_select, mcp__puppeteer__puppeteer_hover, mcp__puppeteer__puppeteer_evaluate, ListMcpResourcesTool, ReadMcpResourceTool, mcp__supabase__list_organizations, mcp__supabase__get_organization, mcp__supabase__list_projects, mcp__supabase__get_project, mcp__supabase__get_cost, mcp__supabase__confirm_cost, mcp__supabase__create_project, mcp__supabase__pause_project, mcp__supabase__restore_project, mcp__supabase__create_branch, mcp__supabase__list_branches, mcp__supabase__delete_branch, mcp__supabase__merge_branch, mcp__supabase__reset_branch, mcp__supabase__rebase_branch, mcp__supabase__list_tables, mcp__supabase__list_extensions, mcp__supabase__list_migrations, mcp__supabase__apply_migration, mcp__supabase__execute_sql, mcp__supabase__get_logs, mcp__supabase__get_advisors, mcp__supabase__get_project_url, mcp__supabase__get_anon_key, mcp__supabase__generate_typescript_types, mcp__supabase__search_docs, mcp__supabase__list_edge_functions, mcp__supabase__deploy_edge_function, mcp__n8n-mcp__tools_documentation, mcp__n8n-mcp__list_nodes, mcp__n8n-mcp__get_node_info, mcp__n8n-mcp__search_nodes, mcp__n8n-mcp__list_ai_tools, mcp__n8n-mcp__get_node_documentation, mcp__n8n-mcp__get_database_statistics, mcp__n8n-mcp__get_node_essentials, mcp__n8n-mcp__search_node_properties, mcp__n8n-mcp__get_node_for_task, mcp__n8n-mcp__list_tasks, mcp__n8n-mcp__validate_node_operation, mcp__n8n-mcp__validate_node_minimal, mcp__n8n-mcp__get_property_dependencies, mcp__n8n-mcp__get_node_as_tool_info, mcp__n8n-mcp__list_node_templates, mcp__n8n-mcp__get_template, mcp__n8n-mcp__search_templates, mcp__n8n-mcp__get_templates_for_task, mcp__n8n-mcp__validate_workflow, mcp__n8n-mcp__validate_workflow_connections, mcp__n8n-mcp__validate_workflow_expressions, mcp__n8n-mcp__n8n_create_workflow, mcp__n8n-mcp__n8n_get_workflow, mcp__n8n-mcp__n8n_get_workflow_details, mcp__n8n-mcp__n8n_get_workflow_structure, mcp__n8n-mcp__n8n_get_workflow_minimal, mcp__n8n-mcp__n8n_update_full_workflow, mcp__n8n-mcp__n8n_update_partial_workflow, mcp__n8n-mcp__n8n_delete_workflow, mcp__n8n-mcp__n8n_list_workflows, mcp__n8n-mcp__n8n_validate_workflow, mcp__n8n-mcp__n8n_trigger_webhook_workflow, mcp__n8n-mcp__n8n_get_execution, mcp__n8n-mcp__n8n_list_executions, mcp__n8n-mcp__n8n_delete_execution, mcp__n8n-mcp__n8n_health_check, mcp__n8n-mcp__n8n_list_available_tools, mcp__n8n-mcp__n8n_diagnostic
|
||||
model: sonnet
|
||||
---
|
||||
|
||||
You are n8n-mcp-tester, a specialized testing agent for the n8n Model Context Protocol (MCP) server. You validate that MCP tools and functionality work correctly in real-world scenarios after fixes or new features are implemented.
|
||||
|
||||
## Your Core Responsibilities
|
||||
|
||||
You test the n8n-mcp server by:
|
||||
1. Using MCP tools to build, validate, and manipulate n8n workflows
|
||||
2. Verifying that recent fixes resolve the reported issues
|
||||
3. Testing new functionality works as designed
|
||||
4. Reporting clear, actionable results back to the invoking agent
|
||||
|
||||
## Testing Methodology
|
||||
|
||||
When invoked with a test request, you will:
|
||||
|
||||
1. **Understand the Context**: Identify what was fixed or added based on the instructions from the invoking agent
|
||||
|
||||
2. **Design Test Scenarios**: Create specific test cases that:
|
||||
- Target the exact functionality that was changed
|
||||
- Include both positive and negative test cases
|
||||
- Test edge cases and boundary conditions
|
||||
- Use realistic n8n workflow configurations
|
||||
|
||||
3. **Execute Tests Using MCP Tools**: You have access to all n8n-mcp tools including:
|
||||
- `search_nodes`: Find relevant n8n nodes
|
||||
- `get_node_info`: Get detailed node configuration
|
||||
- `get_node_essentials`: Get simplified node information
|
||||
- `validate_node_config`: Validate node configurations
|
||||
- `n8n_validate_workflow`: Validate complete workflows
|
||||
- `get_node_example`: Get working examples
|
||||
- `search_templates`: Find workflow templates
|
||||
- Additional tools as available in the MCP server
|
||||
|
||||
4. **Verify Expected Behavior**:
|
||||
- Confirm fixes resolve the original issue
|
||||
- Verify new features work as documented
|
||||
- Check for regressions in related functionality
|
||||
- Test error handling and edge cases
|
||||
|
||||
5. **Report Results**: Provide clear feedback including:
|
||||
- What was tested (specific tools and scenarios)
|
||||
- Whether the fix/feature works as expected
|
||||
- Any unexpected behaviors or issues discovered
|
||||
- Specific error messages if failures occur
|
||||
- Recommendations for additional testing if needed
|
||||
|
||||
## Testing Guidelines
|
||||
|
||||
- **Be Thorough**: Test multiple variations and edge cases
|
||||
- **Be Specific**: Use exact node types, properties, and configurations mentioned in the fix
|
||||
- **Be Realistic**: Create test scenarios that mirror actual n8n usage
|
||||
- **Be Clear**: Report results in a structured, easy-to-understand format
|
||||
- **Be Efficient**: Focus testing on the changed functionality first
|
||||
|
||||
## Example Test Execution
|
||||
|
||||
If testing a validation fix for HTTP Request nodes:
|
||||
1. Call `tools_documentation` to get a list of available tools and get documentation on `search_nodes` tool.
|
||||
2. Search for HTTP Request node using `search_nodes`
|
||||
3. Get node configuration with `get_node_info` or `get_node_essentials`
|
||||
4. Create test configurations that previously failed
|
||||
5. Validate using `validate_node_config` with different profiles
|
||||
6. Test in a complete workflow using `n8n_validate_workflow`
|
||||
6. Report whether validation now works correctly
|
||||
|
||||
## Important Constraints
|
||||
|
||||
- You can only test using the MCP tools available in the server
|
||||
- You cannot modify code or files - only test existing functionality
|
||||
- You must work with the current state of the MCP server (already reloaded)
|
||||
- Focus on functional testing, not unit testing
|
||||
- Report issues objectively without attempting to fix them
|
||||
|
||||
## Response Format
|
||||
|
||||
Structure your test results as:
|
||||
|
||||
```
|
||||
### Test Report: [Feature/Fix Name]
|
||||
|
||||
**Test Objective**: [What was being tested]
|
||||
|
||||
**Test Scenarios**:
|
||||
1. [Scenario 1]: ✅/❌ [Result]
|
||||
2. [Scenario 2]: ✅/❌ [Result]
|
||||
|
||||
**Findings**:
|
||||
- [Key finding 1]
|
||||
- [Key finding 2]
|
||||
|
||||
**Conclusion**: [Overall assessment - works as expected / issues found]
|
||||
|
||||
**Details**: [Any error messages, unexpected behaviors, or additional context]
|
||||
```
|
||||
|
||||
Remember: Your role is to validate that the n8n-mcp server works correctly in practice, providing confidence that fixes and new features function as intended before deployment.
|
||||
14
README.md
14
README.md
@@ -2,11 +2,11 @@
|
||||
|
||||
[](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://codecov.io/gh/czlonkowski/n8n-mcp)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/actions)
|
||||
[](https://github.com/n8n-io/n8n)
|
||||
[](https://github.com/czlonkowski/n8n-mcp/pkgs/container/n8n-mcp)
|
||||
[](https://railway.com/deploy/n8n-mcp?referralCode=n8n-mcp)
|
||||
|
||||
@@ -16,7 +16,7 @@ A Model Context Protocol (MCP) server that provides AI assistants with comprehen
|
||||
|
||||
n8n-MCP serves as a bridge between n8n's workflow automation platform and AI models, enabling them to understand and work with n8n nodes effectively. It provides structured access to:
|
||||
|
||||
- 📚 **532 n8n nodes** from both n8n-nodes-base and @n8n/n8n-nodes-langchain
|
||||
- 📚 **535 n8n nodes** from both n8n-nodes-base and @n8n/n8n-nodes-langchain
|
||||
- 🔧 **Node properties** - 99% coverage with detailed schemas
|
||||
- ⚡ **Node operations** - 63.6% coverage of available actions
|
||||
- 📄 **Documentation** - 90% coverage from official n8n docs (including AI nodes)
|
||||
@@ -663,10 +663,10 @@ npm run dev:http # HTTP dev mode
|
||||
|
||||
## 📊 Metrics & Coverage
|
||||
|
||||
Current database coverage (n8n v1.103.2):
|
||||
Current database coverage (n8n v1.106.3):
|
||||
|
||||
- ✅ **532/532** nodes loaded (100%)
|
||||
- ✅ **525** nodes with properties (98.7%)
|
||||
- ✅ **535/535** nodes loaded (100%)
|
||||
- ✅ **528** nodes with properties (98.7%)
|
||||
- ✅ **470** nodes with documentation (88%)
|
||||
- ✅ **267** AI-capable tools detected
|
||||
- ✅ **AI Agent & LangChain nodes** fully documented
|
||||
|
||||
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -7,6 +7,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2.10.8] - 2025-09-04
|
||||
|
||||
### Updated
|
||||
- **n8n Dependencies**: Updated to latest versions for compatibility and new features
|
||||
- n8n: 1.107.4 → 1.109.2
|
||||
- @n8n/n8n-nodes-langchain: 1.106.2 → 1.109.1
|
||||
- n8n-nodes-base: 1.106.3 → 1.108.0 (via dependencies)
|
||||
- **Node Database**: Rebuilt with 535 nodes from updated n8n packages
|
||||
- **Node.js Compatibility**: Optimized for Node.js v22.17.0 LTS
|
||||
- Enhanced better-sqlite3 native binary compatibility
|
||||
- Fixed SQL.js fallback mode for environments without native binaries
|
||||
- **CI/CD Improvements**: Fixed Rollup native module compatibility for GitHub Actions
|
||||
- Added explicit platform-specific rollup binaries for cross-platform builds
|
||||
- Resolved npm ci failures in Linux CI environment
|
||||
- Fixed package-lock.json synchronization issues
|
||||
- **Platform Support**: Enhanced cross-platform deployment compatibility
|
||||
- macOS ARM64 and Linux x64 platform binaries included
|
||||
- Improved npm package distribution with proper dependency resolution
|
||||
- All 1,728+ tests passing with updated dependencies
|
||||
|
||||
### Fixed
|
||||
- **CI/CD Pipeline**: Resolved test failures in GitHub Actions
|
||||
- Fixed pyodide version conflicts between langchain dependencies
|
||||
- Regenerated package-lock.json with proper dependency resolution
|
||||
- Fixed Rollup native module loading in Linux CI environment
|
||||
- **Database Compatibility**: Enhanced SQL.js fallback reliability
|
||||
- Improved parameter binding and state management
|
||||
- Fixed statement cleanup to prevent memory leaks
|
||||
- **Deployment Reliability**: Better handling of platform-specific dependencies
|
||||
- npm ci now works consistently across development and CI environments
|
||||
|
||||
## [2.10.5] - 2025-08-20
|
||||
|
||||
### Updated
|
||||
- **n8n Dependencies**: Updated to latest versions for compatibility and new features
|
||||
- n8n: 1.106.3 → 1.107.4
|
||||
- n8n-core: 1.105.3 → 1.106.2
|
||||
- n8n-workflow: 1.103.3 → 1.104.1
|
||||
- @n8n/n8n-nodes-langchain: 1.105.3 → 1.106.2
|
||||
- **Node Database**: Rebuilt with 535 nodes from updated n8n packages
|
||||
- All tests passing with updated dependencies
|
||||
|
||||
## [2.10.4] - 2025-08-12
|
||||
|
||||
### Updated
|
||||
- **n8n Dependencies**: Updated to latest versions for compatibility and new features
|
||||
- n8n: 1.105.2 → 1.106.3
|
||||
- n8n-core: 1.104.1 → 1.105.3
|
||||
- n8n-workflow: 1.102.1 → 1.103.3
|
||||
- @n8n/n8n-nodes-langchain: 1.104.1 → 1.105.3
|
||||
- **Node Database**: Rebuilt with 535 nodes from updated n8n packages
|
||||
- All 1,728 tests passing with updated dependencies
|
||||
|
||||
## [2.10.3] - 2025-08-07
|
||||
|
||||
### Fixed
|
||||
@@ -1143,6 +1196,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Basic n8n and MCP integration
|
||||
- Core workflow automation features
|
||||
|
||||
[2.10.4]: https://github.com/czlonkowski/n8n-mcp/compare/v2.10.3...v2.10.4
|
||||
[2.10.3]: https://github.com/czlonkowski/n8n-mcp/compare/v2.10.2...v2.10.3
|
||||
[2.10.2]: https://github.com/czlonkowski/n8n-mcp/compare/v2.10.1...v2.10.2
|
||||
[2.10.1]: https://github.com/czlonkowski/n8n-mcp/compare/v2.10.0...v2.10.1
|
||||
[2.10.0]: https://github.com/czlonkowski/n8n-mcp/compare/v2.9.1...v2.10.0
|
||||
|
||||
26329
package-lock.json
generated
26329
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "n8n-mcp",
|
||||
"version": "2.10.3",
|
||||
"version": "2.10.8",
|
||||
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
@@ -128,16 +128,18 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.13.2",
|
||||
"@n8n/n8n-nodes-langchain": "^1.104.1",
|
||||
"@n8n/n8n-nodes-langchain": "^1.108.1",
|
||||
"dotenv": "^16.5.0",
|
||||
"express": "^5.1.0",
|
||||
"n8n": "^1.105.2",
|
||||
"n8n-core": "^1.104.1",
|
||||
"n8n-workflow": "^1.102.1",
|
||||
"n8n": "^1.109.2",
|
||||
"n8n-core": "^1.108.0",
|
||||
"n8n-workflow": "^1.106.0",
|
||||
"sql.js": "^1.13.0",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-darwin-arm64": "^4.50.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "^4.50.0",
|
||||
"better-sqlite3": "^11.10.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import { getToolDocumentation } from '../src/mcp/tools-documentation';
|
||||
import { ExampleGenerator } from '../src/services/example-generator';
|
||||
import { EnhancedConfigValidator } from '../src/services/enhanced-config-validator';
|
||||
|
||||
const dbPath = process.env.NODE_DB_PATH || './nodes.db';
|
||||
const dbPath = process.env.NODE_DB_PATH || './data/nodes.db';
|
||||
|
||||
async function main() {
|
||||
console.log('🧪 Testing Code Node Documentation Fixes\n');
|
||||
|
||||
@@ -376,27 +376,36 @@ class SQLJSStatement implements PreparedStatement {
|
||||
constructor(private stmt: any, private onModify: () => void) {}
|
||||
|
||||
run(...params: any[]): RunResult {
|
||||
try {
|
||||
if (params.length > 0) {
|
||||
this.bindParams(params);
|
||||
if (this.boundParams) {
|
||||
this.stmt.bind(this.boundParams);
|
||||
}
|
||||
}
|
||||
|
||||
this.stmt.run();
|
||||
this.onModify();
|
||||
|
||||
// sql.js doesn't provide changes/lastInsertRowid easily
|
||||
return {
|
||||
changes: 0,
|
||||
changes: 1, // Assume success means 1 change
|
||||
lastInsertRowid: 0
|
||||
};
|
||||
} catch (error) {
|
||||
this.stmt.reset();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
get(...params: any[]): any {
|
||||
try {
|
||||
if (params.length > 0) {
|
||||
this.bindParams(params);
|
||||
}
|
||||
|
||||
if (this.boundParams) {
|
||||
this.stmt.bind(this.boundParams);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.stmt.step()) {
|
||||
const result = this.stmt.getAsObject();
|
||||
@@ -406,14 +415,20 @@ class SQLJSStatement implements PreparedStatement {
|
||||
|
||||
this.stmt.reset();
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
this.stmt.reset();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
all(...params: any[]): any[] {
|
||||
try {
|
||||
if (params.length > 0) {
|
||||
this.bindParams(params);
|
||||
}
|
||||
|
||||
if (this.boundParams) {
|
||||
this.stmt.bind(this.boundParams);
|
||||
}
|
||||
}
|
||||
|
||||
const results: any[] = [];
|
||||
while (this.stmt.step()) {
|
||||
@@ -422,6 +437,10 @@ class SQLJSStatement implements PreparedStatement {
|
||||
|
||||
this.stmt.reset();
|
||||
return results;
|
||||
} catch (error) {
|
||||
this.stmt.reset();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
iterate(...params: any[]): IterableIterator<any> {
|
||||
@@ -455,12 +474,18 @@ class SQLJSStatement implements PreparedStatement {
|
||||
}
|
||||
|
||||
private bindParams(params: any[]): void {
|
||||
if (params.length === 1 && typeof params[0] === 'object' && !Array.isArray(params[0])) {
|
||||
if (params.length === 0) {
|
||||
this.boundParams = null;
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.length === 1 && typeof params[0] === 'object' && !Array.isArray(params[0]) && params[0] !== null) {
|
||||
// Named parameters passed as object
|
||||
this.boundParams = params[0];
|
||||
} else {
|
||||
// Positional parameters - sql.js uses array for positional
|
||||
this.boundParams = params;
|
||||
// Filter out undefined values that might cause issues
|
||||
this.boundParams = params.map(p => p === undefined ? null : p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -282,6 +282,7 @@ export async function handleGetWorkflowStructure(args: unknown): Promise<McpTool
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
isArchived: workflow.isArchived,
|
||||
nodes: simplifiedNodes,
|
||||
connections: workflow.connections,
|
||||
nodeCount: workflow.nodes.length,
|
||||
@@ -325,6 +326,7 @@ export async function handleGetWorkflowMinimal(args: unknown): Promise<McpToolRe
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
isArchived: workflow.isArchived,
|
||||
tags: workflow.tags || [],
|
||||
createdAt: workflow.createdAt,
|
||||
updatedAt: workflow.updatedAt
|
||||
@@ -470,6 +472,7 @@ export async function handleListWorkflows(args: unknown): Promise<McpToolRespons
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
active: workflow.active,
|
||||
isArchived: workflow.isArchived,
|
||||
createdAt: workflow.createdAt,
|
||||
updatedAt: workflow.updatedAt,
|
||||
tags: workflow.tags || [],
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
import { createDatabaseAdapter } from '../database/database-adapter';
|
||||
import { N8nNodeLoader } from '../loaders/node-loader';
|
||||
import { NodeParser } from '../parsers/node-parser';
|
||||
import { NodeParser, ParsedNode } from '../parsers/node-parser';
|
||||
import { DocsMapper } from '../mappers/docs-mapper';
|
||||
import { NodeRepository } from '../database/node-repository';
|
||||
import { TemplateSanitizer } from '../utils/template-sanitizer';
|
||||
@@ -46,7 +46,10 @@ async function rebuild() {
|
||||
withDocs: 0
|
||||
};
|
||||
|
||||
// Process each node
|
||||
// Process each node (documentation fetching must be outside transaction due to async)
|
||||
console.log('🔄 Processing nodes...');
|
||||
const processedNodes: Array<{ parsed: ParsedNode; docs: string | undefined; nodeName: string }> = [];
|
||||
|
||||
for (const { packageName, nodeName, NodeClass } of nodes) {
|
||||
try {
|
||||
// Parse node
|
||||
@@ -54,15 +57,34 @@ async function rebuild() {
|
||||
|
||||
// Validate parsed data
|
||||
if (!parsed.nodeType || !parsed.displayName) {
|
||||
throw new Error('Missing required fields');
|
||||
throw new Error(`Missing required fields - nodeType: ${parsed.nodeType}, displayName: ${parsed.displayName}, packageName: ${parsed.packageName}`);
|
||||
}
|
||||
|
||||
// Additional validation for required fields
|
||||
if (!parsed.packageName) {
|
||||
throw new Error(`Missing packageName for node ${nodeName}`);
|
||||
}
|
||||
|
||||
// Get documentation
|
||||
const docs = await mapper.fetchDocumentation(parsed.nodeType);
|
||||
parsed.documentation = docs || undefined;
|
||||
|
||||
// Save to database
|
||||
processedNodes.push({ parsed, docs: docs || undefined, nodeName });
|
||||
} catch (error) {
|
||||
stats.failed++;
|
||||
const errorMessage = (error as Error).message;
|
||||
console.error(`❌ Failed to process ${nodeName}: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Now save all processed nodes to database
|
||||
console.log(`\n💾 Saving ${processedNodes.length} processed nodes to database...`);
|
||||
|
||||
let saved = 0;
|
||||
for (const { parsed, docs, nodeName } of processedNodes) {
|
||||
try {
|
||||
repository.saveNode(parsed);
|
||||
saved++;
|
||||
|
||||
// Update statistics
|
||||
stats.successful++;
|
||||
@@ -76,14 +98,29 @@ async function rebuild() {
|
||||
console.log(`✅ ${parsed.nodeType} [Props: ${parsed.properties.length}, Ops: ${parsed.operations.length}]`);
|
||||
} catch (error) {
|
||||
stats.failed++;
|
||||
console.error(`❌ Failed to process ${nodeName}: ${(error as Error).message}`);
|
||||
const errorMessage = (error as Error).message;
|
||||
console.error(`❌ Failed to save ${nodeName}: ${errorMessage}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`💾 Save completed: ${saved} nodes saved successfully`);
|
||||
|
||||
// Validation check
|
||||
console.log('\n🔍 Running validation checks...');
|
||||
try {
|
||||
const validationResults = validateDatabase(repository);
|
||||
|
||||
if (!validationResults.passed) {
|
||||
console.log('⚠️ Validation Issues:');
|
||||
validationResults.issues.forEach(issue => console.log(` - ${issue}`));
|
||||
} else {
|
||||
console.log('✅ All validation checks passed');
|
||||
}
|
||||
} catch (validationError) {
|
||||
console.error('❌ Validation failed:', (validationError as Error).message);
|
||||
console.log('⚠️ Skipping validation due to database compatibility issues');
|
||||
}
|
||||
|
||||
// Summary
|
||||
console.log('\n📊 Summary:');
|
||||
console.log(` Total nodes: ${nodes.length}`);
|
||||
@@ -96,11 +133,6 @@ async function rebuild() {
|
||||
console.log(` With Operations: ${stats.withOperations}`);
|
||||
console.log(` With Documentation: ${stats.withDocs}`);
|
||||
|
||||
if (!validationResults.passed) {
|
||||
console.log('\n⚠️ Validation Issues:');
|
||||
validationResults.issues.forEach(issue => console.log(` - ${issue}`));
|
||||
}
|
||||
|
||||
// Sanitize templates if they exist
|
||||
console.log('\n🧹 Checking for templates to sanitize...');
|
||||
const templateCount = db.prepare('SELECT COUNT(*) as count FROM templates').get() as { count: number };
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface Workflow {
|
||||
nodes: WorkflowNode[];
|
||||
connections: WorkflowConnection;
|
||||
active?: boolean; // Optional for creation as it's read-only
|
||||
isArchived?: boolean; // Optional, available in newer n8n versions
|
||||
settings?: WorkflowSettings;
|
||||
staticData?: Record<string, unknown>;
|
||||
tags?: string[];
|
||||
|
||||
@@ -109,16 +109,16 @@ describe('MCP Error Handling', () => {
|
||||
});
|
||||
|
||||
it('should handle empty search query', async () => {
|
||||
// Empty query returns empty results
|
||||
const response = await client.callTool({ name: 'search_nodes', arguments: {
|
||||
try {
|
||||
await client.callTool({ name: 'search_nodes', arguments: {
|
||||
query: ''
|
||||
} });
|
||||
|
||||
const result = JSON.parse((response as any).content[0].text);
|
||||
// search_nodes returns 'results' not 'nodes'
|
||||
expect(result).toHaveProperty('results');
|
||||
expect(Array.isArray(result.results)).toBe(true);
|
||||
expect(result.results).toHaveLength(0);
|
||||
expect.fail('Should have thrown an error');
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toContain("search_nodes: Validation failed:");
|
||||
expect(error.message).toContain("query: query cannot be empty");
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle non-existent node types', async () => {
|
||||
@@ -149,19 +149,19 @@ describe('MCP Error Handling', () => {
|
||||
});
|
||||
|
||||
it('should handle malformed workflow structure', async () => {
|
||||
const response = await client.callTool({ name: 'validate_workflow', arguments: {
|
||||
try {
|
||||
await client.callTool({ name: 'validate_workflow', arguments: {
|
||||
workflow: {
|
||||
// Missing required 'nodes' array
|
||||
connections: {}
|
||||
}
|
||||
} });
|
||||
|
||||
// Should return validation error, not throw
|
||||
const validation = JSON.parse((response as any).content[0].text);
|
||||
expect(validation.valid).toBe(false);
|
||||
expect(validation.errors).toBeDefined();
|
||||
expect(validation.errors.length).toBeGreaterThan(0);
|
||||
expect(validation.errors[0].message).toContain('nodes');
|
||||
expect.fail('Should have thrown an error');
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
expect(error.message).toContain("validate_workflow: Validation failed:");
|
||||
expect(error.message).toContain("workflow.nodes: workflow.nodes is required");
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle circular workflow references', async () => {
|
||||
@@ -501,7 +501,8 @@ describe('MCP Error Handling', () => {
|
||||
} catch (error: any) {
|
||||
expect(error).toBeDefined();
|
||||
// The error now properly validates required parameters
|
||||
expect(error.message).toContain("Missing required parameters");
|
||||
expect(error.message).toContain("search_nodes: Validation failed:");
|
||||
expect(error.message).toContain("query: query is required");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ describe('Parameter Validation', () => {
|
||||
describe('search_nodes', () => {
|
||||
it('should require query parameter', async () => {
|
||||
await expect(server.testExecuteTool('search_nodes', {}))
|
||||
.rejects.toThrow('Missing required parameters for search_nodes: query');
|
||||
.rejects.toThrow('search_nodes: Validation failed:\n • query: query is required');
|
||||
});
|
||||
|
||||
it('should succeed with valid query', async () => {
|
||||
@@ -194,29 +194,28 @@ describe('Parameter Validation', () => {
|
||||
expect(result).toEqual({ results: [] });
|
||||
});
|
||||
|
||||
it('should convert limit to number and use default on invalid value', async () => {
|
||||
const result = await server.testExecuteTool('search_nodes', {
|
||||
it('should reject invalid limit value', async () => {
|
||||
await expect(server.testExecuteTool('search_nodes', {
|
||||
query: 'http',
|
||||
limit: 'invalid'
|
||||
});
|
||||
expect(result).toEqual({ results: [] });
|
||||
})).rejects.toThrow('search_nodes: Validation failed:\n • limit: limit must be a number, got string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate_node_operation', () => {
|
||||
it('should require nodeType and config parameters', async () => {
|
||||
await expect(server.testExecuteTool('validate_node_operation', {}))
|
||||
.rejects.toThrow('Missing required parameters for validate_node_operation: nodeType, config');
|
||||
.rejects.toThrow('validate_node_operation: Validation failed:\n • nodeType: nodeType is required\n • config: config is required');
|
||||
});
|
||||
|
||||
it('should require nodeType parameter when config is provided', async () => {
|
||||
await expect(server.testExecuteTool('validate_node_operation', { config: {} }))
|
||||
.rejects.toThrow('Missing required parameters for validate_node_operation: nodeType');
|
||||
.rejects.toThrow('validate_node_operation: Validation failed:\n • nodeType: nodeType is required');
|
||||
});
|
||||
|
||||
it('should require config parameter when nodeType is provided', async () => {
|
||||
await expect(server.testExecuteTool('validate_node_operation', { nodeType: 'nodes-base.httpRequest' }))
|
||||
.rejects.toThrow('Missing required parameters for validate_node_operation: config');
|
||||
.rejects.toThrow('validate_node_operation: Validation failed:\n • config: config is required');
|
||||
});
|
||||
|
||||
it('should succeed with valid parameters', async () => {
|
||||
@@ -255,7 +254,7 @@ describe('Parameter Validation', () => {
|
||||
describe('list_node_templates', () => {
|
||||
it('should require nodeTypes parameter', async () => {
|
||||
await expect(server.testExecuteTool('list_node_templates', {}))
|
||||
.rejects.toThrow('Missing required parameters for list_node_templates: nodeTypes');
|
||||
.rejects.toThrow('list_node_templates: Validation failed:\n • nodeTypes: nodeTypes is required');
|
||||
});
|
||||
|
||||
it('should succeed with valid nodeTypes array', async () => {
|
||||
@@ -290,26 +289,18 @@ describe('Parameter Validation', () => {
|
||||
});
|
||||
|
||||
describe('limit parameter conversion', () => {
|
||||
it('should convert string numbers to numbers', async () => {
|
||||
const mockSearchNodes = vi.spyOn(server as any, 'searchNodes');
|
||||
|
||||
await server.testExecuteTool('search_nodes', {
|
||||
it('should reject string limit values', async () => {
|
||||
await expect(server.testExecuteTool('search_nodes', {
|
||||
query: 'test',
|
||||
limit: '15'
|
||||
})).rejects.toThrow('search_nodes: Validation failed:\n • limit: limit must be a number, got string');
|
||||
});
|
||||
|
||||
expect(mockSearchNodes).toHaveBeenCalledWith('test', 15, { mode: undefined });
|
||||
});
|
||||
|
||||
it('should use default when limit is invalid string', async () => {
|
||||
const mockSearchNodes = vi.spyOn(server as any, 'searchNodes');
|
||||
|
||||
await server.testExecuteTool('search_nodes', {
|
||||
it('should reject invalid string limit values', async () => {
|
||||
await expect(server.testExecuteTool('search_nodes', {
|
||||
query: 'test',
|
||||
limit: 'invalid'
|
||||
});
|
||||
|
||||
expect(mockSearchNodes).toHaveBeenCalledWith('test', 20, { mode: undefined });
|
||||
})).rejects.toThrow('search_nodes: Validation failed:\n • limit: limit must be a number, got string');
|
||||
});
|
||||
|
||||
it('should use default when limit is undefined', async () => {
|
||||
@@ -322,15 +313,11 @@ describe('Parameter Validation', () => {
|
||||
expect(mockSearchNodes).toHaveBeenCalledWith('test', 20, { mode: undefined });
|
||||
});
|
||||
|
||||
it('should handle zero as valid limit', async () => {
|
||||
const mockSearchNodes = vi.spyOn(server as any, 'searchNodes');
|
||||
|
||||
await server.testExecuteTool('search_nodes', {
|
||||
it('should reject zero as limit due to minimum constraint', async () => {
|
||||
await expect(server.testExecuteTool('search_nodes', {
|
||||
query: 'test',
|
||||
limit: 0
|
||||
});
|
||||
|
||||
expect(mockSearchNodes).toHaveBeenCalledWith('test', 20, { mode: undefined }); // 0 converts to falsy, uses default
|
||||
})).rejects.toThrow('search_nodes: Validation failed:\n • limit: limit must be at least 1, got 0');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -361,26 +348,18 @@ describe('Parameter Validation', () => {
|
||||
});
|
||||
|
||||
describe('templateLimit parameter conversion', () => {
|
||||
it('should convert string numbers to numbers', async () => {
|
||||
const mockListNodeTemplates = vi.spyOn(server as any, 'listNodeTemplates');
|
||||
|
||||
await server.testExecuteTool('list_node_templates', {
|
||||
it('should reject string limit values', async () => {
|
||||
await expect(server.testExecuteTool('list_node_templates', {
|
||||
nodeTypes: ['nodes-base.httpRequest'],
|
||||
limit: '5'
|
||||
})).rejects.toThrow('list_node_templates: Validation failed:\n • limit: limit must be a number, got string');
|
||||
});
|
||||
|
||||
expect(mockListNodeTemplates).toHaveBeenCalledWith(['nodes-base.httpRequest'], 5);
|
||||
});
|
||||
|
||||
it('should use default when templateLimit is invalid', async () => {
|
||||
const mockListNodeTemplates = vi.spyOn(server as any, 'listNodeTemplates');
|
||||
|
||||
await server.testExecuteTool('list_node_templates', {
|
||||
it('should reject invalid string limit values', async () => {
|
||||
await expect(server.testExecuteTool('list_node_templates', {
|
||||
nodeTypes: ['nodes-base.httpRequest'],
|
||||
limit: 'invalid'
|
||||
});
|
||||
|
||||
expect(mockListNodeTemplates).toHaveBeenCalledWith(['nodes-base.httpRequest'], 10);
|
||||
})).rejects.toThrow('list_node_templates: Validation failed:\n • limit: limit must be a number, got string');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -452,7 +431,7 @@ describe('Parameter Validation', () => {
|
||||
it('should list all missing parameters', () => {
|
||||
expect(() => {
|
||||
server.testValidateToolParams('validate_node_operation', { profile: 'strict' }, ['nodeType', 'config']);
|
||||
}).toThrow('Missing required parameters for validate_node_operation: nodeType, config');
|
||||
}).toThrow('validate_node_operation: Validation failed:\n • nodeType: nodeType is required\n • config: config is required');
|
||||
});
|
||||
|
||||
it('should include helpful guidance', () => {
|
||||
@@ -475,10 +454,10 @@ describe('Parameter Validation', () => {
|
||||
.rejects.toThrow('Missing required parameters for get_node_info: nodeType');
|
||||
|
||||
await expect(server.testExecuteTool('search_nodes', {}))
|
||||
.rejects.toThrow('Missing required parameters for search_nodes: query');
|
||||
.rejects.toThrow('search_nodes: Validation failed:\n • query: query is required');
|
||||
|
||||
await expect(server.testExecuteTool('validate_node_operation', { nodeType: 'test' }))
|
||||
.rejects.toThrow('Missing required parameters for validate_node_operation: config');
|
||||
.rejects.toThrow('validate_node_operation: Validation failed:\n • config: config is required');
|
||||
});
|
||||
|
||||
it('should handle edge cases in parameter validation gracefully', async () => {
|
||||
@@ -492,24 +471,34 @@ describe('Parameter Validation', () => {
|
||||
});
|
||||
|
||||
it('should provide consistent error format across all tools', async () => {
|
||||
const toolsWithRequiredParams = [
|
||||
{ name: 'get_node_info', args: {}, missing: 'nodeType' },
|
||||
{ name: 'search_nodes', args: {}, missing: 'query' },
|
||||
{ name: 'get_node_documentation', args: {}, missing: 'nodeType' },
|
||||
{ name: 'get_node_essentials', args: {}, missing: 'nodeType' },
|
||||
{ name: 'search_node_properties', args: {}, missing: 'nodeType, query' },
|
||||
{ name: 'get_node_for_task', args: {}, missing: 'task' },
|
||||
{ name: 'validate_node_operation', args: {}, missing: 'nodeType, config' },
|
||||
{ name: 'validate_node_minimal', args: {}, missing: 'nodeType, config' },
|
||||
{ name: 'get_property_dependencies', args: {}, missing: 'nodeType' },
|
||||
{ name: 'get_node_as_tool_info', args: {}, missing: 'nodeType' },
|
||||
{ name: 'list_node_templates', args: {}, missing: 'nodeTypes' },
|
||||
{ name: 'get_template', args: {}, missing: 'templateId' },
|
||||
// Tools using legacy validation
|
||||
const legacyValidationTools = [
|
||||
{ name: 'get_node_info', args: {}, expected: 'Missing required parameters for get_node_info: nodeType' },
|
||||
{ name: 'get_node_documentation', args: {}, expected: 'Missing required parameters for get_node_documentation: nodeType' },
|
||||
{ name: 'get_node_essentials', args: {}, expected: 'Missing required parameters for get_node_essentials: nodeType' },
|
||||
{ name: 'search_node_properties', args: {}, expected: 'Missing required parameters for search_node_properties: nodeType, query' },
|
||||
{ name: 'get_node_for_task', args: {}, expected: 'Missing required parameters for get_node_for_task: task' },
|
||||
{ name: 'get_property_dependencies', args: {}, expected: 'Missing required parameters for get_property_dependencies: nodeType' },
|
||||
{ name: 'get_node_as_tool_info', args: {}, expected: 'Missing required parameters for get_node_as_tool_info: nodeType' },
|
||||
{ name: 'get_template', args: {}, expected: 'Missing required parameters for get_template: templateId' },
|
||||
];
|
||||
|
||||
for (const tool of toolsWithRequiredParams) {
|
||||
for (const tool of legacyValidationTools) {
|
||||
await expect(server.testExecuteTool(tool.name, tool.args))
|
||||
.rejects.toThrow(`Missing required parameters for ${tool.name}: ${tool.missing}`);
|
||||
.rejects.toThrow(tool.expected);
|
||||
}
|
||||
|
||||
// Tools using new schema validation
|
||||
const schemaValidationTools = [
|
||||
{ name: 'search_nodes', args: {}, expected: 'search_nodes: Validation failed:\n • query: query is required' },
|
||||
{ name: 'validate_node_operation', args: {}, expected: 'validate_node_operation: Validation failed:\n • nodeType: nodeType is required\n • config: config is required' },
|
||||
{ name: 'validate_node_minimal', args: {}, expected: 'validate_node_minimal: Validation failed:\n • nodeType: nodeType is required\n • config: config is required' },
|
||||
{ name: 'list_node_templates', args: {}, expected: 'list_node_templates: Validation failed:\n • nodeTypes: nodeTypes is required' },
|
||||
];
|
||||
|
||||
for (const tool of schemaValidationTools) {
|
||||
await expect(server.testExecuteTool(tool.name, tool.args))
|
||||
.rejects.toThrow(tool.expected);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -540,23 +529,28 @@ describe('Parameter Validation', () => {
|
||||
}));
|
||||
|
||||
const n8nToolsWithRequiredParams = [
|
||||
{ name: 'n8n_create_workflow', args: {}, missing: 'name, nodes, connections' },
|
||||
{ name: 'n8n_get_workflow', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_get_workflow_details', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_get_workflow_structure', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_get_workflow_minimal', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_update_full_workflow', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_update_partial_workflow', args: {}, missing: 'id, operations' },
|
||||
{ name: 'n8n_delete_workflow', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_validate_workflow', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_trigger_webhook_workflow', args: {}, missing: 'webhookUrl' },
|
||||
{ name: 'n8n_get_execution', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_delete_execution', args: {}, missing: 'id' },
|
||||
{ name: 'n8n_create_workflow', args: {}, expected: 'n8n_create_workflow: Validation failed:\n • name: name is required\n • nodes: nodes is required\n • connections: connections is required' },
|
||||
{ name: 'n8n_get_workflow', args: {}, expected: 'n8n_get_workflow: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_get_workflow_details', args: {}, expected: 'n8n_get_workflow_details: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_get_workflow_structure', args: {}, expected: 'n8n_get_workflow_structure: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_get_workflow_minimal', args: {}, expected: 'n8n_get_workflow_minimal: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_update_full_workflow', args: {}, expected: 'n8n_update_full_workflow: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_delete_workflow', args: {}, expected: 'n8n_delete_workflow: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_validate_workflow', args: {}, expected: 'n8n_validate_workflow: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_get_execution', args: {}, expected: 'n8n_get_execution: Validation failed:\n • id: id is required' },
|
||||
{ name: 'n8n_delete_execution', args: {}, expected: 'n8n_delete_execution: Validation failed:\n • id: id is required' },
|
||||
];
|
||||
|
||||
// n8n_update_partial_workflow and n8n_trigger_webhook_workflow use legacy validation
|
||||
await expect(server.testExecuteTool('n8n_update_partial_workflow', {}))
|
||||
.rejects.toThrow('Missing required parameters for n8n_update_partial_workflow: id, operations');
|
||||
|
||||
await expect(server.testExecuteTool('n8n_trigger_webhook_workflow', {}))
|
||||
.rejects.toThrow('Missing required parameters for n8n_trigger_webhook_workflow: webhookUrl');
|
||||
|
||||
for (const tool of n8nToolsWithRequiredParams) {
|
||||
await expect(server.testExecuteTool(tool.name, tool.args))
|
||||
.rejects.toThrow(`Missing required parameters for ${tool.name}: ${tool.missing}`);
|
||||
.rejects.toThrow(tool.expected);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -46,7 +46,8 @@ describe('Validation System Fixes', () => {
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
delete process.env.NODE_ENV;
|
||||
// Reset NODE_ENV instead of deleting it
|
||||
delete (process.env as any).NODE_ENV;
|
||||
});
|
||||
|
||||
describe('Issue #73: validate_node_minimal crashes without input validation', () => {
|
||||
@@ -146,7 +147,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '1',
|
||||
name: 'Webhook',
|
||||
type: 'n8n-nodes-base.webhook',
|
||||
position: [100, 200],
|
||||
position: [100, 200] as [number, number],
|
||||
parameters: { path: '/test', httpMethod: 'POST' },
|
||||
typeVersion: 1
|
||||
},
|
||||
@@ -154,7 +155,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '2',
|
||||
name: 'Set',
|
||||
type: 'n8n-nodes-base.set',
|
||||
position: [300, 200],
|
||||
position: [300, 200] as [number, number],
|
||||
parameters: { values: {} },
|
||||
typeVersion: 1
|
||||
}
|
||||
@@ -186,7 +187,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '1',
|
||||
name: 'Webhook',
|
||||
type: 'n8n-nodes-base.webhook',
|
||||
position: [100, 200],
|
||||
position: [100, 200] as [number, number],
|
||||
parameters: { path: '/test', httpMethod: 'POST' },
|
||||
typeVersion: 1
|
||||
},
|
||||
@@ -194,7 +195,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '2',
|
||||
name: 'Sticky Note',
|
||||
type: 'n8n-nodes-base.stickyNote',
|
||||
position: [300, 100],
|
||||
position: [300, 100] as [number, number],
|
||||
parameters: { content: 'This is a note' },
|
||||
typeVersion: 1
|
||||
}
|
||||
@@ -216,7 +217,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '1',
|
||||
name: 'Manual Trigger',
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
position: [100, 200],
|
||||
position: [100, 200] as [number, number],
|
||||
parameters: {},
|
||||
typeVersion: 1
|
||||
},
|
||||
@@ -224,7 +225,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '2',
|
||||
name: 'SplitInBatches',
|
||||
type: 'n8n-nodes-base.splitInBatches',
|
||||
position: [300, 200],
|
||||
position: [300, 200] as [number, number],
|
||||
parameters: { batchSize: 1 },
|
||||
typeVersion: 1
|
||||
},
|
||||
@@ -232,7 +233,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '3',
|
||||
name: 'Set',
|
||||
type: 'n8n-nodes-base.set',
|
||||
position: [500, 200],
|
||||
position: [500, 200] as [number, number],
|
||||
parameters: { values: {} },
|
||||
typeVersion: 1
|
||||
}
|
||||
@@ -270,7 +271,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '1',
|
||||
name: 'Invalid Node',
|
||||
type: 'invalid-node-type',
|
||||
position: [100, 200],
|
||||
position: [100, 200] as [number, number],
|
||||
parameters: {},
|
||||
typeVersion: 1
|
||||
}
|
||||
@@ -296,7 +297,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '1',
|
||||
name: 'Webhook',
|
||||
type: 'n8n-nodes-base.webhook',
|
||||
position: [100, 200],
|
||||
position: [100, 200] as [number, number],
|
||||
parameters: { path: '/test', httpMethod: 'POST' },
|
||||
typeVersion: 1
|
||||
}
|
||||
@@ -328,7 +329,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '1',
|
||||
name: 'Invalid Node 1',
|
||||
type: 'invalid-type-1',
|
||||
position: [100, 200],
|
||||
position: [100, 200] as [number, number],
|
||||
parameters: {}
|
||||
// Missing typeVersion
|
||||
},
|
||||
@@ -336,7 +337,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '2',
|
||||
name: 'Invalid Node 2',
|
||||
type: 'invalid-type-2',
|
||||
position: [300, 200],
|
||||
position: [300, 200] as [number, number],
|
||||
parameters: {}
|
||||
// Missing typeVersion
|
||||
},
|
||||
@@ -344,7 +345,7 @@ describe('Validation System Fixes', () => {
|
||||
id: '3',
|
||||
name: 'Invalid Node 3',
|
||||
type: 'invalid-type-3',
|
||||
position: [500, 200],
|
||||
position: [500, 200] as [number, number],
|
||||
parameters: {}
|
||||
// Missing typeVersion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user