mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 01:56:46 +00:00
fix(core): AI agent node data accessibility (#18757)
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,10 @@ describe('WorkflowDataProxy', () => {
|
||||
expect(proxy.$('Rename').params).toEqual({ value1: 'data', value2: 'initialName' });
|
||||
});
|
||||
|
||||
test('$("NodeName").context', () => {
|
||||
expect(proxy.$('Rename').context).toBeDefined();
|
||||
});
|
||||
|
||||
test('$("NodeName") not in workflow should throw', () => {
|
||||
expect(() => proxy.$('doNotExist')).toThrowError(ExpressionError);
|
||||
});
|
||||
@@ -188,6 +192,12 @@ describe('WorkflowDataProxy', () => {
|
||||
test('$input.item', () => {
|
||||
expect(proxy.$input.item?.json?.data).toEqual(105);
|
||||
});
|
||||
test('$input.context', () => {
|
||||
expect(proxy.$input.context).toBeDefined();
|
||||
});
|
||||
test('$input.params', () => {
|
||||
expect(proxy.$input.params).toBeDefined();
|
||||
});
|
||||
test('$thisItem', () => {
|
||||
expect(proxy.$thisItem.json.data).toEqual(105);
|
||||
});
|
||||
@@ -262,8 +272,8 @@ describe('WorkflowDataProxy', () => {
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('Referenced node is unexecuted');
|
||||
expect(exprError.context.type).toEqual('no_node_execution_data');
|
||||
expect(exprError.message).toEqual("Node 'Impossible' hasn't been executed");
|
||||
expect(exprError.context.type).toEqual('no_execution_data');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -274,8 +284,8 @@ describe('WorkflowDataProxy', () => {
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('No execution data available');
|
||||
expect(exprError.context.type).toEqual('no_input_connection');
|
||||
expect(exprError.message).toEqual("Node 'NoInputConnection' hasn't been executed");
|
||||
expect(exprError.context.type).toEqual('no_execution_data');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -286,8 +296,8 @@ describe('WorkflowDataProxy', () => {
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('Referenced node is unexecuted');
|
||||
expect(exprError.context.type).toEqual('no_node_execution_data');
|
||||
expect(exprError.message).toEqual("Node 'Impossible if' hasn't been executed");
|
||||
expect(exprError.context.type).toEqual('no_execution_data');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -298,7 +308,7 @@ describe('WorkflowDataProxy', () => {
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
const exprError = error as ExpressionError;
|
||||
expect(exprError.message).toEqual('No execution data available');
|
||||
expect(exprError.message).toEqual("Node 'Impossible' hasn't been executed");
|
||||
expect(exprError.context.type).toEqual('no_execution_data');
|
||||
}
|
||||
});
|
||||
@@ -826,4 +836,306 @@ describe('WorkflowDataProxy', () => {
|
||||
expect(proxy.$('Set main variable').item.json.main_variable).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Improved error messages for missing execution data', () => {
|
||||
test('should show helpful error message when accessing node without execution data', () => {
|
||||
// Create a simple workflow with two connected nodes
|
||||
const workflow: IWorkflowBase = {
|
||||
id: '1',
|
||||
name: 'test-workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Telegram Trigger',
|
||||
type: 'n8n-nodes-base.telegramTrigger',
|
||||
typeVersion: 1.2,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Send a text message',
|
||||
type: 'n8n-nodes-base.telegram',
|
||||
typeVersion: 1.2,
|
||||
position: [576, 0],
|
||||
parameters: {
|
||||
chatId: "={{ $('Telegram Trigger').item.json.message.chat.id }}",
|
||||
text: 'Test message',
|
||||
},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
'Telegram Trigger': {
|
||||
main: [[{ node: 'Send a text message', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
isArchived: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
// Create run data without execution data for Telegram Trigger
|
||||
const run = {
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {}, // Empty - no nodes have executed
|
||||
},
|
||||
},
|
||||
mode: 'manual' as const,
|
||||
startedAt: new Date(),
|
||||
status: 'success' as const,
|
||||
};
|
||||
|
||||
const proxy = getProxyFromFixture(workflow, run, 'Send a text message');
|
||||
|
||||
// Should throw helpful error when trying to access Telegram Trigger data
|
||||
let error: ExpressionError | undefined;
|
||||
try {
|
||||
proxy.$('Telegram Trigger').item;
|
||||
} catch (e) {
|
||||
error = e as ExpressionError;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
expect(error!.message).toBe("Node 'Telegram Trigger' hasn't been executed");
|
||||
expect(error!.context.type).toBe('no_execution_data');
|
||||
expect(error!.context.messageTemplate).toBe(
|
||||
'An expression references this node, but the node is unexecuted. Consider re-wiring your nodes or checking for execution first, i.e. {{ $if( $("{{nodeName}}").isExecuted, <action_if_executed>, "") }}',
|
||||
);
|
||||
});
|
||||
|
||||
test('should show helpful error message for different node names', () => {
|
||||
const workflow: IWorkflowBase = {
|
||||
id: '1',
|
||||
name: 'test-workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'HTTP Request',
|
||||
type: 'n8n-nodes-base.httpRequest',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Process Data',
|
||||
type: 'n8n-nodes-base.code',
|
||||
typeVersion: 2,
|
||||
position: [300, 0],
|
||||
parameters: {
|
||||
jsCode: "return $('HTTP Request').all();",
|
||||
},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
'HTTP Request': {
|
||||
main: [[{ node: 'Process Data', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
isArchived: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const run = {
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {}, // Empty - no nodes have executed
|
||||
},
|
||||
},
|
||||
mode: 'manual' as const,
|
||||
startedAt: new Date(),
|
||||
status: 'success' as const,
|
||||
};
|
||||
|
||||
const proxy = getProxyFromFixture(workflow, run, 'Process Data');
|
||||
|
||||
let error: ExpressionError | undefined;
|
||||
try {
|
||||
proxy.$('HTTP Request').item;
|
||||
} catch (e) {
|
||||
error = e as ExpressionError;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error!.message).toBe("Node 'HTTP Request' hasn't been executed");
|
||||
expect(error!.context.type).toBe('no_execution_data');
|
||||
expect(error!.context.messageTemplate).toBe(
|
||||
'An expression references this node, but the node is unexecuted. Consider re-wiring your nodes or checking for execution first, i.e. {{ $if( $("{{nodeName}}").isExecuted, <action_if_executed>, "") }}',
|
||||
);
|
||||
});
|
||||
|
||||
test('should use improved error for first(), last(), and all() methods', () => {
|
||||
const workflow: IWorkflowBase = {
|
||||
id: '1',
|
||||
name: 'test-workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Start Node',
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'End Node',
|
||||
type: 'n8n-nodes-base.noOp',
|
||||
typeVersion: 1,
|
||||
position: [300, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {
|
||||
'Start Node': {
|
||||
main: [[{ node: 'End Node', type: NodeConnectionTypes.Main, index: 0 }]],
|
||||
},
|
||||
},
|
||||
active: false,
|
||||
isArchived: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const run = {
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {}, // Empty - no nodes have executed
|
||||
},
|
||||
},
|
||||
mode: 'manual' as const,
|
||||
startedAt: new Date(),
|
||||
status: 'success' as const,
|
||||
};
|
||||
|
||||
const proxy = getProxyFromFixture(workflow, run, 'End Node');
|
||||
|
||||
// Test first() method
|
||||
let error: ExpressionError | undefined;
|
||||
try {
|
||||
proxy.$('Start Node').first();
|
||||
} catch (e) {
|
||||
error = e as ExpressionError;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!.message).toBe("Node 'Start Node' hasn't been executed");
|
||||
expect(error!.context.messageTemplate).toBe(
|
||||
'An expression references this node, but the node is unexecuted. Consider re-wiring your nodes or checking for execution first, i.e. {{ $if( $("{{nodeName}}").isExecuted, <action_if_executed>, "") }}',
|
||||
);
|
||||
|
||||
// Test last() method
|
||||
error = undefined;
|
||||
try {
|
||||
proxy.$('Start Node').last();
|
||||
} catch (e) {
|
||||
error = e as ExpressionError;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!.message).toBe("Node 'Start Node' hasn't been executed");
|
||||
expect(error!.context.messageTemplate).toBe(
|
||||
'An expression references this node, but the node is unexecuted. Consider re-wiring your nodes or checking for execution first, i.e. {{ $if( $("{{nodeName}}").isExecuted, <action_if_executed>, "") }}',
|
||||
);
|
||||
|
||||
// Test all() method
|
||||
error = undefined;
|
||||
try {
|
||||
proxy.$('Start Node').all();
|
||||
} catch (e) {
|
||||
error = e as ExpressionError;
|
||||
}
|
||||
expect(error).toBeDefined();
|
||||
expect(error!.message).toBe("Node 'Start Node' hasn't been executed");
|
||||
expect(error!.context.messageTemplate).toBe(
|
||||
'An expression references this node, but the node is unexecuted. Consider re-wiring your nodes or checking for execution first, i.e. {{ $if( $("{{nodeName}}").isExecuted, <action_if_executed>, "") }}',
|
||||
);
|
||||
});
|
||||
|
||||
test('should show helpful error message when accessing non-existent node', () => {
|
||||
const workflow: IWorkflowBase = {
|
||||
id: '1',
|
||||
name: 'test-workflow',
|
||||
nodes: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Real Node',
|
||||
type: 'n8n-nodes-base.manualTrigger',
|
||||
typeVersion: 1,
|
||||
position: [0, 0],
|
||||
parameters: {},
|
||||
},
|
||||
],
|
||||
connections: {},
|
||||
active: false,
|
||||
isArchived: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
};
|
||||
|
||||
const run = {
|
||||
data: {
|
||||
resultData: {
|
||||
runData: {
|
||||
'Real Node': [
|
||||
{
|
||||
data: {
|
||||
main: [[{ json: { test: 'data' } }]],
|
||||
},
|
||||
source: [null],
|
||||
startTime: 123,
|
||||
executionTime: 456,
|
||||
executionIndex: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
mode: 'manual' as const,
|
||||
startedAt: new Date(),
|
||||
status: 'success' as const,
|
||||
};
|
||||
|
||||
const proxy = getProxyFromFixture(workflow, run, 'Real Node');
|
||||
|
||||
// Should throw helpful error when trying to access a non-existent node
|
||||
let error: ExpressionError | undefined;
|
||||
try {
|
||||
proxy.$('NonExistentNode').item;
|
||||
} catch (e) {
|
||||
error = e as ExpressionError;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
expect(error!.message).toBe("Referenced node doesn't exist");
|
||||
expect(error!.context.descriptionKey).toBe('nodeNotFound');
|
||||
expect(error!.context.nodeCause).toBe('NonExistentNode');
|
||||
});
|
||||
|
||||
test('should show error when accessing item with invalid index via direct proxy access', () => {
|
||||
// Use existing fixture data to test the item index validation path
|
||||
const fixture = loadFixture('base');
|
||||
|
||||
// Create a proxy with itemIndex that exceeds available items for a node
|
||||
const proxy = getProxyFromFixture(fixture.workflow, fixture.run, 'Set Node', 'manual', {
|
||||
throwOnMissingExecutionData: true,
|
||||
runIndex: 10, // itemIndex way too high
|
||||
});
|
||||
|
||||
let error: ExpressionError | undefined;
|
||||
try {
|
||||
// This should trigger the error path for invalid item index
|
||||
proxy.$('Set Node').item;
|
||||
} catch (e) {
|
||||
error = e as ExpressionError;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error).toBeInstanceOf(ExpressionError);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user