Skip to main content
Glama

n8n-MCP

by 88-888
validation-fixes.test.tsβ€’11.2 kB
/** * Test cases for validation fixes - specifically for false positives */ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { WorkflowValidator } from '../../../src/services/workflow-validator'; import { EnhancedConfigValidator } from '../../../src/services/enhanced-config-validator'; import { NodeRepository } from '../../../src/database/node-repository'; import { DatabaseAdapter, PreparedStatement, RunResult } from '../../../src/database/database-adapter'; // Mock logger to prevent console output vi.mock('@/utils/logger', () => ({ Logger: vi.fn().mockImplementation(() => ({ error: vi.fn(), warn: vi.fn(), info: vi.fn(), debug: vi.fn() })) })); // Create a complete mock for DatabaseAdapter class MockDatabaseAdapter implements DatabaseAdapter { private statements = new Map<string, MockPreparedStatement>(); private mockData = new Map<string, any>(); prepare = vi.fn((sql: string) => { if (!this.statements.has(sql)) { this.statements.set(sql, new MockPreparedStatement(sql, this.mockData)); } return this.statements.get(sql)!; }); exec = vi.fn(); close = vi.fn(); pragma = vi.fn(); transaction = vi.fn((fn: () => any) => fn()); checkFTS5Support = vi.fn(() => true); inTransaction = false; // Test helper to set mock data _setMockData(key: string, value: any) { this.mockData.set(key, value); } // Test helper to get statement by SQL _getStatement(sql: string) { return this.statements.get(sql); } } class MockPreparedStatement implements PreparedStatement { run = vi.fn((...params: any[]): RunResult => ({ changes: 1, lastInsertRowid: 1 })); get = vi.fn(); all = vi.fn(() => []); iterate = vi.fn(); pluck = vi.fn(() => this); expand = vi.fn(() => this); raw = vi.fn(() => this); columns = vi.fn(() => []); bind = vi.fn(() => this); constructor(private sql: string, private mockData: Map<string, any>) { // Configure get() based on SQL pattern if (sql.includes('SELECT * FROM nodes WHERE node_type = ?')) { this.get = vi.fn((nodeType: string) => this.mockData.get(`node:${nodeType}`)); } } } describe('Validation Fixes for False Positives', () => { let repository: any; let mockAdapter: MockDatabaseAdapter; let validator: WorkflowValidator; beforeEach(() => { mockAdapter = new MockDatabaseAdapter(); repository = new NodeRepository(mockAdapter); // Add findSimilarNodes method for WorkflowValidator repository.findSimilarNodes = vi.fn().mockReturnValue([]); // Initialize services EnhancedConfigValidator.initializeSimilarityServices(repository); validator = new WorkflowValidator(repository, EnhancedConfigValidator); // Mock Google Drive node data const googleDriveNodeData = { node_type: 'nodes-base.googleDrive', package_name: 'n8n-nodes-base', display_name: 'Google Drive', description: 'Access Google Drive', category: 'input', development_style: 'programmatic', is_ai_tool: 0, is_trigger: 0, is_webhook: 0, is_versioned: 1, version: '3', properties_schema: JSON.stringify([ { name: 'resource', type: 'options', default: 'file', options: [ { value: 'file', name: 'File' }, { value: 'fileFolder', name: 'File/Folder' }, { value: 'folder', name: 'Folder' }, { value: 'drive', name: 'Shared Drive' } ] }, { name: 'operation', type: 'options', displayOptions: { show: { resource: ['fileFolder'] } }, default: 'search', options: [ { value: 'search', name: 'Search' } ] }, { name: 'queryString', type: 'string', displayOptions: { show: { resource: ['fileFolder'], operation: ['search'] } } }, { name: 'filter', type: 'collection', displayOptions: { show: { resource: ['fileFolder'], operation: ['search'] } }, default: {}, options: [ { name: 'folderId', type: 'resourceLocator', default: { mode: 'list', value: '' } } ] }, { name: 'options', type: 'collection', displayOptions: { show: { resource: ['fileFolder'], operation: ['search'] } }, default: {}, options: [ { name: 'fields', type: 'multiOptions', default: [] } ] } ]), operations: JSON.stringify([]), credentials_required: JSON.stringify([]), documentation: null, outputs: null, output_names: null }; // Set mock data for node retrieval mockAdapter._setMockData('node:nodes-base.googleDrive', googleDriveNodeData); mockAdapter._setMockData('node:n8n-nodes-base.googleDrive', googleDriveNodeData); }); describe('Google Drive fileFolder Resource Validation', () => { it('should validate fileFolder as a valid resource', () => { const config = { resource: 'fileFolder' }; const node = repository.getNode('nodes-base.googleDrive'); const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.googleDrive', config, node.properties, 'operation', 'ai-friendly' ); expect(result.valid).toBe(true); // Should not have resource error const resourceError = result.errors.find(e => e.property === 'resource'); expect(resourceError).toBeUndefined(); }); it('should apply default operation when not specified', () => { const config = { resource: 'fileFolder' // operation is not specified, should use default 'search' }; const node = repository.getNode('nodes-base.googleDrive'); const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.googleDrive', config, node.properties, 'operation', 'ai-friendly' ); expect(result.valid).toBe(true); // Should not have operation error const operationError = result.errors.find(e => e.property === 'operation'); expect(operationError).toBeUndefined(); }); it('should not warn about properties being unused when default operation is applied', () => { const config = { resource: 'fileFolder', // operation not specified, will use default 'search' queryString: '=', filter: { folderId: { __rl: true, value: '={{ $json.id }}', mode: 'id' } }, options: { fields: ['id', 'kind', 'mimeType', 'name', 'webViewLink'] } }; const node = repository.getNode('nodes-base.googleDrive'); const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.googleDrive', config, node.properties, 'operation', 'ai-friendly' ); // Should be valid expect(result.valid).toBe(true); // Should not have warnings about properties not being used const propertyWarnings = result.warnings.filter(w => w.message.includes("won't be used") || w.message.includes("not used") ); expect(propertyWarnings.length).toBe(0); }); it.skip('should validate complete workflow with Google Drive nodes', async () => { const workflow = { name: 'Test Google Drive Workflow', nodes: [ { id: '1', name: 'Google Drive', type: 'n8n-nodes-base.googleDrive', typeVersion: 3, position: [100, 100] as [number, number], parameters: { resource: 'fileFolder', queryString: '=', filter: { folderId: { __rl: true, value: '={{ $json.id }}', mode: 'id' } }, options: { fields: ['id', 'kind', 'mimeType', 'name', 'webViewLink'] } } } ], connections: {} }; let result; try { result = await validator.validateWorkflow(workflow, { validateNodes: true, validateConnections: true, validateExpressions: true, profile: 'ai-friendly' }); } catch (error) { console.log('Validation threw error:', error); throw error; } // Debug output if (!result.valid) { console.log('Validation errors:', JSON.stringify(result.errors, null, 2)); console.log('Validation warnings:', JSON.stringify(result.warnings, null, 2)); } // Should be valid expect(result.valid).toBe(true); // Should not have "Invalid resource" errors const resourceErrors = result.errors.filter((e: any) => e.message.includes('Invalid resource') && e.message.includes('fileFolder') ); expect(resourceErrors.length).toBe(0); }); it('should still report errors for truly invalid resources', () => { const config = { resource: 'invalidResource' }; const node = repository.getNode('nodes-base.googleDrive'); const result = EnhancedConfigValidator.validateWithMode( 'nodes-base.googleDrive', config, node.properties, 'operation', 'ai-friendly' ); expect(result.valid).toBe(false); // Should have resource error for invalid resource const resourceError = result.errors.find(e => e.property === 'resource'); expect(resourceError).toBeDefined(); expect(resourceError!.message).toContain('Invalid resource "invalidResource"'); }); }); describe('Node Type Validation', () => { it('should accept both n8n-nodes-base and nodes-base prefixes', async () => { const workflow1 = { name: 'Test with n8n-nodes-base prefix', nodes: [ { id: '1', name: 'Google Drive', type: 'n8n-nodes-base.googleDrive', typeVersion: 3, position: [100, 100] as [number, number], parameters: { resource: 'file' } } ], connections: {} }; const result1 = await validator.validateWorkflow(workflow1); // Should not have errors about node type format const typeErrors1 = result1.errors.filter((e: any) => e.message.includes('Invalid node type') || e.message.includes('must use the full package name') ); expect(typeErrors1.length).toBe(0); // Note: nodes-base prefix might still be invalid in actual workflows // but the validator shouldn't incorrectly suggest it's always wrong }); }); });

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