Skip to main content
Glama

n8n-MCP

by 88-888
node-repository-outputs.test.tsβ€’17.8 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { NodeRepository } from '@/database/node-repository'; import { DatabaseAdapter } from '@/database/database-adapter'; import { ParsedNode } from '@/parsers/node-parser'; describe('NodeRepository - Outputs Handling', () => { let repository: NodeRepository; let mockDb: DatabaseAdapter; let mockStatement: any; beforeEach(() => { mockStatement = { run: vi.fn(), get: vi.fn(), all: vi.fn() }; mockDb = { prepare: vi.fn().mockReturnValue(mockStatement), transaction: vi.fn(), exec: vi.fn(), close: vi.fn(), pragma: vi.fn() } as any; repository = new NodeRepository(mockDb); }); describe('saveNode with outputs', () => { it('should save node with outputs and outputNames correctly', () => { const outputs = [ { displayName: 'Done', description: 'Final results when loop completes' }, { displayName: 'Loop', description: 'Current batch data during iteration' } ]; const outputNames = ['done', 'loop']; const node: ParsedNode = { style: 'programmatic', nodeType: 'nodes-base.splitInBatches', displayName: 'Split In Batches', description: 'Split data into batches', category: 'transform', properties: [], credentials: [], isAITool: false, isTrigger: false, isWebhook: false, operations: [], version: '3', isVersioned: false, packageName: 'n8n-nodes-base', outputs, outputNames }; repository.saveNode(node); expect(mockDb.prepare).toHaveBeenCalledWith(` INSERT OR REPLACE INTO nodes ( node_type, package_name, display_name, description, category, development_style, is_ai_tool, is_trigger, is_webhook, is_versioned, version, documentation, properties_schema, operations, credentials_required, outputs, output_names ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); expect(mockStatement.run).toHaveBeenCalledWith( 'nodes-base.splitInBatches', 'n8n-nodes-base', 'Split In Batches', 'Split data into batches', 'transform', 'programmatic', 0, // false 0, // false 0, // false 0, // false '3', null, // documentation JSON.stringify([], null, 2), // properties JSON.stringify([], null, 2), // operations JSON.stringify([], null, 2), // credentials JSON.stringify(outputs, null, 2), // outputs JSON.stringify(outputNames, null, 2) // output_names ); }); it('should save node with only outputs (no outputNames)', () => { const outputs = [ { displayName: 'True', description: 'Items that match condition' }, { displayName: 'False', description: 'Items that do not match condition' } ]; const node: ParsedNode = { style: 'programmatic', nodeType: 'nodes-base.if', displayName: 'IF', description: 'Route items based on conditions', category: 'transform', properties: [], credentials: [], isAITool: false, isTrigger: false, isWebhook: false, operations: [], version: '2', isVersioned: false, packageName: 'n8n-nodes-base', outputs // no outputNames }; repository.saveNode(node); const callArgs = mockStatement.run.mock.calls[0]; expect(callArgs[15]).toBe(JSON.stringify(outputs, null, 2)); // outputs expect(callArgs[16]).toBe(null); // output_names should be null }); it('should save node with only outputNames (no outputs)', () => { const outputNames = ['main', 'error']; const node: ParsedNode = { style: 'programmatic', nodeType: 'nodes-base.customNode', displayName: 'Custom Node', description: 'Custom node with output names only', category: 'transform', properties: [], credentials: [], isAITool: false, isTrigger: false, isWebhook: false, operations: [], version: '1', isVersioned: false, packageName: 'n8n-nodes-base', outputNames // no outputs }; repository.saveNode(node); const callArgs = mockStatement.run.mock.calls[0]; expect(callArgs[15]).toBe(null); // outputs should be null expect(callArgs[16]).toBe(JSON.stringify(outputNames, null, 2)); // output_names }); it('should save node without outputs or outputNames', () => { const node: ParsedNode = { style: 'programmatic', nodeType: 'nodes-base.httpRequest', displayName: 'HTTP Request', description: 'Make HTTP requests', category: 'input', properties: [], credentials: [], isAITool: false, isTrigger: false, isWebhook: false, operations: [], version: '4', isVersioned: false, packageName: 'n8n-nodes-base' // no outputs or outputNames }; repository.saveNode(node); const callArgs = mockStatement.run.mock.calls[0]; expect(callArgs[15]).toBe(null); // outputs should be null expect(callArgs[16]).toBe(null); // output_names should be null }); it('should handle empty outputs and outputNames arrays', () => { const node: ParsedNode = { style: 'programmatic', nodeType: 'nodes-base.emptyNode', displayName: 'Empty Node', description: 'Node with empty outputs', category: 'misc', properties: [], credentials: [], isAITool: false, isTrigger: false, isWebhook: false, operations: [], version: '1', isVersioned: false, packageName: 'n8n-nodes-base', outputs: [], outputNames: [] }; repository.saveNode(node); const callArgs = mockStatement.run.mock.calls[0]; expect(callArgs[15]).toBe(JSON.stringify([], null, 2)); // outputs expect(callArgs[16]).toBe(JSON.stringify([], null, 2)); // output_names }); }); describe('getNode with outputs', () => { it('should retrieve node with outputs and outputNames correctly', () => { const outputs = [ { displayName: 'Done', description: 'Final results when loop completes' }, { displayName: 'Loop', description: 'Current batch data during iteration' } ]; const outputNames = ['done', 'loop']; const mockRow = { node_type: 'nodes-base.splitInBatches', display_name: 'Split In Batches', description: 'Split data into batches', category: 'transform', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '3', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: JSON.stringify(outputs), output_names: JSON.stringify(outputNames) }; mockStatement.get.mockReturnValue(mockRow); const result = repository.getNode('nodes-base.splitInBatches'); expect(result).toEqual({ nodeType: 'nodes-base.splitInBatches', displayName: 'Split In Batches', description: 'Split data into batches', category: 'transform', developmentStyle: 'programmatic', package: 'n8n-nodes-base', isAITool: false, isTrigger: false, isWebhook: false, isVersioned: false, version: '3', properties: [], operations: [], credentials: [], hasDocumentation: false, outputs, outputNames }); }); it('should retrieve node with only outputs (null outputNames)', () => { const outputs = [ { displayName: 'True', description: 'Items that match condition' } ]; const mockRow = { node_type: 'nodes-base.if', display_name: 'IF', description: 'Route items', category: 'transform', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '2', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: JSON.stringify(outputs), output_names: null }; mockStatement.get.mockReturnValue(mockRow); const result = repository.getNode('nodes-base.if'); expect(result.outputs).toEqual(outputs); expect(result.outputNames).toBe(null); }); it('should retrieve node with only outputNames (null outputs)', () => { const outputNames = ['main']; const mockRow = { node_type: 'nodes-base.customNode', display_name: 'Custom Node', description: 'Custom node', category: 'misc', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '1', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: null, output_names: JSON.stringify(outputNames) }; mockStatement.get.mockReturnValue(mockRow); const result = repository.getNode('nodes-base.customNode'); expect(result.outputs).toBe(null); expect(result.outputNames).toEqual(outputNames); }); it('should retrieve node without outputs or outputNames', () => { const mockRow = { node_type: 'nodes-base.httpRequest', display_name: 'HTTP Request', description: 'Make HTTP requests', category: 'input', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '4', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: null, output_names: null }; mockStatement.get.mockReturnValue(mockRow); const result = repository.getNode('nodes-base.httpRequest'); expect(result.outputs).toBe(null); expect(result.outputNames).toBe(null); }); it('should handle malformed JSON gracefully', () => { const mockRow = { node_type: 'nodes-base.malformed', display_name: 'Malformed Node', description: 'Node with malformed JSON', category: 'misc', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '1', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: '{invalid json}', output_names: '[invalid, json' }; mockStatement.get.mockReturnValue(mockRow); const result = repository.getNode('nodes-base.malformed'); // Should use default values when JSON parsing fails expect(result.outputs).toBe(null); expect(result.outputNames).toBe(null); }); it('should return null for non-existent node', () => { mockStatement.get.mockReturnValue(null); const result = repository.getNode('nodes-base.nonExistent'); expect(result).toBe(null); }); it('should handle SplitInBatches counterintuitive output order correctly', () => { // Test that the output order is preserved: done=0, loop=1 const outputs = [ { displayName: 'Done', description: 'Final results when loop completes', index: 0 }, { displayName: 'Loop', description: 'Current batch data during iteration', index: 1 } ]; const outputNames = ['done', 'loop']; const mockRow = { node_type: 'nodes-base.splitInBatches', display_name: 'Split In Batches', description: 'Split data into batches', category: 'transform', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '3', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: JSON.stringify(outputs), output_names: JSON.stringify(outputNames) }; mockStatement.get.mockReturnValue(mockRow); const result = repository.getNode('nodes-base.splitInBatches'); // Verify order is preserved expect(result.outputs[0].displayName).toBe('Done'); expect(result.outputs[1].displayName).toBe('Loop'); expect(result.outputNames[0]).toBe('done'); expect(result.outputNames[1]).toBe('loop'); }); }); describe('parseNodeRow with outputs', () => { it('should parse node row with outputs correctly using parseNodeRow', () => { const outputs = [{ displayName: 'Output' }]; const outputNames = ['main']; const mockRow = { node_type: 'nodes-base.test', display_name: 'Test', description: 'Test node', category: 'misc', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '1', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: JSON.stringify(outputs), output_names: JSON.stringify(outputNames) }; mockStatement.all.mockReturnValue([mockRow]); const results = repository.getAllNodes(1); expect(results[0].outputs).toEqual(outputs); expect(results[0].outputNames).toEqual(outputNames); }); it('should handle empty string as null for outputs', () => { const mockRow = { node_type: 'nodes-base.empty', display_name: 'Empty', description: 'Empty node', category: 'misc', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '1', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: '', // empty string output_names: '' // empty string }; mockStatement.all.mockReturnValue([mockRow]); const results = repository.getAllNodes(1); // Empty strings should be treated as null since they fail JSON parsing expect(results[0].outputs).toBe(null); expect(results[0].outputNames).toBe(null); }); }); describe('complex output structures', () => { it('should handle complex output objects with metadata', () => { const complexOutputs = [ { displayName: 'Done', name: 'done', type: 'main', hint: 'Receives the final data after all batches have been processed', description: 'Final results when loop completes', index: 0 }, { displayName: 'Loop', name: 'loop', type: 'main', hint: 'Receives the current batch data during each iteration', description: 'Current batch data during iteration', index: 1 } ]; const node: ParsedNode = { style: 'programmatic', nodeType: 'nodes-base.splitInBatches', displayName: 'Split In Batches', description: 'Split data into batches', category: 'transform', properties: [], credentials: [], isAITool: false, isTrigger: false, isWebhook: false, operations: [], version: '3', isVersioned: false, packageName: 'n8n-nodes-base', outputs: complexOutputs, outputNames: ['done', 'loop'] }; repository.saveNode(node); // Simulate retrieval const mockRow = { node_type: 'nodes-base.splitInBatches', display_name: 'Split In Batches', description: 'Split data into batches', category: 'transform', development_style: 'programmatic', package_name: 'n8n-nodes-base', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 0, version: '3', properties_schema: JSON.stringify([]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: JSON.stringify(complexOutputs), output_names: JSON.stringify(['done', 'loop']) }; mockStatement.get.mockReturnValue(mockRow); const result = repository.getNode('nodes-base.splitInBatches'); expect(result.outputs).toEqual(complexOutputs); expect(result.outputs[0]).toMatchObject({ displayName: 'Done', name: 'done', type: 'main', hint: 'Receives the final data after all batches have been processed' }); }); }); });

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