test: add comprehensive unit tests for database, parsers, loaders, and MCP tools
- Database layer tests (32 tests): - node-repository.ts: 100% coverage - template-repository.ts: 80.31% coverage - database-adapter.ts: interface compliance tests - Parser tests (99 tests): - node-parser.ts: 93.10% coverage - property-extractor.ts: 95.18% coverage - simple-parser.ts: 91.26% coverage - Fixed parser bugs for version extraction - Loader tests (22 tests): - node-loader.ts: comprehensive mocking tests - MCP tools tests (85 tests): - tools.ts: 100% coverage - tools-documentation.ts: 100% coverage - docs-mapper.ts: 100% coverage Total: 943 tests passing across 32 test files Significant progress from 2.45% to ~30% overall coverage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
377
tests/unit/mcp/tools-documentation.test.ts
Normal file
377
tests/unit/mcp/tools-documentation.test.ts
Normal file
@@ -0,0 +1,377 @@
|
||||
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']
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
// Mock package.json for version info
|
||||
vi.mock('../../package.json', () => ({
|
||||
default: {
|
||||
dependencies: {
|
||||
n8n: '^1.103.2'
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
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');
|
||||
expect(overview).toContain('n8n version 1.103.2');
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
320
tests/unit/mcp/tools.test.ts
Normal file
320
tests/unit/mcp/tools.test.ts
Normal file
@@ -0,0 +1,320 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { n8nDocumentationToolsFinal } from '@/mcp/tools';
|
||||
import { z } from 'zod';
|
||||
|
||||
describe('n8nDocumentationToolsFinal', () => {
|
||||
describe('Tool Structure Validation', () => {
|
||||
it('should have all required properties for each tool', () => {
|
||||
n8nDocumentationToolsFinal.forEach(tool => {
|
||||
// Check required properties exist
|
||||
expect(tool).toHaveProperty('name');
|
||||
expect(tool).toHaveProperty('description');
|
||||
expect(tool).toHaveProperty('inputSchema');
|
||||
|
||||
// Check property types
|
||||
expect(typeof tool.name).toBe('string');
|
||||
expect(typeof tool.description).toBe('string');
|
||||
expect(tool.inputSchema).toBeTypeOf('object');
|
||||
|
||||
// Name should be non-empty
|
||||
expect(tool.name.length).toBeGreaterThan(0);
|
||||
|
||||
// Description should be meaningful
|
||||
expect(tool.description.length).toBeGreaterThan(10);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have unique tool names', () => {
|
||||
const names = n8nDocumentationToolsFinal.map(tool => tool.name);
|
||||
const uniqueNames = new Set(names);
|
||||
expect(names.length).toBe(uniqueNames.size);
|
||||
});
|
||||
|
||||
it('should have valid JSON Schema for all inputSchemas', () => {
|
||||
// Define a minimal JSON Schema validator using Zod
|
||||
const jsonSchemaValidator = z.object({
|
||||
type: z.literal('object'),
|
||||
properties: z.record(z.any()).optional(),
|
||||
required: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
n8nDocumentationToolsFinal.forEach(tool => {
|
||||
expect(() => {
|
||||
jsonSchemaValidator.parse(tool.inputSchema);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Individual Tool Validation', () => {
|
||||
describe('tools_documentation', () => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'tools_documentation');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(tool).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have correct schema', () => {
|
||||
expect(tool?.inputSchema).toMatchObject({
|
||||
type: 'object',
|
||||
properties: {
|
||||
topic: {
|
||||
type: 'string',
|
||||
description: expect.any(String)
|
||||
},
|
||||
depth: {
|
||||
type: 'string',
|
||||
enum: ['essentials', 'full'],
|
||||
description: expect.any(String),
|
||||
default: 'essentials'
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should have helpful description', () => {
|
||||
expect(tool?.description).toContain('documentation');
|
||||
expect(tool?.description).toContain('MCP tools');
|
||||
});
|
||||
});
|
||||
|
||||
describe('list_nodes', () => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'list_nodes');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(tool).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have correct schema properties', () => {
|
||||
const properties = tool?.inputSchema.properties;
|
||||
expect(properties).toHaveProperty('package');
|
||||
expect(properties).toHaveProperty('category');
|
||||
expect(properties).toHaveProperty('developmentStyle');
|
||||
expect(properties).toHaveProperty('isAITool');
|
||||
expect(properties).toHaveProperty('limit');
|
||||
});
|
||||
|
||||
it('should have correct defaults', () => {
|
||||
expect(tool?.inputSchema.properties.limit.default).toBe(50);
|
||||
});
|
||||
|
||||
it('should have proper enum values', () => {
|
||||
expect(tool?.inputSchema.properties.developmentStyle.enum).toEqual(['declarative', 'programmatic']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get_node_info', () => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'get_node_info');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(tool).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have nodeType as required parameter', () => {
|
||||
expect(tool?.inputSchema.required).toContain('nodeType');
|
||||
});
|
||||
|
||||
it('should mention performance implications in description', () => {
|
||||
expect(tool?.description).toMatch(/100KB\+|large|full/i);
|
||||
});
|
||||
});
|
||||
|
||||
describe('search_nodes', () => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'search_nodes');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(tool).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have query as required parameter', () => {
|
||||
expect(tool?.inputSchema.required).toContain('query');
|
||||
});
|
||||
|
||||
it('should have mode enum with correct values', () => {
|
||||
expect(tool?.inputSchema.properties.mode.enum).toEqual(['OR', 'AND', 'FUZZY']);
|
||||
expect(tool?.inputSchema.properties.mode.default).toBe('OR');
|
||||
});
|
||||
|
||||
it('should have limit with default value', () => {
|
||||
expect(tool?.inputSchema.properties.limit.default).toBe(20);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validate_workflow', () => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'validate_workflow');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(tool).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have workflow as required parameter', () => {
|
||||
expect(tool?.inputSchema.required).toContain('workflow');
|
||||
});
|
||||
|
||||
it('should have options with correct validation settings', () => {
|
||||
const options = tool?.inputSchema.properties.options.properties;
|
||||
expect(options).toHaveProperty('validateNodes');
|
||||
expect(options).toHaveProperty('validateConnections');
|
||||
expect(options).toHaveProperty('validateExpressions');
|
||||
expect(options).toHaveProperty('profile');
|
||||
});
|
||||
|
||||
it('should have correct profile enum values', () => {
|
||||
const profile = tool?.inputSchema.properties.options.properties.profile;
|
||||
expect(profile.enum).toEqual(['minimal', 'runtime', 'ai-friendly', 'strict']);
|
||||
expect(profile.default).toBe('runtime');
|
||||
});
|
||||
});
|
||||
|
||||
describe('get_templates_for_task', () => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === 'get_templates_for_task');
|
||||
|
||||
it('should exist', () => {
|
||||
expect(tool).toBeDefined();
|
||||
});
|
||||
|
||||
it('should have task as required parameter', () => {
|
||||
expect(tool?.inputSchema.required).toContain('task');
|
||||
});
|
||||
|
||||
it('should have correct task enum values', () => {
|
||||
const expectedTasks = [
|
||||
'ai_automation',
|
||||
'data_sync',
|
||||
'webhook_processing',
|
||||
'email_automation',
|
||||
'slack_integration',
|
||||
'data_transformation',
|
||||
'file_processing',
|
||||
'scheduling',
|
||||
'api_integration',
|
||||
'database_operations'
|
||||
];
|
||||
expect(tool?.inputSchema.properties.task.enum).toEqual(expectedTasks);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tool Description Quality', () => {
|
||||
it('should have concise descriptions that fit in one line', () => {
|
||||
n8nDocumentationToolsFinal.forEach(tool => {
|
||||
// Descriptions should be informative but not overly long
|
||||
expect(tool.description.length).toBeLessThan(300);
|
||||
});
|
||||
});
|
||||
|
||||
it('should include examples or key information in descriptions', () => {
|
||||
const toolsWithExamples = [
|
||||
'list_nodes',
|
||||
'get_node_info',
|
||||
'search_nodes',
|
||||
'get_node_essentials',
|
||||
'get_node_documentation'
|
||||
];
|
||||
|
||||
toolsWithExamples.forEach(toolName => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
||||
// Should include either example usage, format information, or "nodes-base"
|
||||
expect(tool?.description).toMatch(/example|Example|format|Format|nodes-base|Common:/i);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Schema Consistency', () => {
|
||||
it('should use consistent parameter naming', () => {
|
||||
const toolsWithNodeType = n8nDocumentationToolsFinal.filter(tool =>
|
||||
tool.inputSchema.properties?.nodeType
|
||||
);
|
||||
|
||||
toolsWithNodeType.forEach(tool => {
|
||||
const nodeTypeParam = tool.inputSchema.properties.nodeType;
|
||||
expect(nodeTypeParam.type).toBe('string');
|
||||
// Should mention the prefix requirement
|
||||
expect(nodeTypeParam.description).toMatch(/nodes-base|prefix/i);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have consistent limit parameter defaults', () => {
|
||||
const toolsWithLimit = n8nDocumentationToolsFinal.filter(tool =>
|
||||
tool.inputSchema.properties?.limit
|
||||
);
|
||||
|
||||
toolsWithLimit.forEach(tool => {
|
||||
const limitParam = tool.inputSchema.properties.limit;
|
||||
expect(limitParam.type).toBe('number');
|
||||
expect(limitParam.default).toBeDefined();
|
||||
expect(limitParam.default).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tool Categories Coverage', () => {
|
||||
it('should have tools for all major categories', () => {
|
||||
const categories = {
|
||||
discovery: ['list_nodes', 'search_nodes', 'list_ai_tools'],
|
||||
configuration: ['get_node_info', 'get_node_essentials', 'get_node_documentation'],
|
||||
validation: ['validate_node_operation', 'validate_workflow', 'validate_node_minimal'],
|
||||
templates: ['list_tasks', 'get_node_for_task', 'search_templates'],
|
||||
documentation: ['tools_documentation']
|
||||
};
|
||||
|
||||
Object.entries(categories).forEach(([category, expectedTools]) => {
|
||||
expectedTools.forEach(toolName => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
||||
expect(tool).toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Parameter Validation', () => {
|
||||
it('should have proper type definitions for all parameters', () => {
|
||||
const validTypes = ['string', 'number', 'boolean', 'object', 'array'];
|
||||
|
||||
n8nDocumentationToolsFinal.forEach(tool => {
|
||||
if (tool.inputSchema.properties) {
|
||||
Object.entries(tool.inputSchema.properties).forEach(([paramName, param]) => {
|
||||
expect(validTypes).toContain(param.type);
|
||||
expect(param.description).toBeDefined();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
it('should mark required parameters correctly', () => {
|
||||
const toolsWithRequired = n8nDocumentationToolsFinal.filter(tool =>
|
||||
tool.inputSchema.required && tool.inputSchema.required.length > 0
|
||||
);
|
||||
|
||||
toolsWithRequired.forEach(tool => {
|
||||
tool.inputSchema.required.forEach(requiredParam => {
|
||||
expect(tool.inputSchema.properties).toHaveProperty(requiredParam);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle tools with no parameters', () => {
|
||||
const toolsWithNoParams = ['list_ai_tools', 'get_database_statistics'];
|
||||
|
||||
toolsWithNoParams.forEach(toolName => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
||||
expect(tool).toBeDefined();
|
||||
expect(Object.keys(tool?.inputSchema.properties || {}).length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('should have array parameters defined correctly', () => {
|
||||
const toolsWithArrays = ['list_node_templates'];
|
||||
|
||||
toolsWithArrays.forEach(toolName => {
|
||||
const tool = n8nDocumentationToolsFinal.find(t => t.name === toolName);
|
||||
const arrayParam = tool?.inputSchema.properties.nodeTypes;
|
||||
expect(arrayParam?.type).toBe('array');
|
||||
expect(arrayParam?.items).toBeDefined();
|
||||
expect(arrayParam?.items.type).toBe('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user