Compare commits

..

9 Commits

Author SHA1 Message Date
czlonkowski
ced7fafcbf fix: address code review findings for HTTP Request validation
- Make protocol detection case-insensitive (HTTP://, HTTPS://, Http://)
- Refactor API endpoint detection to prevent false positives
- Add subdomain pattern detection (api.example.com)
- Use regex with word boundaries for path patterns
- Add test coverage for edge cases:
  * Uppercase protocol variants
  * False positive URLs (therapist, restaurant, forest)
  * Case-insensitive API path detection
  * Null/undefined URL handling

All 50 tests passing. Addresses critical issues from PR #366 code review.

Conceived by Romuald Członkowski - www.aiadvisors.pl/en
2025-10-24 17:19:20 +02:00
czlonkowski
ad4b521402 enhance: Add HTTP Request node validation suggestions (issue #361)
Added helpful suggestions for HTTP Request node best practices after thorough investigation of issue #361.

## What's New

1. **alwaysOutputData Suggestion**
   - Suggests adding alwaysOutputData: true at node level
   - Prevents silent workflow failures when HTTP requests error
   - Ensures downstream error handling can process failed requests

2. **responseFormat Suggestion for API Endpoints**
   - Suggests setting options.response.response.responseFormat
   - Prevents JSON parsing confusion
   - Triggered for URLs containing /api, /rest, supabase, firebase, googleapis, .com/v

3. **Enhanced URL Protocol Validation**
   - Detects missing protocol in expression-based URLs
   - Warns about patterns like =www.{{ $json.domain }}.com
   - Warns about expressions without protocol

## Investigation Findings

**Key Discoveries:**
- Mixed expression syntax =literal{{ expression }} actually works in n8n (claim was incorrect)
- Real validation gaps: missing alwaysOutputData and responseFormat checks
- Compared broken vs fixed workflows to identify actual production issues

**Testing Evidence:**
- Analyzed workflow SwjKJsJhe8OsYfBk with mixed syntax - executions successful
- Compared broken workflow (mBmkyj460i5rYTG4) with fixed workflow (hQI9pby3nSFtk4TV)
- Identified that fixed workflow has alwaysOutputData: true and explicit responseFormat

## Impact

- Non-Breaking: All changes are suggestions/warnings, not errors
- Actionable: Clear guidance on how to implement best practices
- Production-Focused: Addresses real workflow reliability concerns

## Test Coverage

Added 8 new test cases covering:
- alwaysOutputData suggestion for all HTTP Request nodes
- responseFormat suggestion for API endpoint detection
- responseFormat NOT suggested when already configured
- URL protocol validation for expression-based URLs
- No false positives when protocol is correctly included

## Files Changed

- src/services/enhanced-config-validator.ts - Added enhanceHttpRequestValidation()
- tests/unit/services/enhanced-config-validator.test.ts - Added 8 test cases
- CHANGELOG.md - Documented enhancement with investigation findings
- package.json - Bump version to 2.22.2

Fixes #361

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
2025-10-24 16:51:18 +02:00
Romuald Członkowski
b18f6ec7a4 Merge pull request #364 from czlonkowski/fix/if-node-connection-separation
fix: add warnings for If/Switch node connection parameters (issue #360)
2025-10-24 15:06:58 +02:00
czlonkowski
95ea6ca0bb fix: update test expectations for validateOnly mode to include warnings field
Fixed failing CI test by updating test expectations to match the new response
structure that includes a details.warnings field in validateOnly mode.

Changes:
- Updated test mock to include warnings: [] in applyDiff response
- Updated test expectations to include details: { warnings: [] }

Related to issue #360 fix.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
2025-10-24 14:53:44 +02:00
czlonkowski
a4c7e097e8 fix: pass warnings through MCP handler to user
Fixed critical bug where warnings were generated by the diff engine
but not included in the MCP response, making them invisible to users.

Now warnings are properly passed through in all return paths:
- Success path (workflow updated)
- validateOnly path (dry run mode)
- Failure path (continueOnError mode)

This completes the fix for issue #360, ensuring users receive helpful
guidance when using sourceIndex instead of branch/case parameters.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 14:28:36 +02:00
czlonkowski
0778c55d85 fix: add warnings for If/Switch node connection parameters (issue #360)
Implemented a warning system to guide users toward using smart parameters
(branch="true"/"false" for If nodes, case=N for Switch nodes) instead of
sourceIndex, which can lead to incorrect branch routing.

Changes:
- Added warnings property to WorkflowDiffResult interface
- Warnings generated when sourceIndex used with If/Switch nodes
- Enhanced tool documentation with CRITICAL pitfalls
- Added regression tests reproducing issue #360
- Version bump to 2.22.1

The branch parameter functionality works correctly - this fix adds helpful
warnings to prevent users from accidentally using the less intuitive
sourceIndex parameter.

Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 14:17:30 +02:00
Romuald Członkowski
913ff31164 Merge pull request #363 from czlonkowski/fix/release-workflow-yaml-syntax
fix: resolve YAML syntax error in release.yml workflow
2025-10-24 14:00:27 +02:00
czlonkowski
952a97ef73 fix: resolve YAML syntax error in release.yml workflow
Fixed invalid multi-line string syntax at line 148 that was breaking
YAML parsing and blocking CI on main branch.

Changed from quoted multi-line string to heredoc (cat <<EOF) which is
the proper way to handle multi-line strings in bash within GitHub Actions.

Error: "You have an error in your yaml syntax on line 148"
Root cause: Multi-line bash string using quotes breaks YAML parsing
Resolution: Use heredoc for multi-line strings in bash scripts

This resolves CI failure: https://github.com/czlonkowski/n8n-mcp/actions/runs/18777697750

Concieved by Romuald Członkowski - www.aiadvisors.pl/en

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-24 13:49:39 +02:00
Romuald Członkowski
56114f041b Merge pull request #359 from czlonkowski/feature/auto-update-node-versions 2025-10-24 12:58:31 +02:00
11 changed files with 663 additions and 5 deletions

View File

@@ -7,6 +7,138 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### ✨ Enhancements
**Issue #361: Enhanced HTTP Request Node Validation Suggestions**
Added helpful suggestions for HTTP Request node best practices to prevent common production issues discovered through real-world workflow analysis.
#### What's New
1. **alwaysOutputData Suggestion**
- Suggests adding `alwaysOutputData: true` at node level (not in parameters)
- Prevents silent workflow failures when HTTP requests error
- Ensures downstream error handling can process failed requests
- Example suggestion: "Consider adding alwaysOutputData: true at node level for better error handling. This ensures the node produces output even when HTTP requests fail, allowing downstream error handling."
2. **responseFormat Suggestion for API Endpoints**
- Suggests setting `options.response.response.responseFormat` for API endpoints
- Prevents JSON parsing confusion
- Triggered when URL contains `/api`, `/rest`, `supabase`, `firebase`, `googleapis`, or `.com/v` patterns
- Example suggestion: "API endpoints should explicitly set options.response.response.responseFormat to 'json' or 'text' to prevent confusion about response parsing"
3. **Enhanced URL Protocol Validation**
- Detects missing protocol in expression-based URLs
- Warns about patterns like `=www.{{ $json.domain }}.com` (missing http://)
- Warns about expressions without protocol: `={{ $json.domain }}/api/data`
- Example warning: "URL expression appears to be missing http:// or https:// protocol"
#### Investigation Findings
This enhancement was developed after thorough investigation of issue #361:
**Key Discoveries:**
- ✅ Mixed expression syntax `=literal{{ expression }}` **actually works in n8n** - the issue report's primary claim was incorrect
- ✅ Real validation gaps identified: missing `alwaysOutputData` and `responseFormat` checks
- ✅ Workflow analysis showed "?" icon in UI caused by missing required URL (already caught by validation)
- ✅ Compared broken vs fixed workflows to identify actual production issues
**Testing Evidence:**
- Analyzed workflow SwjKJsJhe8OsYfBk with mixed syntax - executions successful
- Compared broken workflow (mBmkyj460i5rYTG4) with fixed workflow (hQI9pby3nSFtk4TV)
- Identified that fixed workflow has `alwaysOutputData: true` and explicit `responseFormat: "json"`
#### Impact
- **Non-Breaking**: All changes are suggestions/warnings, not errors
- **Profile-Aware**: Suggestions shown in all profiles for maximum helpfulness
- **Actionable**: Clear guidance on how to implement best practices
- **Production-Focused**: Addresses real workflow reliability concerns from actual broken workflows
#### Test Coverage
Added 8 new test cases covering:
- alwaysOutputData suggestion for all HTTP Request nodes
- responseFormat suggestion for API endpoint detection (various patterns)
- responseFormat NOT suggested when already configured
- URL protocol validation for expression-based URLs
- Protocol warnings for missing http:// in expressions
- No false positives when protocol is correctly included
#### Technical Details
**Files Modified:**
- `src/services/enhanced-config-validator.ts` - Added `enhanceHttpRequestValidation()` implementation
- `tests/unit/services/enhanced-config-validator.test.ts` - Added 8 comprehensive test cases
**Validation Flow:**
1. Check for alwaysOutputData suggestion (all HTTP Request nodes)
2. Detect API endpoints by URL patterns
3. Check for explicit responseFormat configuration
4. Validate expression-based URLs for protocol issues
#### Related
- **Issue**: #361 - validate_node_operation: Missing critical HTTP Request node configuration checks
- **Analysis**: Deep investigation with @agent-Explore and @agent-n8n-mcp-tester
- **Workflows Analyzed**:
- SwjKJsJhe8OsYfBk (mixed syntax test)
- mBmkyj460i5rYTG4 (broken workflow)
- hQI9pby3nSFtk4TV (fixed workflow)
Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
---
### 🐛 Bug Fixes
**Issue #360: Enhanced Warnings for If/Switch Node Connection Parameters**
Fixed issue where users could unintentionally place multiple If node connections on the same branch (TRUE/FALSE) when using `sourceIndex` parameter instead of the recommended `branch` parameter. The system now provides helpful warnings to guide users toward better practices.
#### What Was Fixed
1. **New Warning System**:
- Warns when using `sourceIndex` with If nodes - suggests `branch="true"` or `branch="false"` instead
- Warns when using `sourceIndex` with Switch nodes - suggests `case=N` instead
- Explains the correct branch structure: `main[0]=TRUE branch, main[1]=FALSE branch`
2. **Enhanced Documentation**:
- Added **CRITICAL** pitfalls to `n8n_update_partial_workflow` tool documentation
- Clear guidance that using `sourceIndex=0` for multiple connections puts them ALL on the TRUE branch
- Examples showing correct vs. incorrect usage
3. **Type System Improvements**:
- Added `warnings` field to `WorkflowDiffResult` interface
- Warnings are non-blocking (operations still succeed)
- Differentiated from errors for better UX
#### Behavior
The existing `branch` parameter works correctly and has comprehensive test coverage:
- `branch="true"` → routes to `main[0]` (TRUE path)
- `branch="false"` → routes to `main[1]` (FALSE path)
The issue was that users who didn't know about the `branch` parameter would naturally use `sourceIndex`, which led to incorrect branch routing.
#### Example Warning
```
Connection to If node "Check Condition" uses sourceIndex=0.
Consider using branch="true" or branch="false" for better clarity.
If node outputs: main[0]=TRUE branch, main[1]=FALSE branch.
```
#### Test Coverage
- Added regression tests that reproduce the exact issue from #360
- Verify warnings are generated for If and Switch nodes
- Confirm existing smart parameter tests still pass
**Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en**
---
### ✨ New Features
**Auto-Update Node Versions with Smart Migration**

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "n8n-mcp",
"version": "2.22.0",
"version": "2.22.2",
"description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
"main": "dist/index.js",
"types": "dist/index.d.ts",

View File

@@ -138,6 +138,7 @@ export async function handleUpdatePartialWorkflow(
error: 'Failed to apply diff operations',
details: {
errors: diffResult.errors,
warnings: diffResult.warnings,
operationsApplied: diffResult.operationsApplied,
applied: diffResult.applied,
failed: diffResult.failed
@@ -154,6 +155,9 @@ export async function handleUpdatePartialWorkflow(
data: {
valid: true,
operationsToApply: input.operations.length
},
details: {
warnings: diffResult.warnings
}
};
}
@@ -252,7 +256,8 @@ export async function handleUpdatePartialWorkflow(
workflowName: updatedWorkflow.name,
applied: diffResult.applied,
failed: diffResult.failed,
errors: diffResult.errors
errors: diffResult.errors,
warnings: diffResult.warnings
}
};
} catch (error) {

View File

@@ -272,6 +272,8 @@ Please choose a different name.
'Use "updates" property for updateNode operations: {type: "updateNode", updates: {...}}',
'Smart parameters (branch, case) only work with IF and Switch nodes - ignored for other node types',
'Explicit sourceIndex overrides smart parameters (branch, case) if both provided',
'**CRITICAL**: For If nodes, ALWAYS use branch="true"/"false" instead of sourceIndex. Using sourceIndex=0 for multiple connections will put them ALL on the TRUE branch (main[0]), breaking your workflow logic!',
'**CRITICAL**: For Switch nodes, ALWAYS use case=N instead of sourceIndex. Using same sourceIndex for multiple connections will put them on the same case output.',
'cleanStaleConnections removes ALL broken connections - cannot be selective',
'replaceConnections overwrites entire connections object - all previous connections lost',
'**Auto-sanitization behavior**: Binary operators (equals, contains) automatically have singleValue removed; unary operators (isEmpty, isNotEmpty) automatically get singleValue:true added',

View File

@@ -401,7 +401,59 @@ export class EnhancedConfigValidator extends ConfigValidator {
config: Record<string, any>,
result: EnhancedValidationResult
): void {
// Examples removed - validation provides error messages and fixes instead
const url = String(config.url || '');
const options = config.options || {};
// 1. Suggest alwaysOutputData for better error handling (node-level property)
// Note: We can't check if it exists (it's node-level, not in parameters),
// but we can suggest it as a best practice
if (!result.suggestions.some(s => typeof s === 'string' && s.includes('alwaysOutputData'))) {
result.suggestions.push(
'Consider adding alwaysOutputData: true at node level (not in parameters) for better error handling. ' +
'This ensures the node produces output even when HTTP requests fail, allowing downstream error handling.'
);
}
// 2. Suggest responseFormat for API endpoints
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(
'API endpoints should explicitly set options.response.response.responseFormat to "json" or "text" ' +
'to prevent confusion about response parsing. Example: ' +
'{ "options": { "response": { "response": { "responseFormat": "json" } } } }'
);
}
// 3. Enhanced URL protocol validation for expressions
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 (case-insensitive)
if (expressionContent.startsWith('www.') ||
(expressionContent.includes('{{') && !lowerExpression.includes('http'))) {
result.warnings.push({
type: 'invalid_value',
property: 'url',
message: 'URL expression appears to be missing http:// or https:// protocol',
suggestion: 'Include protocol in your expression. Example: ={{ "https://" + $json.domain + ".com" }}'
});
}
}
}
/**

View File

@@ -38,6 +38,8 @@ const logger = new Logger({ prefix: '[WorkflowDiffEngine]' });
export class WorkflowDiffEngine {
// Track node name changes during operations for connection reference updates
private renameMap: Map<string, string> = new Map();
// Track warnings during operation processing
private warnings: WorkflowDiffValidationError[] = [];
/**
* Apply diff operations to a workflow
@@ -47,8 +49,9 @@ export class WorkflowDiffEngine {
request: WorkflowDiffRequest
): Promise<WorkflowDiffResult> {
try {
// Reset rename tracking for this diff operation
// Reset tracking for this diff operation
this.renameMap.clear();
this.warnings = [];
// Clone workflow to avoid modifying original
const workflowCopy = JSON.parse(JSON.stringify(workflow));
@@ -114,6 +117,7 @@ export class WorkflowDiffEngine {
? 'Validation successful. All operations are valid.'
: `Validation completed with ${errors.length} errors.`,
errors: errors.length > 0 ? errors : undefined,
warnings: this.warnings.length > 0 ? this.warnings : undefined,
applied: appliedIndices,
failed: failedIndices
};
@@ -126,6 +130,7 @@ export class WorkflowDiffEngine {
operationsApplied: appliedIndices.length,
message: `Applied ${appliedIndices.length} operations, ${failedIndices.length} failed (continueOnError mode)`,
errors: errors.length > 0 ? errors : undefined,
warnings: this.warnings.length > 0 ? this.warnings : undefined,
applied: appliedIndices,
failed: failedIndices
};
@@ -213,7 +218,8 @@ export class WorkflowDiffEngine {
success: true,
workflow: workflowCopy,
operationsApplied,
message: `Successfully applied ${operationsApplied} operations (${nodeOperations.length} node ops, ${otherOperations.length} other ops)`
message: `Successfully applied ${operationsApplied} operations (${nodeOperations.length} node ops, ${otherOperations.length} other ops)`,
warnings: this.warnings.length > 0 ? this.warnings : undefined
};
}
} catch (error) {
@@ -685,6 +691,24 @@ export class WorkflowDiffEngine {
sourceIndex = operation.case;
}
// Validation: Warn if using sourceIndex with If/Switch nodes without smart parameters
if (sourceNode && operation.sourceIndex !== undefined && operation.branch === undefined && operation.case === undefined) {
if (sourceNode.type === 'n8n-nodes-base.if') {
this.warnings.push({
operation: -1, // Not tied to specific operation index in request
message: `Connection to If node "${operation.source}" uses sourceIndex=${operation.sourceIndex}. ` +
`Consider using branch="true" or branch="false" for better clarity. ` +
`If node outputs: main[0]=TRUE branch, main[1]=FALSE branch.`
});
} else if (sourceNode.type === 'n8n-nodes-base.switch') {
this.warnings.push({
operation: -1, // Not tied to specific operation index in request
message: `Connection to Switch node "${operation.source}" uses sourceIndex=${operation.sourceIndex}. ` +
`Consider using case=N for better clarity (case=0 for first output, case=1 for second, etc.).`
});
}
}
return { sourceOutput, sourceIndex };
}

View File

@@ -170,6 +170,7 @@ export interface WorkflowDiffResult {
success: boolean;
workflow?: any; // Updated workflow if successful
errors?: WorkflowDiffValidationError[];
warnings?: WorkflowDiffValidationError[]; // Non-blocking warnings (e.g., parameter suggestions)
operationsApplied?: number;
message?: string;
applied?: number[]; // Indices of successfully applied operations (when continueOnError is true)

View File

@@ -188,6 +188,7 @@ describe('handlers-workflow-diff', () => {
operationsApplied: 1,
message: 'Validation successful',
errors: [],
warnings: []
});
const result = await handleUpdatePartialWorkflow(diffRequest, mockRepository);
@@ -199,6 +200,9 @@ describe('handlers-workflow-diff', () => {
valid: true,
operationsToApply: 1,
},
details: {
warnings: []
}
});
expect(mockApiClient.updateWorkflow).not.toHaveBeenCalled();

View File

@@ -802,4 +802,335 @@ describe('EnhancedConfigValidator', () => {
expect(result.errors[0].property).toBe('test');
});
});
describe('enhanceHttpRequestValidation', () => {
it('should suggest alwaysOutputData for HTTP Request nodes', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: 'https://api.example.com/data',
method: 'GET'
};
const properties = [
{ name: 'url', type: 'string', required: true },
{ name: 'method', type: 'options', required: false }
];
const result = EnhancedConfigValidator.validateWithMode(
nodeType,
config,
properties,
'operation',
'ai-friendly'
);
expect(result.valid).toBe(true);
expect(result.suggestions).toContainEqual(
expect.stringContaining('alwaysOutputData: true at node level')
);
expect(result.suggestions).toContainEqual(
expect.stringContaining('ensures the node produces output even when HTTP requests fail')
);
});
it('should suggest responseFormat for API endpoint URLs', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: 'https://api.example.com/data',
method: 'GET',
options: {} // Empty options, no responseFormat
};
const properties = [
{ name: 'url', type: 'string', required: true },
{ name: 'method', type: 'options', required: false },
{ name: 'options', type: 'collection', required: false }
];
const result = EnhancedConfigValidator.validateWithMode(
nodeType,
config,
properties,
'operation',
'ai-friendly'
);
expect(result.valid).toBe(true);
expect(result.suggestions).toContainEqual(
expect.stringContaining('responseFormat')
);
expect(result.suggestions).toContainEqual(
expect.stringContaining('options.response.response.responseFormat')
);
});
it('should suggest responseFormat for Supabase URLs', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: 'https://xxciwnthnnywanbplqwg.supabase.co/rest/v1/messages',
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 NOT suggest responseFormat when already configured', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: 'https://api.example.com/data',
method: 'GET',
options: {
response: {
response: {
responseFormat: 'json'
}
}
}
};
const properties = [
{ name: 'url', type: 'string', required: true },
{ name: 'options', type: 'collection', required: false }
];
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 warn about missing protocol in expression-based URLs', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: '=www.{{ $json.domain }}.com',
method: 'GET'
};
const properties = [
{ name: 'url', type: 'string', required: true }
];
const result = EnhancedConfigValidator.validateWithMode(
nodeType,
config,
properties,
'operation',
'ai-friendly'
);
expect(result.warnings).toContainEqual(
expect.objectContaining({
type: 'invalid_value',
property: 'url',
message: expect.stringContaining('missing http:// or https://')
})
);
});
it('should warn about missing protocol in expressions with template markers', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: '={{ $json.domain }}/api/data',
method: 'GET'
};
const properties = [
{ name: 'url', type: 'string', required: true }
];
const result = EnhancedConfigValidator.validateWithMode(
nodeType,
config,
properties,
'operation',
'ai-friendly'
);
expect(result.warnings).toContainEqual(
expect.objectContaining({
type: 'invalid_value',
property: 'url',
message: expect.stringContaining('missing http:// or https://')
})
);
});
it('should NOT warn when expression includes http protocol', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: '={{ "https://" + $json.domain + ".com" }}',
method: 'GET'
};
const properties = [
{ name: 'url', type: 'string', required: true }
];
const result = EnhancedConfigValidator.validateWithMode(
nodeType,
config,
properties,
'operation',
'ai-friendly'
);
const urlWarning = result.warnings.find(
(w: any) => w.property === 'url' && w.message.includes('protocol')
);
expect(urlWarning).toBeUndefined();
});
it('should NOT suggest responseFormat for non-API URLs', () => {
const nodeType = 'nodes-base.httpRequest';
const config = {
url: 'https://example.com/page.html',
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 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();
});
});
});
});

View File

@@ -1418,6 +1418,113 @@ describe('WorkflowDiffEngine', () => {
expect(result.workflow!.connections['Switch']['main'][2][0].node).toBe('Handler');
expect(result.workflow!.connections['Switch']['main'][1]).toEqual([]);
});
it('should warn when using sourceIndex with If node (issue #360)', async () => {
const addIF: any = {
type: 'addNode',
node: {
name: 'Check Condition',
type: 'n8n-nodes-base.if',
position: [400, 300]
}
};
const addSuccess: any = {
type: 'addNode',
node: {
name: 'Success Handler',
type: 'n8n-nodes-base.set',
position: [600, 200]
}
};
const addError: any = {
type: 'addNode',
node: {
name: 'Error Handler',
type: 'n8n-nodes-base.set',
position: [600, 400]
}
};
// BAD: Using sourceIndex with If node (reproduces issue #360)
const connectSuccess: any = {
type: 'addConnection',
source: 'Check Condition',
target: 'Success Handler',
sourceIndex: 0 // Should use branch="true" instead
};
const connectError: any = {
type: 'addConnection',
source: 'Check Condition',
target: 'Error Handler',
sourceIndex: 0 // Should use branch="false" instead - both will end up in main[0]!
};
const request: WorkflowDiffRequest = {
id: 'test-workflow',
operations: [addIF, addSuccess, addError, connectSuccess, connectError]
};
const result = await diffEngine.applyDiff(baseWorkflow, request);
expect(result.success).toBe(true);
// Should produce warnings
expect(result.warnings).toBeDefined();
expect(result.warnings!.length).toBe(2);
expect(result.warnings![0].message).toContain('Consider using branch="true" or branch="false"');
expect(result.warnings![0].message).toContain('If node outputs: main[0]=TRUE branch, main[1]=FALSE branch');
expect(result.warnings![1].message).toContain('Consider using branch="true" or branch="false"');
// Both connections end up in main[0] (the bug behavior)
expect(result.workflow!.connections['Check Condition']['main'][0].length).toBe(2);
expect(result.workflow!.connections['Check Condition']['main'][0][0].node).toBe('Success Handler');
expect(result.workflow!.connections['Check Condition']['main'][0][1].node).toBe('Error Handler');
});
it('should warn when using sourceIndex with Switch node', async () => {
const addSwitch: any = {
type: 'addNode',
node: {
name: 'Switch',
type: 'n8n-nodes-base.switch',
position: [400, 300]
}
};
const addHandler: any = {
type: 'addNode',
node: {
name: 'Handler',
type: 'n8n-nodes-base.set',
position: [600, 300]
}
};
// BAD: Using sourceIndex with Switch node
const connect: any = {
type: 'addConnection',
source: 'Switch',
target: 'Handler',
sourceIndex: 1 // Should use case=1 instead
};
const request: WorkflowDiffRequest = {
id: 'test-workflow',
operations: [addSwitch, addHandler, connect]
};
const result = await diffEngine.applyDiff(baseWorkflow, request);
expect(result.success).toBe(true);
// Should produce warning
expect(result.warnings).toBeDefined();
expect(result.warnings!.length).toBe(1);
expect(result.warnings![0].message).toContain('Consider using case=N for better clarity');
});
});
describe('AddConnection with sourceIndex (Phase 0 Fix)', () => {