Skip to main content
Glama

n8n-MCP

by 88-888
fixed-collection-validation.test.tsโ€ข12.8 kB
/** * Fixed Collection Validation Tests * Tests for the fix of issue #90: "propertyValues[itemName] is not iterable" error * * This ensures AI agents cannot create invalid fixedCollection structures that break n8n UI */ import { describe, test, expect } from 'vitest'; import { EnhancedConfigValidator } from '../../../src/services/enhanced-config-validator'; describe('FixedCollection Validation', () => { describe('Switch Node v2/v3 Validation', () => { test('should detect invalid nested conditions structure', () => { const invalidConfig = { rules: { conditions: { values: [ { value1: '={{$json.status}}', operation: 'equals', value2: 'active' } ] } } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', invalidConfig, [], 'operation', 'ai-friendly' ); expect(result.valid).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors[0].type).toBe('invalid_value'); expect(result.errors[0].property).toBe('rules'); expect(result.errors[0].message).toContain('propertyValues[itemName] is not iterable'); expect(result.errors[0].fix).toContain('{ "rules": { "values": [{ "conditions": {...}, "outputKey": "output1" }] } }'); }); test('should detect direct conditions in rules (another invalid pattern)', () => { const invalidConfig = { rules: { conditions: { value1: '={{$json.status}}', operation: 'equals', value2: 'active' } } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', invalidConfig, [], 'operation', 'ai-friendly' ); expect(result.valid).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors[0].message).toContain('Invalid structure for nodes-base.switch node'); }); test('should provide auto-fix for invalid switch structure', () => { const invalidConfig = { rules: { conditions: { values: [ { value1: '={{$json.status}}', operation: 'equals', value2: 'active' } ] } } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', invalidConfig, [], 'operation', 'ai-friendly' ); expect(result.autofix).toBeDefined(); expect(result.autofix!.rules).toBeDefined(); expect(result.autofix!.rules.values).toBeInstanceOf(Array); expect(result.autofix!.rules.values).toHaveLength(1); expect(result.autofix!.rules.values[0]).toHaveProperty('conditions'); expect(result.autofix!.rules.values[0]).toHaveProperty('outputKey'); }); test('should accept valid switch structure', () => { const validConfig = { rules: { values: [ { conditions: { value1: '={{$json.status}}', operation: 'equals', value2: 'active' }, outputKey: 'active' } ] } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', validConfig, [], 'operation', 'ai-friendly' ); // Should not have the specific fixedCollection error const hasFixedCollectionError = result.errors.some(e => e.message.includes('propertyValues[itemName] is not iterable') ); expect(hasFixedCollectionError).toBe(false); }); test('should warn about missing outputKey in valid structure', () => { const configMissingOutputKey = { rules: { values: [ { conditions: { value1: '={{$json.status}}', operation: 'equals', value2: 'active' } // Missing outputKey } ] } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', configMissingOutputKey, [], 'operation', 'ai-friendly' ); const hasOutputKeyWarning = result.warnings.some(w => w.message.includes('missing "outputKey" property') ); expect(hasOutputKeyWarning).toBe(true); }); }); describe('If Node Validation', () => { test('should detect invalid nested values structure', () => { const invalidConfig = { conditions: { values: [ { value1: '={{$json.age}}', operation: 'largerEqual', value2: 18 } ] } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.if', invalidConfig, [], 'operation', 'ai-friendly' ); expect(result.valid).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors[0].type).toBe('invalid_value'); expect(result.errors[0].property).toBe('conditions'); expect(result.errors[0].message).toContain('Invalid structure for nodes-base.if node'); expect(result.errors[0].fix).toBe('Use: { "conditions": {...} } or { "conditions": [...] } directly, not nested under "values"'); }); test('should provide auto-fix for invalid if structure', () => { const invalidConfig = { conditions: { values: [ { value1: '={{$json.age}}', operation: 'largerEqual', value2: 18 } ] } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.if', invalidConfig, [], 'operation', 'ai-friendly' ); expect(result.autofix).toBeDefined(); expect(result.autofix!.conditions).toEqual(invalidConfig.conditions.values); }); test('should accept valid if structure', () => { const validConfig = { conditions: { value1: '={{$json.age}}', operation: 'largerEqual', value2: 18 } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.if', validConfig, [], 'operation', 'ai-friendly' ); // Should not have the specific structure error const hasStructureError = result.errors.some(e => e.message.includes('should be a filter object/array directly') ); expect(hasStructureError).toBe(false); }); }); describe('Filter Node Validation', () => { test('should detect invalid nested values structure', () => { const invalidConfig = { conditions: { values: [ { value1: '={{$json.score}}', operation: 'larger', value2: 80 } ] } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.filter', invalidConfig, [], 'operation', 'ai-friendly' ); expect(result.valid).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors[0].type).toBe('invalid_value'); expect(result.errors[0].property).toBe('conditions'); expect(result.errors[0].message).toContain('Invalid structure for nodes-base.filter node'); }); test('should accept valid filter structure', () => { const validConfig = { conditions: { value1: '={{$json.score}}', operation: 'larger', value2: 80 } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.filter', validConfig, [], 'operation', 'ai-friendly' ); // Should not have the specific structure error const hasStructureError = result.errors.some(e => e.message.includes('should be a filter object/array directly') ); expect(hasStructureError).toBe(false); }); }); describe('Edge Cases', () => { test('should not validate non-problematic nodes', () => { const config = { someProperty: { conditions: { values: ['should', 'not', 'trigger', 'validation'] } } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.httpRequest', config, [], 'operation', 'ai-friendly' ); // Should not have fixedCollection errors for non-problematic nodes const hasFixedCollectionError = result.errors.some(e => e.message.includes('propertyValues[itemName] is not iterable') ); expect(hasFixedCollectionError).toBe(false); }); test('should handle empty config gracefully', () => { const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', {}, [], 'operation', 'ai-friendly' ); // Should not crash or produce false positives expect(result).toBeDefined(); expect(result.errors).toBeInstanceOf(Array); }); test('should handle non-object property values', () => { const config = { rules: 'not an object' }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', config, [], 'operation', 'ai-friendly' ); // Should not crash on non-object values expect(result).toBeDefined(); expect(result.errors).toBeInstanceOf(Array); }); }); describe('Real-world AI Agent Patterns', () => { test('should catch common ChatGPT/Claude switch patterns', () => { // This is a pattern commonly generated by AI agents const aiGeneratedConfig = { rules: { conditions: { values: [ { "value1": "={{$json.status}}", "operation": "equals", "value2": "active" }, { "value1": "={{$json.priority}}", "operation": "equals", "value2": "high" } ] } } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', aiGeneratedConfig, [], 'operation', 'ai-friendly' ); expect(result.valid).toBe(false); expect(result.errors).toHaveLength(1); expect(result.errors[0].message).toContain('propertyValues[itemName] is not iterable'); // Check auto-fix generates correct structure expect(result.autofix!.rules.values).toHaveLength(2); result.autofix!.rules.values.forEach((rule: any) => { expect(rule).toHaveProperty('conditions'); expect(rule).toHaveProperty('outputKey'); }); }); test('should catch common AI if/filter patterns', () => { const aiGeneratedIfConfig = { conditions: { values: { "value1": "={{$json.age}}", "operation": "largerEqual", "value2": 21 } } }; const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.if', aiGeneratedIfConfig, [], 'operation', 'ai-friendly' ); expect(result.valid).toBe(false); expect(result.errors[0].message).toContain('Invalid structure for nodes-base.if node'); }); }); describe('Version Compatibility', () => { test('should work across different validation profiles', () => { const invalidConfig = { rules: { conditions: { values: [{ value1: 'test', operation: 'equals', value2: 'test' }] } } }; const profiles: Array<'strict' | 'runtime' | 'ai-friendly' | 'minimal'> = ['strict', 'runtime', 'ai-friendly', 'minimal']; profiles.forEach(profile => { const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.switch', invalidConfig, [], 'operation', profile ); // All profiles should catch this critical error const hasCriticalError = result.errors.some(e => e.message.includes('propertyValues[itemName] is not iterable') ); expect(hasCriticalError, `Profile ${profile} should catch critical fixedCollection error`).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