mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-03-20 09:23:07 +00:00
- Fix $() node reference triggering "Invalid $ usage" warning by adding ( and _ to regex lookahead - Fix helper function primitive returns triggering "Cannot return primitive values" error - Fix null values in diff engine causing Zod validation errors — null now deletes properties - Update property removal docs from undefined to null 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:
committed by
GitHub
parent
599bc664d0
commit
65ab94deb2
@@ -1912,6 +1912,55 @@ return [{"json": {"result": result}}]
|
||||
});
|
||||
});
|
||||
|
||||
it('should not error on primitive return inside helper functions', () => {
|
||||
context.config = {
|
||||
language: 'javaScript',
|
||||
jsCode: 'function isValid(item) { return false; }\nconst items = $input.all();\nreturn items.filter(isValid).map(i => ({json: i.json}));'
|
||||
};
|
||||
|
||||
NodeSpecificValidators.validateCode(context);
|
||||
|
||||
const primitiveErrors = context.errors.filter(e => e.message === 'Cannot return primitive values directly');
|
||||
expect(primitiveErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not error on primitive return inside arrow function helpers', () => {
|
||||
context.config = {
|
||||
language: 'javaScript',
|
||||
jsCode: 'const isValid = (item) => { return false; };\nreturn $input.all().filter(isValid).map(i => ({json: i.json}));'
|
||||
};
|
||||
|
||||
NodeSpecificValidators.validateCode(context);
|
||||
|
||||
const primitiveErrors = context.errors.filter(e => e.message === 'Cannot return primitive values directly');
|
||||
expect(primitiveErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not error on primitive return inside async function helpers', () => {
|
||||
context.config = {
|
||||
language: 'javaScript',
|
||||
jsCode: 'async function fetchData(url) { return null; }\nconst data = await fetchData("https://api.example.com");\nreturn [{json: {data}}];'
|
||||
};
|
||||
|
||||
NodeSpecificValidators.validateCode(context);
|
||||
|
||||
const primitiveErrors = context.errors.filter(e => e.message === 'Cannot return primitive values directly');
|
||||
expect(primitiveErrors).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should still error on primitive return without helper functions', () => {
|
||||
context.config = {
|
||||
language: 'javaScript',
|
||||
jsCode: 'return "success";'
|
||||
};
|
||||
|
||||
NodeSpecificValidators.validateCode(context);
|
||||
|
||||
expect(context.errors).toContainEqual(expect.objectContaining({
|
||||
message: 'Cannot return primitive values directly'
|
||||
}));
|
||||
});
|
||||
|
||||
it('should error on Python primitive return', () => {
|
||||
context.config = {
|
||||
language: 'python',
|
||||
@@ -2038,6 +2087,30 @@ return [{"json": {"result": result}}]
|
||||
});
|
||||
});
|
||||
|
||||
it('should not warn about $() node reference syntax', () => {
|
||||
context.config = {
|
||||
language: 'javaScript',
|
||||
jsCode: 'const data = $("Previous Node").first().json;\nreturn [{json: data}];'
|
||||
};
|
||||
|
||||
NodeSpecificValidators.validateCode(context);
|
||||
|
||||
const dollarWarnings = context.warnings.filter(w => w.message === 'Invalid $ usage detected');
|
||||
expect(dollarWarnings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should not warn about $_ variables', () => {
|
||||
context.config = {
|
||||
language: 'javaScript',
|
||||
jsCode: 'const $_temp = 1;\nreturn [{json: {value: $_temp}}];'
|
||||
};
|
||||
|
||||
NodeSpecificValidators.validateCode(context);
|
||||
|
||||
const dollarWarnings = context.warnings.filter(w => w.message === 'Invalid $ usage detected');
|
||||
expect(dollarWarnings).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('should correct helpers usage', () => {
|
||||
context.config = {
|
||||
language: 'javaScript',
|
||||
|
||||
@@ -4882,4 +4882,111 @@ describe('WorkflowDiffEngine', () => {
|
||||
expect(result.workflow.connections['Source Node']['main'][0][0].type).toBe('main');
|
||||
});
|
||||
});
|
||||
|
||||
describe('null value property deletion', () => {
|
||||
it('should delete a property when value is null', async () => {
|
||||
const node = baseWorkflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
(node as any).continueOnFail = true;
|
||||
|
||||
const operation: UpdateNodeOperation = {
|
||||
type: 'updateNode',
|
||||
nodeName: 'HTTP Request',
|
||||
updates: { continueOnFail: null }
|
||||
};
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations: [operation]
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
const updatedNode = result.workflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
expect('continueOnFail' in updatedNode).toBe(false);
|
||||
});
|
||||
|
||||
it('should delete a nested property when value is null', async () => {
|
||||
const node = baseWorkflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
(node as any).parameters = { url: 'https://example.com', authentication: 'basic' };
|
||||
|
||||
const operation: UpdateNodeOperation = {
|
||||
type: 'updateNode',
|
||||
nodeName: 'HTTP Request',
|
||||
updates: { 'parameters.authentication': null }
|
||||
};
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations: [operation]
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
const updatedNode = result.workflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
expect((updatedNode as any).parameters.url).toBe('https://example.com');
|
||||
expect('authentication' in (updatedNode as any).parameters).toBe(false);
|
||||
});
|
||||
|
||||
it('should set property normally when value is not null', async () => {
|
||||
const operation: UpdateNodeOperation = {
|
||||
type: 'updateNode',
|
||||
nodeName: 'HTTP Request',
|
||||
updates: { continueOnFail: true }
|
||||
};
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations: [operation]
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
const updatedNode = result.workflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
expect((updatedNode as any).continueOnFail).toBe(true);
|
||||
});
|
||||
|
||||
it('should be a no-op when deleting a non-existent property', async () => {
|
||||
const node = baseWorkflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
const originalKeys = Object.keys(node).sort();
|
||||
|
||||
const operation: UpdateNodeOperation = {
|
||||
type: 'updateNode',
|
||||
nodeName: 'HTTP Request',
|
||||
updates: { nonExistentProp: null }
|
||||
};
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations: [operation]
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
const updatedNode = result.workflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
expect('nonExistentProp' in updatedNode).toBe(false);
|
||||
});
|
||||
|
||||
it('should skip intermediate object creation when deleting from non-existent parent path', async () => {
|
||||
const operation: UpdateNodeOperation = {
|
||||
type: 'updateNode',
|
||||
nodeName: 'HTTP Request',
|
||||
updates: { 'nonExistent.deeply.nested.prop': null }
|
||||
};
|
||||
|
||||
const request: WorkflowDiffRequest = {
|
||||
id: 'test-workflow',
|
||||
operations: [operation]
|
||||
};
|
||||
|
||||
const result = await diffEngine.applyDiff(baseWorkflow, request);
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
const updatedNode = result.workflow.nodes.find((n: any) => n.name === 'HTTP Request')!;
|
||||
expect('nonExistent' in updatedNode).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user