fix(core): Force full execution data fetching for evaluation test runs (#27335)

This commit is contained in:
Arvin A 2026-03-23 10:56:05 +01:00 committed by GitHub
parent 3ad5926a5e
commit 87afcd8db4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 87 additions and 2 deletions

15
.vscode/launch.json vendored
View file

@ -32,6 +32,21 @@
"outputCapture": "std", "outputCapture": "std",
"killBehavior": "forceful" "killBehavior": "forceful"
}, },
{
"name": "Launch n8n CLI worker dev with debug",
"runtimeExecutable": "pnpm",
"cwd": "${workspaceFolder}/packages/cli",
"runtimeArgs": ["run", "dev:worker", "--", "--inspect-brk"],
"console": "integratedTerminal",
"restart": true,
"autoAttachChildProcesses": true,
"request": "launch",
"skipFiles": ["<node_internals>/**"],
"type": "node",
"envFile": "${workspaceFolder}/.env",
"outputCapture": "std",
"killBehavior": "polite"
},
{ {
"name": "Launch n8n CLI dev with debug", "name": "Launch n8n CLI dev with debug",
"runtimeExecutable": "pnpm", "runtimeExecutable": "pnpm",

View file

@ -572,6 +572,57 @@ describe('workflow timeout with startedAt', () => {
}); });
}); });
describe('needsFullExecutionData', () => {
const originalEnv = process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING;
afterEach(() => {
if (originalEnv === undefined) {
delete process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING;
} else {
process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = originalEnv;
}
});
it('should return true when forceFullExecutionData is true even with N8N_MINIMIZE_EXECUTION_DATA_FETCHING set', () => {
process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = 'true';
// @ts-expect-error Private method
const result = runner.needsFullExecutionData('evaluation', 'exec-id', true);
expect(result).toBe(true);
});
it('should return true when env var is not set and forceFullExecutionData is undefined', () => {
delete process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING;
// @ts-expect-error Private method
const result = runner.needsFullExecutionData('webhook', 'exec-id', undefined);
expect(result).toBe(true);
});
it('should return false when env var is set, forceFullExecutionData is undefined, and mode is not integrated', () => {
process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = 'true';
const activeExecutions = Container.get(ActiveExecutions);
jest.spyOn(activeExecutions, 'getResponseMode').mockReturnValue('responseNode');
// @ts-expect-error Private method
const result = runner.needsFullExecutionData('webhook', 'exec-id', undefined);
expect(result).toBe(false);
});
it('should return true when env var is set and mode is integrated', () => {
process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING = 'true';
// @ts-expect-error Private method
const result = runner.needsFullExecutionData('integrated', 'exec-id', undefined);
expect(result).toBe(true);
});
});
describe('streaming functionality', () => { describe('streaming functionality', () => {
it('should setup sendChunk handler when streaming is enabled and execution mode is not manual', async () => { it('should setup sendChunk handler when streaming is enabled and execution mode is not manual', async () => {
// ARRANGE // ARRANGE

View file

@ -489,6 +489,7 @@ describe('TestRunnerService', () => {
resource: 'dataset', resource: 'dataset',
operation: 'getRows', operation: 'getRows',
}); });
expect(runCallArg).toHaveProperty('forceFullExecutionData', true);
}); });
test('should call workflowRunner.run with correct data in queue execution mode and manual offload', async () => { test('should call workflowRunner.run with correct data in queue execution mode and manual offload', async () => {
@ -575,6 +576,7 @@ describe('TestRunnerService', () => {
resource: 'dataset', resource: 'dataset',
operation: 'getRows', operation: 'getRows',
}); });
expect(runCallArg).toHaveProperty('forceFullExecutionData', true);
// after reset // after reset
delete process.env.OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS; delete process.env.OFFLOAD_MANUAL_EXECUTIONS_TO_WORKERS;
@ -742,6 +744,7 @@ describe('TestRunnerService', () => {
}, },
}, },
userId: metadata.userId, userId: metadata.userId,
forceFullExecutionData: true,
triggerToStartFrom: { triggerToStartFrom: {
name: triggerNodeName, name: triggerNodeName,
}, },
@ -869,6 +872,7 @@ describe('TestRunnerService', () => {
expect(runCallArg).toEqual( expect(runCallArg).toEqual(
expect.objectContaining({ expect.objectContaining({
executionMode: 'evaluation', executionMode: 'evaluation',
forceFullExecutionData: true,
pinData: { pinData: {
[triggerNodeName]: [testCase], [triggerNodeName]: [testCase],
}, },

View file

@ -250,6 +250,7 @@ export class TestRunnerService {
const data: IWorkflowExecutionDataProcess = { const data: IWorkflowExecutionDataProcess = {
executionMode: 'evaluation', executionMode: 'evaluation',
pinData, pinData,
forceFullExecutionData: true,
workflowData: { workflowData: {
...workflow, ...workflow,
settings: { settings: {
@ -336,6 +337,7 @@ export class TestRunnerService {
destinationNode: { nodeName: triggerNode.name, mode: 'inclusive' }, destinationNode: { nodeName: triggerNode.name, mode: 'inclusive' },
executionMode: 'manual', executionMode: 'manual',
runData: {}, runData: {},
forceFullExecutionData: true,
workflowData: { workflowData: {
...workflow, ...workflow,
settings: { settings: {

View file

@ -489,7 +489,10 @@ export class WorkflowRunner {
let runData: IRun; let runData: IRun;
if (!jobResult || this.needsFullExecutionData(data.executionMode, executionId)) { if (
!jobResult ||
this.needsFullExecutionData(data.executionMode, executionId, data.forceFullExecutionData)
) {
const fullExecutionData = await this.executionRepository.findSingleExecution( const fullExecutionData = await this.executionRepository.findSingleExecution(
executionId, executionId,
{ {
@ -561,7 +564,12 @@ export class WorkflowRunner {
* In all other cases we can skip the DB fetch and use the lightweight * In all other cases we can skip the DB fetch and use the lightweight
* result summary sent by the worker via the job progress message. * result summary sent by the worker via the job progress message.
*/ */
private needsFullExecutionData(executionMode: WorkflowExecuteMode, executionId: string): boolean { private needsFullExecutionData(
executionMode: WorkflowExecuteMode,
executionId: string,
forceFullExecutionData?: boolean,
): boolean {
if (forceFullExecutionData) return true;
if (!process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING) return true; if (!process.env.N8N_MINIMIZE_EXECUTION_DATA_FETCHING) return true;
return ( return (

View file

@ -2880,6 +2880,11 @@ export interface IWorkflowExecutionDataProcess {
destinationNode?: IDestinationNode; destinationNode?: IDestinationNode;
restartExecutionId?: string; restartExecutionId?: string;
executionMode: WorkflowExecuteMode; executionMode: WorkflowExecuteMode;
/**
* When true, forces the execution data to be present in the run data
* ignores N8N_MINIMIZE_EXECUTION_DATA_FETCHING environment variable if set
*/
forceFullExecutionData?: boolean;
/** /**
* The data that is sent in the body of the webhook that started this * The data that is sent in the body of the webhook that started this
* execution. * execution.