fix: correct operator names, connection types, and implement __patch_find_replace (#665, #659, #642) (#672)

Three critical fixes in n8n_update_partial_workflow:

- **#665**: Replace incorrect `isNotEmpty`/`isEmpty` operator names with `notEmpty`/`empty`
  across validators, sanitizer, docs, and error messages. Add auto-correction in sanitizer.
  Unknown operators silently returned false in n8n's execution engine.

- **#659**: Remap numeric `targetInput` values (e.g., "0") to "main" in addConnection.
  Relax sourceOutput remapping guard for redundant sourceOutput+sourceIndex combinations.
  Also resolves #653 (dangling connections caused by malformed type:"0" connections).

- **#642**: Implement __patch_find_replace for surgical string edits in updateNode.
  Previously stored patch objects literally as jsCode, producing [object Object].
  Now reads current value, applies find/replace sequentially, writes back the string.

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

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Romuald Członkowski
2026-03-27 16:26:21 +01:00
committed by GitHub
parent de2abaf89d
commit 6be9ffa53e
28 changed files with 522 additions and 96 deletions

View File

@@ -102,9 +102,9 @@ describe('Node Sanitizer', () => {
const sanitized = sanitizeNode(node);
const condition = (sanitized.parameters.conditions as any).conditions[0];
// Should fix operator structure
expect(condition.operator.type).toBe('boolean'); // Inferred data type (isEmpty/isNotEmpty are boolean ops)
expect(condition.operator.operation).toBe('isNotEmpty'); // Moved to operation field
// Should fix operator structure and auto-correct isNotEmpty to notEmpty
expect(condition.operator.type).toBe('string'); // Inferred data type (default)
expect(condition.operator.operation).toBe('notEmpty'); // Moved to operation field and auto-corrected
});
it('should add singleValue for unary operators', () => {
@@ -253,6 +253,39 @@ describe('Node Sanitizer', () => {
expect(condition.operator.type).toBe('string');
expect(condition.operator.operation).toBe('equals');
});
it('should auto-correct isNotEmpty to notEmpty', () => {
const node: WorkflowNode = {
id: 'test-if-autocorrect',
name: 'IF AutoCorrect',
type: 'n8n-nodes-base.if',
typeVersion: 2.2,
position: [0, 0],
parameters: {
conditions: {
conditions: [
{
id: 'condition1',
leftValue: '={{ $json.value }}',
rightValue: '',
operator: {
type: 'string',
operation: 'isNotEmpty' // Legacy operator name
}
}
]
}
}
};
const sanitized = sanitizeNode(node);
const condition = (sanitized.parameters.conditions as any).conditions[0];
// Should auto-correct isNotEmpty to notEmpty
expect(condition.operator.operation).toBe('notEmpty');
expect(condition.operator.type).toBe('string');
expect(condition.operator.singleValue).toBe(true); // notEmpty is unary
});
});
describe('validateNodeMetadata', () => {
@@ -370,7 +403,7 @@ describe('Node Sanitizer', () => {
rightValue: '',
operator: {
type: 'string',
operation: 'isNotEmpty'
operation: 'notEmpty'
// Missing singleValue: true
}
}
@@ -444,7 +477,7 @@ describe('Node Sanitizer', () => {
rightValue: '',
operator: {
type: 'string',
operation: 'isNotEmpty',
operation: 'notEmpty',
singleValue: true
}
}