Skip to main content
Glama

n8n-MCP

by 88-888
node-parser.test.tsβ€’17.5 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { NodeParser } from '@/parsers/node-parser'; import { PropertyExtractor } from '@/parsers/property-extractor'; import { programmaticNodeFactory, declarativeNodeFactory, triggerNodeFactory, webhookNodeFactory, aiToolNodeFactory, versionedNodeClassFactory, versionedNodeTypeClassFactory, malformedNodeFactory, nodeClassFactory, propertyFactory, stringPropertyFactory, optionsPropertyFactory } from '@tests/fixtures/factories/parser-node.factory'; // Mock PropertyExtractor vi.mock('@/parsers/property-extractor'); describe('NodeParser', () => { let parser: NodeParser; let mockPropertyExtractor: any; beforeEach(() => { vi.clearAllMocks(); // Setup mock property extractor mockPropertyExtractor = { extractProperties: vi.fn().mockReturnValue([]), extractCredentials: vi.fn().mockReturnValue([]), detectAIToolCapability: vi.fn().mockReturnValue(false), extractOperations: vi.fn().mockReturnValue([]) }; (PropertyExtractor as any).mockImplementation(() => mockPropertyExtractor); parser = new NodeParser(); }); describe('parse method', () => { it('should parse correctly when node is programmatic', () => { const nodeDefinition = programmaticNodeFactory.build(); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); mockPropertyExtractor.extractProperties.mockReturnValue(nodeDefinition.properties); mockPropertyExtractor.extractCredentials.mockReturnValue(nodeDefinition.credentials); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result).toMatchObject({ style: 'programmatic', nodeType: `nodes-base.${nodeDefinition.name}`, displayName: nodeDefinition.displayName, description: nodeDefinition.description, category: nodeDefinition.group?.[0] || 'misc', packageName: 'n8n-nodes-base' }); // Check specific properties separately to avoid strict matching expect(result.isVersioned).toBe(false); expect(result.version).toBe(nodeDefinition.version?.toString() || '1'); expect(mockPropertyExtractor.extractProperties).toHaveBeenCalledWith(NodeClass); expect(mockPropertyExtractor.extractCredentials).toHaveBeenCalledWith(NodeClass); }); it('should parse correctly when node is declarative', () => { const nodeDefinition = declarativeNodeFactory.build(); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.style).toBe('declarative'); expect(result.nodeType).toBe(`nodes-base.${nodeDefinition.name}`); }); it('should preserve type when package prefix is already included', () => { const nodeDefinition = programmaticNodeFactory.build({ name: 'nodes-base.slack' }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.nodeType).toBe('nodes-base.slack'); }); it('should set isTrigger flag when node is a trigger', () => { const nodeDefinition = triggerNodeFactory.build(); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isTrigger).toBe(true); }); it('should set isWebhook flag when node is a webhook', () => { const nodeDefinition = webhookNodeFactory.build(); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isWebhook).toBe(true); }); it('should set isAITool flag when node has AI capability', () => { const nodeDefinition = aiToolNodeFactory.build(); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); mockPropertyExtractor.detectAIToolCapability.mockReturnValue(true); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isAITool).toBe(true); }); it('should parse correctly when node uses VersionedNodeType class', () => { // Create a simple versioned node class without modifying function properties const VersionedNodeClass = class VersionedNodeType { baseDescription = { name: 'versionedNode', displayName: 'Versioned Node', description: 'A versioned node', defaultVersion: 2 }; nodeVersions = { 1: { description: { properties: [] } }, 2: { description: { properties: [] } } }; currentVersion = 2; }; mockPropertyExtractor.extractProperties.mockReturnValue([ propertyFactory.build(), propertyFactory.build() ]); const result = parser.parse(VersionedNodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(true); expect(result.version).toBe('2'); expect(result.nodeType).toBe('nodes-base.versionedNode'); }); it('should parse correctly when node has nodeVersions property', () => { const versionedDef = versionedNodeClassFactory.build(); const NodeClass = class { nodeVersions = versionedDef.nodeVersions; baseDescription = versionedDef.baseDescription; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(true); expect(result.version).toBe('2'); }); it('should use max version when version is an array', () => { const nodeDefinition = programmaticNodeFactory.build({ version: [1, 1.1, 1.2, 2] }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(true); expect(result.version).toBe('2'); // Should return max version }); it('should throw error when node is missing name property', () => { const nodeDefinition = malformedNodeFactory.build(); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); expect(() => parser.parse(NodeClass as any, 'n8n-nodes-base')).toThrow('Node is missing name property'); }); it('should use static description when instantiation fails', () => { const NodeClass = class { static description = programmaticNodeFactory.build(); constructor() { throw new Error('Cannot instantiate'); } }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.displayName).toBe(NodeClass.description.displayName); }); it('should extract category when using different property names', () => { const testCases = [ { group: ['transform'], expected: 'transform' }, { categories: ['output'], expected: 'output' }, { category: 'trigger', expected: 'trigger' }, { /* no category */ expected: 'misc' } ]; testCases.forEach(({ group, categories, category, expected }) => { const nodeDefinition = programmaticNodeFactory.build({ group, categories, category } as any); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.category).toBe(expected); }); }); it('should set isTrigger flag when node has polling property', () => { const nodeDefinition = programmaticNodeFactory.build({ polling: true }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isTrigger).toBe(true); }); it('should set isTrigger flag when node has eventTrigger property', () => { const nodeDefinition = programmaticNodeFactory.build({ eventTrigger: true }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isTrigger).toBe(true); }); it('should set isTrigger flag when node name contains trigger', () => { const nodeDefinition = programmaticNodeFactory.build({ name: 'myTrigger' }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isTrigger).toBe(true); }); it('should set isWebhook flag when node name contains webhook', () => { const nodeDefinition = programmaticNodeFactory.build({ name: 'customWebhook' }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isWebhook).toBe(true); }); it('should parse correctly when node is an instance object', () => { const nodeDefinition = programmaticNodeFactory.build(); const nodeInstance = { description: nodeDefinition }; mockPropertyExtractor.extractProperties.mockReturnValue(nodeDefinition.properties); const result = parser.parse(nodeInstance as any, 'n8n-nodes-base'); expect(result.displayName).toBe(nodeDefinition.displayName); }); it('should handle different package name formats', () => { const nodeDefinition = programmaticNodeFactory.build(); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const testCases = [ { packageName: '@n8n/n8n-nodes-langchain', expectedPrefix: 'nodes-langchain' }, { packageName: 'n8n-nodes-custom', expectedPrefix: 'nodes-custom' }, { packageName: 'custom-package', expectedPrefix: 'custom-package' } ]; testCases.forEach(({ packageName, expectedPrefix }) => { const result = parser.parse(NodeClass as any, packageName); expect(result.nodeType).toBe(`${expectedPrefix}.${nodeDefinition.name}`); }); }); }); describe('version extraction', () => { it('should prioritize currentVersion over description.defaultVersion', () => { const NodeClass = class { currentVersion = 2.2; // Should be returned description = { name: 'AI Agent', displayName: 'AI Agent', defaultVersion: 3 // Should be ignored when currentVersion exists }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('2.2'); }); it('should extract version from description.defaultVersion', () => { const NodeClass = class { description = { name: 'test', displayName: 'Test', defaultVersion: 3 }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('3'); }); it('should handle currentVersion = 0 correctly', () => { const NodeClass = class { currentVersion = 0; // Edge case: version 0 should be valid description = { name: 'test', displayName: 'Test', defaultVersion: 5 // Should be ignored }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('0'); }); it('should NOT extract version from non-existent baseDescription (legacy bug)', () => { const NodeClass = class { baseDescription = { // This property doesn't exist on VersionedNodeType! name: 'test', displayName: 'Test', defaultVersion: 3 }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('1'); // Should fallback to default }); it('should extract version from nodeVersions keys', () => { const NodeClass = class { description = { name: 'test', displayName: 'Test' }; nodeVersions = { 1: { description: {} }, 2: { description: {} }, 3: { description: {} } }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('3'); }); it('should extract version from instance nodeVersions', () => { const NodeClass = class { description = { name: 'test', displayName: 'Test' }; constructor() { (this as any).nodeVersions = { 1: { description: {} }, 2: { description: {} }, 4: { description: {} } }; } }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('4'); }); it('should handle version as number in description', () => { const nodeDefinition = programmaticNodeFactory.build({ version: 2 }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('2'); }); it('should handle version as string in description', () => { const nodeDefinition = programmaticNodeFactory.build({ version: '1.5' as any }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('1.5'); }); it('should default to version 1 when no version found', () => { const nodeDefinition = programmaticNodeFactory.build(); delete (nodeDefinition as any).version; const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.version).toBe('1'); }); }); describe('versioned node detection', () => { it('should detect versioned nodes with nodeVersions', () => { const NodeClass = class { description = { name: 'test', displayName: 'Test' }; nodeVersions = { 1: {}, 2: {} }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(true); }); it('should detect versioned nodes with defaultVersion', () => { const NodeClass = class { baseDescription = { name: 'test', displayName: 'Test', defaultVersion: 2 }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(true); }); it('should detect versioned nodes with version array in instance', () => { const NodeClass = class { description = { name: 'test', displayName: 'Test', version: [1, 1.1, 2] }; }; const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(true); }); it('should not detect non-versioned nodes as versioned', () => { const nodeDefinition = programmaticNodeFactory.build({ version: 1 }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(false); }); }); describe('edge cases', () => { it('should handle null/undefined description gracefully', () => { const NodeClass = class { description = null; }; expect(() => parser.parse(NodeClass as any, 'n8n-nodes-base')).toThrow(); }); it('should handle empty routing object for declarative nodes', () => { const nodeDefinition = declarativeNodeFactory.build({ routing: {} as any }); const NodeClass = nodeClassFactory.build({ description: nodeDefinition }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.style).toBe('declarative'); }); it('should handle complex nested versioned structure', () => { const NodeClass = class VersionedNodeType { constructor() { (this as any).baseDescription = { name: 'complex', displayName: 'Complex Node', defaultVersion: 3 }; (this as any).nodeVersions = { 1: { description: { properties: [] } }, 2: { description: { properties: [] } }, 3: { description: { properties: [] } } }; } }; // Override constructor name check Object.defineProperty(NodeClass.prototype.constructor, 'name', { value: 'VersionedNodeType' }); const result = parser.parse(NodeClass as any, 'n8n-nodes-base'); expect(result.isVersioned).toBe(true); expect(result.version).toBe('3'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/88-888/n8n-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server