diff --git a/src/services/enhanced-config-validator.ts b/src/services/enhanced-config-validator.ts index 472d224..6c652ce 100644 --- a/src/services/enhanced-config-validator.ts +++ b/src/services/enhanced-config-validator.ts @@ -415,9 +415,19 @@ export class EnhancedConfigValidator extends ConfigValidator { } // 2. Suggest responseFormat for API endpoints - const isApiEndpoint = url.includes('/api') || url.includes('/rest') || - url.includes('supabase') || url.includes('firebase') || - url.includes('googleapis') || url.includes('.com/v'); + const lowerUrl = url.toLowerCase(); + const isApiEndpoint = + // Subdomain patterns (api.example.com) + /^https?:\/\/api\./i.test(url) || + // Path patterns with word boundaries to prevent false positives like "therapist", "restaurant" + /\/api[\/\?]|\/api$/i.test(url) || + /\/rest[\/\?]|\/rest$/i.test(url) || + // Known API service domains + lowerUrl.includes('supabase.co') || + lowerUrl.includes('firebase') || + lowerUrl.includes('googleapis.com') || + // Versioned API paths (e.g., example.com/v1, example.com/v2) + /\.com\/v\d+/i.test(url); if (isApiEndpoint && !options.response?.response?.responseFormat) { result.suggestions.push( @@ -431,10 +441,11 @@ export class EnhancedConfigValidator extends ConfigValidator { if (url && url.startsWith('=')) { // Expression-based URL - check for common protocol issues const expressionContent = url.slice(1); // Remove = prefix + const lowerExpression = expressionContent.toLowerCase(); - // Check for missing protocol in expression + // Check for missing protocol in expression (case-insensitive) if (expressionContent.startsWith('www.') || - (expressionContent.includes('{{') && !expressionContent.includes('http'))) { + (expressionContent.includes('{{') && !lowerExpression.includes('http'))) { result.warnings.push({ type: 'invalid_value', property: 'url', diff --git a/tests/unit/services/enhanced-config-validator.test.ts b/tests/unit/services/enhanced-config-validator.test.ts index e3217f6..64df123 100644 --- a/tests/unit/services/enhanced-config-validator.test.ts +++ b/tests/unit/services/enhanced-config-validator.test.ts @@ -1020,5 +1020,117 @@ describe('EnhancedConfigValidator', () => { ); expect(responseFormatSuggestion).toBeUndefined(); }); + + it('should detect missing protocol in expressions with uppercase HTTP', () => { + const nodeType = 'nodes-base.httpRequest'; + const config = { + url: '={{ "HTTP://" + $json.domain + ".com" }}', + method: 'GET' + }; + const properties = [ + { name: 'url', type: 'string', required: true } + ]; + + const result = EnhancedConfigValidator.validateWithMode( + nodeType, + config, + properties, + 'operation', + 'ai-friendly' + ); + + // Should NOT warn because HTTP:// is present (case-insensitive) + expect(result.warnings).toHaveLength(0); + }); + + it('should NOT suggest responseFormat for false positive URLs', () => { + const nodeType = 'nodes-base.httpRequest'; + const testUrls = [ + 'https://example.com/therapist-directory', + 'https://restaurant-bookings.com/reserve', + 'https://forest-management.org/data' + ]; + + testUrls.forEach(url => { + const config = { + url, + method: 'GET', + options: {} + }; + const properties = [ + { name: 'url', type: 'string', required: true } + ]; + + const result = EnhancedConfigValidator.validateWithMode( + nodeType, + config, + properties, + 'operation', + 'ai-friendly' + ); + + const responseFormatSuggestion = result.suggestions.find( + (s: string) => s.includes('responseFormat') + ); + expect(responseFormatSuggestion).toBeUndefined(); + }); + }); + + it('should suggest responseFormat for case-insensitive API paths', () => { + const nodeType = 'nodes-base.httpRequest'; + const testUrls = [ + 'https://example.com/API/users', + 'https://example.com/Rest/data', + 'https://example.com/REST/v1/items' + ]; + + testUrls.forEach(url => { + const config = { + url, + method: 'GET', + options: {} + }; + const properties = [ + { name: 'url', type: 'string', required: true } + ]; + + const result = EnhancedConfigValidator.validateWithMode( + nodeType, + config, + properties, + 'operation', + 'ai-friendly' + ); + + expect(result.suggestions).toContainEqual( + expect.stringContaining('responseFormat') + ); + }); + }); + + it('should handle null and undefined URLs gracefully', () => { + const nodeType = 'nodes-base.httpRequest'; + const testConfigs = [ + { url: null, method: 'GET' }, + { url: undefined, method: 'GET' }, + { url: '', method: 'GET' } + ]; + + testConfigs.forEach(config => { + const properties = [ + { name: 'url', type: 'string', required: true } + ]; + + expect(() => { + EnhancedConfigValidator.validateWithMode( + nodeType, + config, + properties, + 'operation', + 'ai-friendly' + ); + }).not.toThrow(); + }); + }); }); }); \ No newline at end of file