Skip to main content
Glama

n8n-MCP

by 88-888
expression-validator-edge-cases.test.tsβ€’13.1 kB
import { describe, it, expect, vi, beforeEach } from 'vitest'; import { ExpressionValidator } from '@/services/expression-validator'; // Mock the database vi.mock('better-sqlite3'); describe('ExpressionValidator - Edge Cases', () => { beforeEach(() => { vi.clearAllMocks(); }); describe('Null and Undefined Handling', () => { it('should handle null expression gracefully', () => { const context = { availableNodes: ['Node1'] }; const result = ExpressionValidator.validateExpression(null as any, context); expect(result.valid).toBe(true); expect(result.errors).toEqual([]); }); it('should handle undefined expression gracefully', () => { const context = { availableNodes: ['Node1'] }; const result = ExpressionValidator.validateExpression(undefined as any, context); expect(result.valid).toBe(true); expect(result.errors).toEqual([]); }); it('should handle null context gracefully', () => { const result = ExpressionValidator.validateExpression('{{ $json.data }}', null as any); expect(result).toBeDefined(); // With null context, it will likely have errors about missing context expect(result.valid).toBe(false); }); it('should handle undefined context gracefully', () => { const result = ExpressionValidator.validateExpression('{{ $json.data }}', undefined as any); expect(result).toBeDefined(); // With undefined context, it will likely have errors about missing context expect(result.valid).toBe(false); }); }); describe('Boundary Value Testing', () => { it('should handle empty string expression', () => { const context = { availableNodes: [] }; const result = ExpressionValidator.validateExpression('', context); expect(result.valid).toBe(true); expect(result.errors).toEqual([]); expect(result.usedVariables.size).toBe(0); }); it('should handle extremely long expressions', () => { const longExpression = '{{ ' + '$json.field'.repeat(1000) + ' }}'; const context = { availableNodes: ['Node1'] }; const start = Date.now(); const result = ExpressionValidator.validateExpression(longExpression, context); const duration = Date.now() - start; expect(result).toBeDefined(); expect(duration).toBeLessThan(1000); // Should process within 1 second }); it('should handle deeply nested property access', () => { const deepExpression = '{{ $json' + '.property'.repeat(50) + ' }}'; const context = { availableNodes: ['Node1'] }; const result = ExpressionValidator.validateExpression(deepExpression, context); expect(result.valid).toBe(true); expect(result.usedVariables.has('$json')).toBe(true); }); it('should handle many different variables in one expression', () => { const complexExpression = `{{ $json.data + $node["Node1"].json.value + $input.item.field + $items("Node2", 0)[0].data + $parameter["apiKey"] + $env.API_URL + $workflow.name + $execution.id + $itemIndex + $now }}`; const context = { availableNodes: ['Node1', 'Node2'], hasInputData: true }; const result = ExpressionValidator.validateExpression(complexExpression, context); expect(result.usedVariables.size).toBeGreaterThan(5); expect(result.usedNodes.has('Node1')).toBe(true); expect(result.usedNodes.has('Node2')).toBe(true); }); }); describe('Invalid Syntax Handling', () => { it('should detect unclosed expressions', () => { const expressions = [ '{{ $json.field', '$json.field }}', '{{ $json.field }', '{ $json.field }}' ]; const context = { availableNodes: [] }; expressions.forEach(expr => { const result = ExpressionValidator.validateExpression(expr, context); expect(result.errors.some(e => e.includes('Unmatched'))).toBe(true); }); }); it('should detect nested expressions', () => { const nestedExpression = '{{ $json.field + {{ $node["Node1"].json }} }}'; const context = { availableNodes: ['Node1'] }; const result = ExpressionValidator.validateExpression(nestedExpression, context); expect(result.errors.some(e => e.includes('Nested expressions'))).toBe(true); }); it('should detect empty expressions', () => { const emptyExpression = 'Value: {{}}'; const context = { availableNodes: [] }; const result = ExpressionValidator.validateExpression(emptyExpression, context); expect(result.errors.some(e => e.includes('Empty expression'))).toBe(true); }); it('should handle malformed node references', () => { const expressions = [ '{{ $node[].json }}', '{{ $node[""].json }}', '{{ $node[Node1].json }}', // Missing quotes '{{ $node["Node1" ].json }}' // Extra space - this might actually be valid ]; const context = { availableNodes: ['Node1'] }; expressions.forEach(expr => { const result = ExpressionValidator.validateExpression(expr, context); // Some of these might generate warnings or errors expect(result).toBeDefined(); }); }); }); describe('Special Characters and Unicode', () => { it('should handle special characters in node names', () => { const specialNodes = ['Node-123', 'Node_Test', 'Node@Special', 'Node δΈ­ζ–‡', 'Node😊']; const context = { availableNodes: specialNodes }; specialNodes.forEach(nodeName => { const expression = `{{ $node["${nodeName}"].json.value }}`; const result = ExpressionValidator.validateExpression(expression, context); expect(result.usedNodes.has(nodeName)).toBe(true); expect(result.errors.filter(e => e.includes(nodeName))).toHaveLength(0); }); }); it('should handle Unicode in property names', () => { const expression = '{{ $json.名前 + $json.שם + $json.имя }}'; const context = { availableNodes: [] }; const result = ExpressionValidator.validateExpression(expression, context); expect(result.usedVariables.has('$json')).toBe(true); }); }); describe('Context Validation', () => { it('should warn about $input when no input data available', () => { const expression = '{{ $input.item.data }}'; const context = { availableNodes: [], hasInputData: false }; const result = ExpressionValidator.validateExpression(expression, context); expect(result.warnings.some(w => w.includes('$input'))).toBe(true); }); it('should handle references to non-existent nodes', () => { const expression = '{{ $node["NonExistentNode"].json.value }}'; const context = { availableNodes: ['Node1', 'Node2'] }; const result = ExpressionValidator.validateExpression(expression, context); expect(result.errors.some(e => e.includes('NonExistentNode'))).toBe(true); }); it('should validate $items function references', () => { const expression = '{{ $items("NonExistentNode", 0)[0].json }}'; const context = { availableNodes: ['Node1', 'Node2'] }; const result = ExpressionValidator.validateExpression(expression, context); expect(result.errors.some(e => e.includes('NonExistentNode'))).toBe(true); }); }); describe('Complex Expression Patterns', () => { it('should handle JavaScript operations in expressions', () => { const expressions = [ '{{ $json.count > 10 ? "high" : "low" }}', '{{ Math.round($json.price * 1.2) }}', '{{ $json.items.filter(item => item.active).length }}', '{{ new Date($json.timestamp).toISOString() }}', '{{ $json.name.toLowerCase().replace(" ", "-") }}' ]; const context = { availableNodes: [] }; expressions.forEach(expr => { const result = ExpressionValidator.validateExpression(expr, context); expect(result.usedVariables.has('$json')).toBe(true); }); }); it('should handle array access patterns', () => { const expressions = [ '{{ $json[0] }}', '{{ $json.items[5].name }}', '{{ $node["Node1"].json[0].data[1] }}', '{{ $json["items"][0]["name"] }}' ]; const context = { availableNodes: ['Node1'] }; expressions.forEach(expr => { const result = ExpressionValidator.validateExpression(expr, context); expect(result.usedVariables.size).toBeGreaterThan(0); }); }); }); describe('validateNodeExpressions', () => { it('should validate all expressions in node parameters', () => { const parameters = { field1: '{{ $json.data }}', field2: 'static value', nested: { field3: '{{ $node["Node1"].json.value }}', array: [ '{{ $json.item1 }}', 'not an expression', '{{ $json.item2 }}' ] } }; const context = { availableNodes: ['Node1'] }; const result = ExpressionValidator.validateNodeExpressions(parameters, context); expect(result.usedVariables.has('$json')).toBe(true); expect(result.usedNodes.has('Node1')).toBe(true); expect(result.valid).toBe(true); }); it('should handle null/undefined in parameters', () => { const parameters = { field1: null, field2: undefined, field3: '', field4: '{{ $json.data }}' }; const context = { availableNodes: [] }; const result = ExpressionValidator.validateNodeExpressions(parameters, context); expect(result.usedVariables.has('$json')).toBe(true); expect(result.errors.length).toBe(0); }); it('should handle circular references in parameters', () => { const parameters: any = { field1: '{{ $json.data }}' }; parameters.circular = parameters; const context = { availableNodes: [] }; // Should not throw expect(() => { ExpressionValidator.validateNodeExpressions(parameters, context); }).not.toThrow(); }); it('should aggregate errors from multiple expressions', () => { const parameters = { field1: '{{ $node["Missing1"].json }}', field2: '{{ $node["Missing2"].json }}', field3: '{{ }}', // Empty expression field4: '{{ $json.valid }}' }; const context = { availableNodes: ['ValidNode'] }; const result = ExpressionValidator.validateNodeExpressions(parameters, context); expect(result.valid).toBe(false); // Should have at least 3 errors: 2 missing nodes + 1 empty expression expect(result.errors.length).toBeGreaterThanOrEqual(3); expect(result.usedVariables.has('$json')).toBe(true); }); }); describe('Performance Edge Cases', () => { it('should handle recursive parameter structures efficiently', () => { const createNestedObject = (depth: number): any => { if (depth === 0) return '{{ $json.value }}'; return { level: depth, expression: `{{ $json.level${depth} }}`, nested: createNestedObject(depth - 1) }; }; const deepParameters = createNestedObject(100); const context = { availableNodes: [] }; const start = Date.now(); const result = ExpressionValidator.validateNodeExpressions(deepParameters, context); const duration = Date.now() - start; expect(result).toBeDefined(); expect(duration).toBeLessThan(1000); // Should complete within 1 second }); it('should handle large arrays of expressions', () => { const parameters = { items: Array(1000).fill(null).map((_, i) => `{{ $json.item${i} }}`) }; const context = { availableNodes: [] }; const result = ExpressionValidator.validateNodeExpressions(parameters, context); expect(result.usedVariables.has('$json')).toBe(true); expect(result.valid).toBe(true); }); }); describe('Error Message Quality', () => { it('should provide helpful error messages', () => { const testCases = [ { expression: '{{ $node["Node With Spaces"].json }}', context: { availableNodes: ['NodeWithSpaces'] }, expectedError: 'Node With Spaces' }, { expression: '{{ $items("WrongNode", -1) }}', context: { availableNodes: ['RightNode'] }, expectedError: 'WrongNode' } ]; testCases.forEach(({ expression, context, expectedError }) => { const result = ExpressionValidator.validateExpression(expression, context); const hasRelevantError = result.errors.some(e => e.includes(expectedError)); expect(hasRelevantError).toBe(true); }); }); }); });

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