Files
n8n-mcp/tests/unit/mcp/tools-documentation.test.ts
czlonkowski f23fc92c01 fix: make tools-documentation test dynamically read n8n version from package.json
- Removed hardcoded version check in test
- Test now reads actual n8n version from package.json at runtime
- Fixes test failure when n8n version is updated

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-30 16:19:03 +02:00

374 lines
14 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest';
import {
getToolDocumentation,
getToolsOverview,
searchToolDocumentation,
getToolsByCategory,
getAllCategories
} from '@/mcp/tools-documentation';
// Mock the tool-docs import
vi.mock('@/mcp/tool-docs', () => ({
toolsDocumentation: {
search_nodes: {
name: 'search_nodes',
category: 'discovery',
essentials: {
description: 'Search nodes by keywords',
keyParameters: ['query', 'mode', 'limit'],
example: 'search_nodes({query: "slack"})',
performance: 'Instant (<10ms)',
tips: ['Use single words for precision', 'Try FUZZY mode for typos']
},
full: {
description: 'Full-text search across all n8n nodes with multiple matching modes',
parameters: {
query: {
type: 'string',
description: 'Search terms',
required: true
},
mode: {
type: 'string',
description: 'Search mode',
enum: ['OR', 'AND', 'FUZZY'],
default: 'OR'
},
limit: {
type: 'number',
description: 'Max results',
default: 20
}
},
returns: 'Array of matching nodes with metadata',
examples: [
'search_nodes({query: "webhook"})',
'search_nodes({query: "http request", mode: "AND"})'
],
useCases: ['Finding integration nodes', 'Discovering available triggers'],
performance: 'Instant - uses in-memory index',
bestPractices: ['Start with single words', 'Use FUZZY for uncertain names'],
pitfalls: ['Overly specific queries may return no results'],
relatedTools: ['list_nodes', 'get_node_info']
}
},
validate_workflow: {
name: 'validate_workflow',
category: 'validation',
essentials: {
description: 'Validate complete workflow structure',
keyParameters: ['workflow', 'options'],
example: 'validate_workflow(workflow)',
performance: 'Moderate (100-500ms)',
tips: ['Run before deployment', 'Check all validation types']
},
full: {
description: 'Comprehensive workflow validation',
parameters: {
workflow: {
type: 'object',
description: 'Workflow JSON',
required: true
},
options: {
type: 'object',
description: 'Validation options'
}
},
returns: 'Validation results with errors and warnings',
examples: ['validate_workflow(workflow)'],
useCases: ['Pre-deployment checks', 'CI/CD validation'],
performance: 'Depends on workflow complexity',
bestPractices: ['Validate before saving', 'Fix errors first'],
pitfalls: ['Large workflows may take time'],
relatedTools: ['validate_node_operation']
}
},
get_node_essentials: {
name: 'get_node_essentials',
category: 'configuration',
essentials: {
description: 'Get essential node properties only',
keyParameters: ['nodeType'],
example: 'get_node_essentials("nodes-base.slack")',
performance: 'Fast (<100ms)',
tips: ['Use this before get_node_info', 'Returns 95% smaller payload']
},
full: {
description: 'Returns 10-20 most important properties',
parameters: {
nodeType: {
type: 'string',
description: 'Full node type with prefix',
required: true
}
},
returns: 'Essential properties with examples',
examples: ['get_node_essentials("nodes-base.httpRequest")'],
useCases: ['Quick configuration', 'Property discovery'],
performance: 'Fast - pre-filtered data',
bestPractices: ['Always try essentials first'],
pitfalls: ['May not include all advanced options'],
relatedTools: ['get_node_info']
}
}
}
}));
// No need to mock package.json - let the actual module read it
describe('tools-documentation', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('getToolDocumentation', () => {
describe('essentials mode', () => {
it('should return essential documentation for existing tool', () => {
const doc = getToolDocumentation('search_nodes', 'essentials');
expect(doc).toContain('# search_nodes');
expect(doc).toContain('Search nodes by keywords');
expect(doc).toContain('**Example**: search_nodes({query: "slack"})');
expect(doc).toContain('**Key parameters**: query, mode, limit');
expect(doc).toContain('**Performance**: Instant (<10ms)');
expect(doc).toContain('- Use single words for precision');
expect(doc).toContain('- Try FUZZY mode for typos');
expect(doc).toContain('For full documentation, use: tools_documentation({topic: "search_nodes", depth: "full"})');
});
it('should return error message for unknown tool', () => {
const doc = getToolDocumentation('unknown_tool', 'essentials');
expect(doc).toBe("Tool 'unknown_tool' not found. Use tools_documentation() to see available tools.");
});
it('should use essentials as default depth', () => {
const docDefault = getToolDocumentation('search_nodes');
const docEssentials = getToolDocumentation('search_nodes', 'essentials');
expect(docDefault).toBe(docEssentials);
});
});
describe('full mode', () => {
it('should return complete documentation for existing tool', () => {
const doc = getToolDocumentation('search_nodes', 'full');
expect(doc).toContain('# search_nodes');
expect(doc).toContain('Full-text search across all n8n nodes');
expect(doc).toContain('## Parameters');
expect(doc).toContain('- **query** (string, required): Search terms');
expect(doc).toContain('- **mode** (string): Search mode');
expect(doc).toContain('- **limit** (number): Max results');
expect(doc).toContain('## Returns');
expect(doc).toContain('Array of matching nodes with metadata');
expect(doc).toContain('## Examples');
expect(doc).toContain('search_nodes({query: "webhook"})');
expect(doc).toContain('## Common Use Cases');
expect(doc).toContain('- Finding integration nodes');
expect(doc).toContain('## Performance');
expect(doc).toContain('Instant - uses in-memory index');
expect(doc).toContain('## Best Practices');
expect(doc).toContain('- Start with single words');
expect(doc).toContain('## Common Pitfalls');
expect(doc).toContain('- Overly specific queries');
expect(doc).toContain('## Related Tools');
expect(doc).toContain('- list_nodes');
});
});
describe('special documentation topics', () => {
it('should return JavaScript Code node guide for javascript_code_node_guide', () => {
const doc = getToolDocumentation('javascript_code_node_guide', 'essentials');
expect(doc).toContain('# JavaScript Code Node Guide');
expect(doc).toContain('$input.all()');
expect(doc).toContain('DateTime');
});
it('should return Python Code node guide for python_code_node_guide', () => {
const doc = getToolDocumentation('python_code_node_guide', 'essentials');
expect(doc).toContain('# Python Code Node Guide');
expect(doc).toContain('_input.all()');
expect(doc).toContain('_json');
});
it('should return full JavaScript guide when requested', () => {
const doc = getToolDocumentation('javascript_code_node_guide', 'full');
expect(doc).toContain('# JavaScript Code Node Complete Guide');
expect(doc).toContain('## Data Access Patterns');
expect(doc).toContain('## Available Built-in Functions');
expect(doc).toContain('$helpers.httpRequest');
});
it('should return full Python guide when requested', () => {
const doc = getToolDocumentation('python_code_node_guide', 'full');
expect(doc).toContain('# Python Code Node Complete Guide');
expect(doc).toContain('## Available Built-in Modules');
expect(doc).toContain('## Limitations & Workarounds');
expect(doc).toContain('import json');
});
});
});
describe('getToolsOverview', () => {
describe('essentials mode', () => {
it('should return essential overview with categories', () => {
const overview = getToolsOverview('essentials');
expect(overview).toContain('# n8n MCP Tools Reference');
expect(overview).toContain('## Important: Compatibility Notice');
// The tools-documentation module dynamically reads version from package.json
// so we need to read it the same way to match
const packageJson = require('../../../package.json');
const n8nVersion = packageJson.dependencies.n8n.replace(/[^0-9.]/g, '');
expect(overview).toContain(`n8n version ${n8nVersion}`);
expect(overview).toContain('## Code Node Configuration');
expect(overview).toContain('## Standard Workflow Pattern');
expect(overview).toContain('**Discovery Tools**');
expect(overview).toContain('**Configuration Tools**');
expect(overview).toContain('**Validation Tools**');
expect(overview).toContain('## Performance Characteristics');
expect(overview).toContain('- Instant (<10ms)');
expect(overview).toContain('tools_documentation({topic: "tool_name", depth: "full"})');
});
it('should use essentials as default', () => {
const overviewDefault = getToolsOverview();
const overviewEssentials = getToolsOverview('essentials');
expect(overviewDefault).toBe(overviewEssentials);
});
});
describe('full mode', () => {
it('should return complete overview with all tools', () => {
const overview = getToolsOverview('full');
expect(overview).toContain('# n8n MCP Tools - Complete Reference');
expect(overview).toContain('## All Available Tools by Category');
expect(overview).toContain('### Discovery');
expect(overview).toContain('- **search_nodes**: Search nodes by keywords');
expect(overview).toContain('### Validation');
expect(overview).toContain('- **validate_workflow**: Validate complete workflow structure');
expect(overview).toContain('## Usage Notes');
});
});
});
describe('searchToolDocumentation', () => {
it('should find tools matching keyword in name', () => {
const results = searchToolDocumentation('search');
expect(results).toContain('search_nodes');
});
it('should find tools matching keyword in description', () => {
const results = searchToolDocumentation('workflow');
expect(results).toContain('validate_workflow');
});
it('should be case insensitive', () => {
const resultsLower = searchToolDocumentation('search');
const resultsUpper = searchToolDocumentation('SEARCH');
expect(resultsLower).toEqual(resultsUpper);
});
it('should return empty array for no matches', () => {
const results = searchToolDocumentation('nonexistentxyz123');
expect(results).toEqual([]);
});
it('should search in both essentials and full descriptions', () => {
const results = searchToolDocumentation('validation');
expect(results.length).toBeGreaterThan(0);
});
});
describe('getToolsByCategory', () => {
it('should return tools for discovery category', () => {
const tools = getToolsByCategory('discovery');
expect(tools).toContain('search_nodes');
});
it('should return tools for validation category', () => {
const tools = getToolsByCategory('validation');
expect(tools).toContain('validate_workflow');
});
it('should return tools for configuration category', () => {
const tools = getToolsByCategory('configuration');
expect(tools).toContain('get_node_essentials');
});
it('should return empty array for unknown category', () => {
const tools = getToolsByCategory('unknown_category');
expect(tools).toEqual([]);
});
});
describe('getAllCategories', () => {
it('should return all unique categories', () => {
const categories = getAllCategories();
expect(categories).toContain('discovery');
expect(categories).toContain('validation');
expect(categories).toContain('configuration');
});
it('should not have duplicates', () => {
const categories = getAllCategories();
const uniqueCategories = new Set(categories);
expect(categories.length).toBe(uniqueCategories.size);
});
it('should return non-empty array', () => {
const categories = getAllCategories();
expect(categories.length).toBeGreaterThan(0);
});
});
describe('Error Handling', () => {
it('should handle missing tool gracefully', () => {
const doc = getToolDocumentation('missing_tool');
expect(doc).toContain("Tool 'missing_tool' not found");
expect(doc).toContain('Use tools_documentation()');
});
it('should handle empty search query', () => {
const results = searchToolDocumentation('');
// Should match all tools since empty string is in everything
expect(results.length).toBeGreaterThan(0);
});
});
describe('Documentation Quality', () => {
it('should format parameters correctly in full mode', () => {
const doc = getToolDocumentation('search_nodes', 'full');
// Check parameter formatting
expect(doc).toMatch(/- \*\*query\*\* \(string, required\): Search terms/);
expect(doc).toMatch(/- \*\*mode\*\* \(string\): Search mode/);
expect(doc).toMatch(/- \*\*limit\*\* \(number\): Max results/);
});
it('should include code blocks for examples', () => {
const doc = getToolDocumentation('search_nodes', 'full');
expect(doc).toContain('```javascript');
expect(doc).toContain('```');
});
it('should have consistent section headers', () => {
const doc = getToolDocumentation('search_nodes', 'full');
const expectedSections = [
'## Parameters',
'## Returns',
'## Examples',
'## Common Use Cases',
'## Performance',
'## Best Practices',
'## Common Pitfalls',
'## Related Tools'
];
expectedSections.forEach(section => {
expect(doc).toContain(section);
});
});
});
});