mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
fix(core): Increment executionIndex in partial executions (no-changelog) (#14946)
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { Container } from '@n8n/di';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IWorkflowBase } from 'n8n-workflow';
|
||||
@@ -23,9 +24,11 @@ import {
|
||||
} from '@/executions/pre-execution-checks';
|
||||
import { ExternalHooks } from '@/external-hooks';
|
||||
import { SecretsHelper } from '@/secrets-helpers.ee';
|
||||
import { UrlService } from '@/services/url.service';
|
||||
import { WorkflowStatisticsService } from '@/services/workflow-statistics.service';
|
||||
import { Telemetry } from '@/telemetry';
|
||||
import { executeWorkflow, getBase, getRunData } from '@/workflow-execute-additional-data';
|
||||
import * as WorkflowHelpers from '@/workflow-helpers';
|
||||
import { mockInstance } from '@test/mocking';
|
||||
|
||||
const EXECUTION_ID = '123';
|
||||
@@ -97,6 +100,9 @@ describe('WorkflowExecuteAdditionalData', () => {
|
||||
mockInstance(SubworkflowPolicyChecker);
|
||||
mockInstance(WorkflowStatisticsService);
|
||||
|
||||
const urlService = mockInstance(UrlService);
|
||||
Container.set(UrlService, urlService);
|
||||
|
||||
test('logAiEvent should call MessageEventBus', async () => {
|
||||
const additionalData = await getBase('user-id');
|
||||
|
||||
@@ -264,4 +270,67 @@ describe('WorkflowExecuteAdditionalData', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getBase', () => {
|
||||
const mockWebhookBaseUrl = 'webhook-base-url.com';
|
||||
jest.spyOn(urlService, 'getWebhookBaseUrl').mockReturnValue(mockWebhookBaseUrl);
|
||||
|
||||
const globalConfig = mockInstance(GlobalConfig);
|
||||
Container.set(GlobalConfig, globalConfig);
|
||||
globalConfig.endpoints = mock<GlobalConfig['endpoints']>({
|
||||
rest: '/rest/',
|
||||
formWaiting: '/form-waiting/',
|
||||
webhook: '/webhook/',
|
||||
webhookWaiting: '/webhook-waiting/',
|
||||
webhookTest: '/webhook-test/',
|
||||
});
|
||||
|
||||
const mockVariables = { variable: 1 };
|
||||
jest.spyOn(WorkflowHelpers, 'getVariables').mockResolvedValue(mockVariables);
|
||||
|
||||
it('should return base additional data with default values', async () => {
|
||||
const additionalData = await getBase();
|
||||
|
||||
expect(additionalData).toMatchObject({
|
||||
currentNodeExecutionIndex: 0,
|
||||
credentialsHelper,
|
||||
executeWorkflow: expect.any(Function),
|
||||
restApiUrl: `${mockWebhookBaseUrl}/rest/`,
|
||||
instanceBaseUrl: mockWebhookBaseUrl,
|
||||
formWaitingBaseUrl: `${mockWebhookBaseUrl}/form-waiting/`,
|
||||
webhookBaseUrl: `${mockWebhookBaseUrl}/webhook/`,
|
||||
webhookWaitingBaseUrl: `${mockWebhookBaseUrl}/webhook-waiting/`,
|
||||
webhookTestBaseUrl: `${mockWebhookBaseUrl}/webhook-test/`,
|
||||
currentNodeParameters: undefined,
|
||||
executionTimeoutTimestamp: undefined,
|
||||
userId: undefined,
|
||||
setExecutionStatus: expect.any(Function),
|
||||
variables: mockVariables,
|
||||
secretsHelpers: secretsHelper,
|
||||
startRunnerTask: expect.any(Function),
|
||||
logAiEvent: expect.any(Function),
|
||||
});
|
||||
});
|
||||
|
||||
it('should include userId when provided', async () => {
|
||||
const userId = 'test-user-id';
|
||||
const additionalData = await getBase(userId);
|
||||
|
||||
expect(additionalData.userId).toBe(userId);
|
||||
});
|
||||
|
||||
it('should include currentNodeParameters when provided', async () => {
|
||||
const currentNodeParameters = { param1: 'value1' };
|
||||
const additionalData = await getBase(undefined, currentNodeParameters);
|
||||
|
||||
expect(additionalData.currentNodeParameters).toBe(currentNodeParameters);
|
||||
});
|
||||
|
||||
it('should include executionTimeoutTimestamp when provided', async () => {
|
||||
const executionTimeoutTimestamp = Date.now() + 1000;
|
||||
const additionalData = await getBase(undefined, undefined, executionTimeoutTimestamp);
|
||||
|
||||
expect(additionalData.executionTimeoutTimestamp).toBe(executionTimeoutTimestamp);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
IWorkflowBase,
|
||||
IWorkflowExecutionDataProcess,
|
||||
StartNodeData,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
} from 'n8n-workflow';
|
||||
import { Workflow, type ExecutionError } from 'n8n-workflow';
|
||||
import PCancelable from 'p-cancelable';
|
||||
@@ -22,7 +23,9 @@ import type { ExecutionEntity } from '@/databases/entities/execution-entity';
|
||||
import type { User } from '@/databases/entities/user';
|
||||
import { ExecutionNotFoundError } from '@/errors/execution-not-found-error';
|
||||
import { CredentialsPermissionChecker } from '@/executions/pre-execution-checks';
|
||||
import { ManualExecutionService } from '@/manual-execution.service';
|
||||
import { Telemetry } from '@/telemetry';
|
||||
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
|
||||
import { WorkflowRunner } from '@/workflow-runner';
|
||||
import { mockInstance } from '@test/mocking';
|
||||
import { createExecution } from '@test-integration/db/executions';
|
||||
@@ -197,4 +200,61 @@ describe('run', () => {
|
||||
// ASSERT
|
||||
expect(recreateNodeExecutionStackSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('run partial execution with additional data', async () => {
|
||||
// ARRANGE
|
||||
const activeExecutions = Container.get(ActiveExecutions);
|
||||
jest.spyOn(activeExecutions, 'add').mockResolvedValue('1');
|
||||
jest.spyOn(activeExecutions, 'attachWorkflowExecution').mockReturnValueOnce();
|
||||
const permissionChecker = Container.get(CredentialsPermissionChecker);
|
||||
jest.spyOn(permissionChecker, 'check').mockResolvedValueOnce();
|
||||
jest.spyOn(WorkflowExecute.prototype, 'processRunExecutionData').mockReturnValueOnce(
|
||||
new PCancelable(() => {
|
||||
return mock<IRun>();
|
||||
}),
|
||||
);
|
||||
|
||||
jest.spyOn(Workflow.prototype, 'getNode').mockReturnValueOnce(mock<INode>());
|
||||
jest.spyOn(DirectedGraph, 'fromWorkflow').mockReturnValueOnce(new DirectedGraph());
|
||||
|
||||
const additionalData = mock<IWorkflowExecuteAdditionalData>();
|
||||
jest.spyOn(WorkflowExecuteAdditionalData, 'getBase').mockResolvedValue(additionalData);
|
||||
jest.spyOn(ManualExecutionService.prototype, 'runManually');
|
||||
jest.spyOn(core, 'recreateNodeExecutionStack').mockReturnValueOnce({
|
||||
nodeExecutionStack: mock<IExecuteData[]>(),
|
||||
waitingExecution: mock<IWaitingForExecution>(),
|
||||
waitingExecutionSource: mock<IWaitingForExecutionSource>(),
|
||||
});
|
||||
|
||||
const data = mock<IWorkflowExecutionDataProcess>({
|
||||
triggerToStartFrom: { name: 'trigger', data: mock<ITaskData>() },
|
||||
|
||||
workflowData: { nodes: [] },
|
||||
executionData: undefined,
|
||||
startNodes: [mock<StartNodeData>()],
|
||||
destinationNode: undefined,
|
||||
runData: {
|
||||
trigger: [mock<ITaskData>({ executionIndex: 7 })],
|
||||
otherNode: [mock<ITaskData>({ executionIndex: 8 }), mock<ITaskData>({ executionIndex: 9 })],
|
||||
},
|
||||
userId: 'mock-user-id',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await runner.run(data);
|
||||
|
||||
// ASSERT
|
||||
expect(WorkflowExecuteAdditionalData.getBase).toHaveBeenCalledWith(
|
||||
data.userId,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
expect(ManualExecutionService.prototype.runManually).toHaveBeenCalledWith(
|
||||
data,
|
||||
expect.any(Workflow),
|
||||
additionalData,
|
||||
'1',
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { Logger } from 'n8n-core';
|
||||
import { mockInstance } from 'n8n-core/test/utils';
|
||||
import type { IRunExecutionData, WorkflowExecuteMode } from 'n8n-workflow/src';
|
||||
import type { IPinData, ITaskData, IWorkflowExecuteAdditionalData } from 'n8n-workflow';
|
||||
import { Workflow, type IRunExecutionData, type WorkflowExecuteMode } from 'n8n-workflow';
|
||||
|
||||
import { CredentialsHelper } from '@/credentials-helper';
|
||||
import type { ExecutionRepository } from '@/databases/repositories/execution.repository';
|
||||
@@ -11,6 +12,7 @@ import type { ManualExecutionService } from '@/manual-execution.service';
|
||||
import { SecretsHelper } from '@/secrets-helpers.ee';
|
||||
import { WorkflowStatisticsService } from '@/services/workflow-statistics.service';
|
||||
import type { IExecutionResponse } from '@/types-db';
|
||||
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
|
||||
import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service';
|
||||
|
||||
import { JobProcessor } from '../job-processor';
|
||||
@@ -80,4 +82,57 @@ describe('JobProcessor', () => {
|
||||
expect(manualExecutionService.runManually).toHaveBeenCalledTimes(1);
|
||||
},
|
||||
);
|
||||
|
||||
it('should pass additional data for partial executions to run', async () => {
|
||||
const executionRepository = mock<ExecutionRepository>();
|
||||
const pinData: IPinData = { pinned: [] };
|
||||
const execution = mock<IExecutionResponse>({
|
||||
mode: 'manual',
|
||||
workflowData: { nodes: [], pinData },
|
||||
data: mock<IRunExecutionData>({
|
||||
isTestWebhook: false,
|
||||
resultData: {
|
||||
runData: {
|
||||
trigger: [mock<ITaskData>({ executionIndex: 1 })],
|
||||
node: [mock<ITaskData>({ executionIndex: 3 }), mock<ITaskData>({ executionIndex: 4 })],
|
||||
},
|
||||
pinData,
|
||||
},
|
||||
}),
|
||||
});
|
||||
executionRepository.findSingleExecution.mockResolvedValue(execution);
|
||||
|
||||
const additionalData = mock<IWorkflowExecuteAdditionalData>();
|
||||
jest.spyOn(WorkflowExecuteAdditionalData, 'getBase').mockResolvedValue(additionalData);
|
||||
|
||||
const manualExecutionService = mock<ManualExecutionService>();
|
||||
const jobProcessor = new JobProcessor(
|
||||
logger,
|
||||
mock(),
|
||||
executionRepository,
|
||||
mock(),
|
||||
mock(),
|
||||
mock(),
|
||||
manualExecutionService,
|
||||
);
|
||||
|
||||
const executionId = 'execution-id';
|
||||
await jobProcessor.processJob(mock<Job>({ data: { executionId, loadStaticData: false } }));
|
||||
|
||||
expect(WorkflowExecuteAdditionalData.getBase).toHaveBeenCalledWith(
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
);
|
||||
|
||||
expect(manualExecutionService.runManually).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
executionMode: 'manual',
|
||||
}),
|
||||
expect.any(Workflow),
|
||||
additionalData,
|
||||
executionId,
|
||||
pinData,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -235,6 +235,7 @@ export class WorkflowRunner {
|
||||
settings: workflowSettings,
|
||||
pinData,
|
||||
});
|
||||
|
||||
const additionalData = await WorkflowExecuteAdditionalData.getBase(
|
||||
data.userId,
|
||||
undefined,
|
||||
|
||||
Reference in New Issue
Block a user