refactor(core): Move ExecutionLifecycleHooks to core (#13042)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2025-02-07 18:16:37 +01:00
committed by GitHub
parent cae98e733d
commit d41ca832dc
24 changed files with 911 additions and 886 deletions

View File

@@ -1,7 +1,13 @@
import { stringify } from 'flatted';
import { mock } from 'jest-mock-extended';
import { BinaryDataService, ErrorReporter, InstanceSettings, Logger } from 'n8n-core';
import { ExpressionError, WorkflowHooks } from 'n8n-workflow';
import {
BinaryDataService,
ErrorReporter,
InstanceSettings,
Logger,
ExecutionLifecycleHooks,
} from 'n8n-core';
import { ExpressionError } from 'n8n-workflow';
import type {
IRunExecutionData,
ITaskData,
@@ -10,6 +16,7 @@ import type {
IRun,
INode,
IWorkflowBase,
WorkflowExecuteMode,
} from 'n8n-workflow';
import config from '@/config';
@@ -25,10 +32,10 @@ import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.serv
import { mockInstance } from '@test/mocking';
import {
getWorkflowHooksIntegrated,
getWorkflowHooksMain,
getWorkflowHooksWorkerExecuter,
getWorkflowHooksWorkerMain,
getLifecycleHooksForSubExecutions,
getLifecycleHooksForRegularMain,
getLifecycleHooksForScalingWorker,
getLifecycleHooksForScalingMain,
} from '../execution-lifecycle-hooks';
describe('Execution Lifecycle Hooks', () => {
@@ -79,14 +86,13 @@ describe('Execution Lifecycle Hooks', () => {
waitTill: new Date(),
});
const expressionError = new ExpressionError('Error');
const executionMode = 'manual';
const pushRef = 'test-push-ref';
const retryOf = 'test-retry-of';
const now = new Date('2025-01-13T18:25:50.267Z');
jest.useFakeTimers({ now });
let hooks: WorkflowHooks;
let lifecycleHooks: ExecutionLifecycleHooks;
beforeEach(() => {
jest.clearAllMocks();
@@ -107,7 +113,7 @@ describe('Execution Lifecycle Hooks', () => {
const workflowEventTests = () => {
describe('workflowExecuteBefore', () => {
it('should emit workflow-pre-execute events', async () => {
await hooks.executeHookFunctions('workflowExecuteBefore', [workflow, runExecutionData]);
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
expect(eventService.emit).toHaveBeenCalledWith('workflow-pre-execute', {
executionId,
@@ -118,7 +124,7 @@ describe('Execution Lifecycle Hooks', () => {
describe('workflowExecuteAfter', () => {
it('should emit workflow-post-execute events', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(eventService.emit).toHaveBeenCalledWith('workflow-post-execute', {
executionId,
@@ -128,7 +134,7 @@ describe('Execution Lifecycle Hooks', () => {
});
it('should not emit workflow-post-execute events for waiting executions', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [waitingRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [waitingRun, {}]);
expect(eventService.emit).not.toHaveBeenCalledWith('workflow-post-execute');
});
@@ -138,7 +144,7 @@ describe('Execution Lifecycle Hooks', () => {
const nodeEventsTests = () => {
describe('nodeExecuteBefore', () => {
it('should emit node-pre-execute event', async () => {
await hooks.executeHookFunctions('nodeExecuteBefore', [nodeName]);
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName]);
expect(eventService.emit).toHaveBeenCalledWith('node-pre-execute', {
executionId,
@@ -150,11 +156,7 @@ describe('Execution Lifecycle Hooks', () => {
describe('nodeExecuteAfter', () => {
it('should emit node-post-execute event', async () => {
await hooks.executeHookFunctions('nodeExecuteAfter', [
nodeName,
taskData,
runExecutionData,
]);
await lifecycleHooks.runHook('nodeExecuteAfter', [nodeName, taskData, runExecutionData]);
expect(eventService.emit).toHaveBeenCalledWith('node-post-execute', {
executionId,
@@ -168,18 +170,15 @@ describe('Execution Lifecycle Hooks', () => {
const externalHooksTests = () => {
describe('workflowExecuteBefore', () => {
it('should run workflow.preExecute hook', async () => {
await hooks.executeHookFunctions('workflowExecuteBefore', [workflow, runExecutionData]);
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
expect(externalHooks.run).toHaveBeenCalledWith('workflow.preExecute', [
workflow,
executionMode,
]);
expect(externalHooks.run).toHaveBeenCalledWith('workflow.preExecute', [workflow, 'manual']);
});
});
describe('workflowExecuteAfter', () => {
it('should run workflow.postExecute hook', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(externalHooks.run).toHaveBeenCalledWith('workflow.postExecute', [
successfulRun,
@@ -193,7 +192,7 @@ describe('Execution Lifecycle Hooks', () => {
const statisticsTests = () => {
describe('statistics events', () => {
it('workflowExecuteAfter should emit workflowExecutionCompleted statistics event', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(workflowStatisticsService.emit).toHaveBeenCalledWith('workflowExecutionCompleted', {
workflowData,
@@ -202,7 +201,7 @@ describe('Execution Lifecycle Hooks', () => {
});
it('nodeFetchedData should handle nodeFetchedData statistics event', async () => {
await hooks.executeHookFunctions('nodeFetchedData', [workflowId, node]);
await lifecycleHooks.runHook('nodeFetchedData', [workflowId, node]);
expect(workflowStatisticsService.emit).toHaveBeenCalledWith('nodeFetchedData', {
workflowId,
@@ -212,12 +211,15 @@ describe('Execution Lifecycle Hooks', () => {
});
};
describe('getWorkflowHooksMain', () => {
const createHooks = () =>
getWorkflowHooksMain({ executionMode, workflowData, pushRef, retryOf }, executionId);
describe('getLifecycleHooksForRegularMain', () => {
const createHooks = (executionMode: WorkflowExecuteMode = 'manual') =>
getLifecycleHooksForRegularMain(
{ executionMode, workflowData, pushRef, retryOf },
executionId,
);
beforeEach(() => {
hooks = createHooks();
lifecycleHooks = createHooks();
});
workflowEventTests();
@@ -226,23 +228,23 @@ describe('Execution Lifecycle Hooks', () => {
statisticsTests();
it('should setup the correct set of hooks', () => {
expect(hooks).toBeInstanceOf(WorkflowHooks);
expect(hooks.mode).toBe('manual');
expect(hooks.executionId).toBe(executionId);
expect(hooks.workflowData).toEqual(workflowData);
expect(lifecycleHooks).toBeInstanceOf(ExecutionLifecycleHooks);
expect(lifecycleHooks.mode).toBe('manual');
expect(lifecycleHooks.executionId).toBe(executionId);
expect(lifecycleHooks.workflowData).toEqual(workflowData);
const { hookFunctions } = hooks;
expect(hookFunctions.nodeExecuteBefore).toHaveLength(2);
expect(hookFunctions.nodeExecuteAfter).toHaveLength(2);
expect(hookFunctions.workflowExecuteBefore).toHaveLength(3);
expect(hookFunctions.workflowExecuteAfter).toHaveLength(5);
expect(hookFunctions.nodeFetchedData).toHaveLength(1);
expect(hookFunctions.sendResponse).toHaveLength(0);
const { handlers } = lifecycleHooks;
expect(handlers.nodeExecuteBefore).toHaveLength(2);
expect(handlers.nodeExecuteAfter).toHaveLength(2);
expect(handlers.workflowExecuteBefore).toHaveLength(3);
expect(handlers.workflowExecuteAfter).toHaveLength(5);
expect(handlers.nodeFetchedData).toHaveLength(1);
expect(handlers.sendResponse).toHaveLength(0);
});
describe('nodeExecuteBefore', () => {
it('should send nodeExecuteBefore push event', async () => {
await hooks.executeHookFunctions('nodeExecuteBefore', [nodeName]);
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName]);
expect(push.send).toHaveBeenCalledWith(
{ type: 'nodeExecuteBefore', data: { executionId, nodeName } },
@@ -253,11 +255,7 @@ describe('Execution Lifecycle Hooks', () => {
describe('nodeExecuteAfter', () => {
it('should send nodeExecuteAfter push event', async () => {
await hooks.executeHookFunctions('nodeExecuteAfter', [
nodeName,
taskData,
runExecutionData,
]);
await lifecycleHooks.runHook('nodeExecuteAfter', [nodeName, taskData, runExecutionData]);
expect(push.send).toHaveBeenCalledWith(
{ type: 'nodeExecuteAfter', data: { executionId, nodeName, data: taskData } },
@@ -267,15 +265,11 @@ describe('Execution Lifecycle Hooks', () => {
it('should save execution progress when enabled', async () => {
workflowData.settings = { saveExecutionProgress: true };
hooks = createHooks();
lifecycleHooks = createHooks();
expect(hooks.hookFunctions.nodeExecuteAfter).toHaveLength(3);
expect(lifecycleHooks.handlers.nodeExecuteAfter).toHaveLength(3);
await hooks.executeHookFunctions('nodeExecuteAfter', [
nodeName,
taskData,
runExecutionData,
]);
await lifecycleHooks.runHook('nodeExecuteAfter', [nodeName, taskData, runExecutionData]);
expect(executionRepository.findSingleExecution).toHaveBeenCalledWith(executionId, {
includeData: true,
@@ -285,15 +279,11 @@ describe('Execution Lifecycle Hooks', () => {
it('should not save execution progress when disabled', async () => {
workflowData.settings = { saveExecutionProgress: false };
hooks = createHooks();
lifecycleHooks = createHooks();
expect(hooks.hookFunctions.nodeExecuteAfter).toHaveLength(2);
expect(lifecycleHooks.handlers.nodeExecuteAfter).toHaveLength(2);
await hooks.executeHookFunctions('nodeExecuteAfter', [
nodeName,
taskData,
runExecutionData,
]);
await lifecycleHooks.runHook('nodeExecuteAfter', [nodeName, taskData, runExecutionData]);
expect(executionRepository.findSingleExecution).not.toHaveBeenCalled();
});
@@ -301,14 +291,14 @@ describe('Execution Lifecycle Hooks', () => {
describe('workflowExecuteBefore', () => {
it('should send executionStarted push event', async () => {
await hooks.executeHookFunctions('workflowExecuteBefore', [workflow, runExecutionData]);
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
expect(push.send).toHaveBeenCalledWith(
{
type: 'executionStarted',
data: {
executionId,
mode: executionMode,
mode: 'manual',
retryOf,
workflowId: 'test-workflow-id',
workflowName: 'Test Workflow',
@@ -321,18 +311,15 @@ describe('Execution Lifecycle Hooks', () => {
});
it('should run workflow.preExecute external hook', async () => {
await hooks.executeHookFunctions('workflowExecuteBefore', [workflow, runExecutionData]);
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
expect(externalHooks.run).toHaveBeenCalledWith('workflow.preExecute', [
workflow,
executionMode,
]);
expect(externalHooks.run).toHaveBeenCalledWith('workflow.preExecute', [workflow, 'manual']);
});
});
describe('workflowExecuteAfter', () => {
it('should send executionFinished push event', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(push.send).toHaveBeenCalledWith(
{
type: 'executionFinished',
@@ -348,7 +335,7 @@ describe('Execution Lifecycle Hooks', () => {
});
it('should send executionWaiting push event', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [waitingRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [waitingRun, {}]);
expect(push.send).toHaveBeenCalledWith(
{
@@ -361,17 +348,15 @@ describe('Execution Lifecycle Hooks', () => {
describe('saving static data', () => {
it('should skip saving static data for manual executions', async () => {
hooks.mode = 'manual';
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, staticData]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, staticData]);
expect(workflowStaticDataService.saveStaticDataById).not.toHaveBeenCalled();
});
it('should save static data for prod executions', async () => {
hooks.mode = 'trigger';
lifecycleHooks = createHooks('trigger');
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, staticData]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, staticData]);
expect(workflowStaticDataService.saveStaticDataById).toHaveBeenCalledWith(
workflowId,
@@ -380,11 +365,12 @@ describe('Execution Lifecycle Hooks', () => {
});
it('should handle static data saving errors', async () => {
hooks.mode = 'trigger';
lifecycleHooks = createHooks('trigger');
const error = new Error('Static data save failed');
workflowStaticDataService.saveStaticDataById.mockRejectedValueOnce(error);
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, staticData]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, staticData]);
expect(errorReporter.error).toHaveBeenCalledWith(error);
});
@@ -392,7 +378,7 @@ describe('Execution Lifecycle Hooks', () => {
describe('saving execution data', () => {
it('should update execution with proper data', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(executionRepository.updateExistingExecution).toHaveBeenCalledWith(
executionId,
@@ -406,31 +392,31 @@ describe('Execution Lifecycle Hooks', () => {
it('should not delete unfinished executions', async () => {
const unfinishedRun = mock<IRun>({ finished: false, status: 'running' });
await hooks.executeHookFunctions('workflowExecuteAfter', [unfinishedRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [unfinishedRun, {}]);
expect(executionRepository.hardDelete).not.toHaveBeenCalled();
});
it('should not delete waiting executions', async () => {
await hooks.executeHookFunctions('workflowExecuteAfter', [waitingRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [waitingRun, {}]);
expect(executionRepository.hardDelete).not.toHaveBeenCalled();
});
it('should soft delete manual executions when manual saving is disabled', async () => {
hooks.workflowData.settings = { saveManualExecutions: false };
hooks = createHooks();
lifecycleHooks.workflowData.settings = { saveManualExecutions: false };
lifecycleHooks = createHooks();
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(executionRepository.softDelete).toHaveBeenCalledWith(executionId);
});
it('should not soft delete manual executions with waitTill', async () => {
hooks.workflowData.settings = { saveManualExecutions: false };
hooks = createHooks();
lifecycleHooks.workflowData.settings = { saveManualExecutions: false };
lifecycleHooks = createHooks();
await hooks.executeHookFunctions('workflowExecuteAfter', [waitingRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [waitingRun, {}]);
expect(executionRepository.softDelete).not.toHaveBeenCalled();
});
@@ -438,15 +424,14 @@ describe('Execution Lifecycle Hooks', () => {
describe('error workflow', () => {
it('should not execute error workflow for manual executions', async () => {
hooks.mode = 'manual';
await hooks.executeHookFunctions('workflowExecuteAfter', [failedRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [failedRun, {}]);
expect(workflowExecutionService.executeErrorWorkflow).not.toHaveBeenCalled();
});
it('should execute error workflow for failed non-manual executions', async () => {
hooks.mode = 'trigger';
lifecycleHooks = createHooks('trigger');
const errorWorkflow = 'error-workflow-id';
workflowData.settings = { errorWorkflow };
const project = mock<Project>();
@@ -454,7 +439,7 @@ describe('Execution Lifecycle Hooks', () => {
.calledWith(workflowId)
.mockResolvedValue(project);
await hooks.executeHookFunctions('workflowExecuteAfter', [failedRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [failedRun, {}]);
expect(workflowExecutionService.executeErrorWorkflow).toHaveBeenCalledWith(
errorWorkflow,
@@ -479,7 +464,8 @@ describe('Execution Lifecycle Hooks', () => {
it('should restore binary data IDs after workflow execution for webhooks', async () => {
config.set('binaryDataManager.mode', 'filesystem');
hooks.mode = 'webhook';
lifecycleHooks = createHooks('webhook');
(successfulRun.data.resultData.runData = {
[nodeName]: [
{
@@ -505,7 +491,7 @@ describe('Execution Lifecycle Hooks', () => {
},
],
}),
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(binaryDataService.rename).toHaveBeenCalledWith(
'workflows/test-workflow-id/executions/temp/binary_data/123',
@@ -516,33 +502,32 @@ describe('Execution Lifecycle Hooks', () => {
describe("when pushRef isn't set", () => {
beforeEach(() => {
hooks = getWorkflowHooksMain({ executionMode, workflowData, retryOf }, executionId);
lifecycleHooks = getLifecycleHooksForRegularMain(
{ executionMode: 'manual', workflowData, retryOf },
executionId,
);
});
it('should not setup any push hooks', async () => {
const { hookFunctions } = hooks;
expect(hookFunctions.nodeExecuteBefore).toHaveLength(1);
expect(hookFunctions.nodeExecuteAfter).toHaveLength(1);
expect(hookFunctions.workflowExecuteBefore).toHaveLength(2);
expect(hookFunctions.workflowExecuteAfter).toHaveLength(4);
const { handlers } = lifecycleHooks;
expect(handlers.nodeExecuteBefore).toHaveLength(1);
expect(handlers.nodeExecuteAfter).toHaveLength(1);
expect(handlers.workflowExecuteBefore).toHaveLength(2);
expect(handlers.workflowExecuteAfter).toHaveLength(4);
await hooks.executeHookFunctions('nodeExecuteBefore', [nodeName]);
await hooks.executeHookFunctions('nodeExecuteAfter', [
nodeName,
taskData,
runExecutionData,
]);
await hooks.executeHookFunctions('workflowExecuteBefore', [workflow, runExecutionData]);
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('nodeExecuteBefore', [nodeName]);
await lifecycleHooks.runHook('nodeExecuteAfter', [nodeName, taskData, runExecutionData]);
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(push.send).not.toHaveBeenCalled();
});
});
});
describe('getWorkflowHooksWorkerMain', () => {
describe('getLifecycleHooksForScalingMain', () => {
beforeEach(() => {
hooks = getWorkflowHooksWorkerMain(executionMode, executionId, workflowData, {
lifecycleHooks = getLifecycleHooksForScalingMain('manual', executionId, workflowData, {
pushRef,
retryOf,
});
@@ -552,28 +537,25 @@ describe('Execution Lifecycle Hooks', () => {
externalHooksTests();
it('should setup the correct set of hooks', () => {
expect(hooks).toBeInstanceOf(WorkflowHooks);
expect(hooks.mode).toBe('manual');
expect(hooks.executionId).toBe(executionId);
expect(hooks.workflowData).toEqual(workflowData);
expect(lifecycleHooks).toBeInstanceOf(ExecutionLifecycleHooks);
expect(lifecycleHooks.mode).toBe('manual');
expect(lifecycleHooks.executionId).toBe(executionId);
expect(lifecycleHooks.workflowData).toEqual(workflowData);
const { hookFunctions } = hooks;
expect(hookFunctions.nodeExecuteBefore).toHaveLength(0);
expect(hookFunctions.nodeExecuteAfter).toHaveLength(0);
expect(hookFunctions.workflowExecuteBefore).toHaveLength(2);
expect(hookFunctions.workflowExecuteAfter).toHaveLength(4);
expect(hookFunctions.nodeFetchedData).toHaveLength(0);
expect(hookFunctions.sendResponse).toHaveLength(0);
const { handlers } = lifecycleHooks;
expect(handlers.nodeExecuteBefore).toHaveLength(0);
expect(handlers.nodeExecuteAfter).toHaveLength(0);
expect(handlers.workflowExecuteBefore).toHaveLength(2);
expect(handlers.workflowExecuteAfter).toHaveLength(4);
expect(handlers.nodeFetchedData).toHaveLength(0);
expect(handlers.sendResponse).toHaveLength(0);
});
describe('workflowExecuteBefore', () => {
it('should run the workflow.preExecute external hook', async () => {
await hooks.executeHookFunctions('workflowExecuteBefore', [workflow, runExecutionData]);
await lifecycleHooks.runHook('workflowExecuteBefore', [workflow, runExecutionData]);
expect(externalHooks.run).toHaveBeenCalledWith('workflow.preExecute', [
workflow,
executionMode,
]);
expect(externalHooks.run).toHaveBeenCalledWith('workflow.preExecute', [workflow, 'manual']);
});
});
@@ -583,12 +565,17 @@ describe('Execution Lifecycle Hooks', () => {
saveDataSuccessExecution: 'none',
saveDataErrorExecution: 'all',
};
const hooks = getWorkflowHooksWorkerMain('webhook', executionId, workflowData, {
pushRef,
retryOf,
});
const lifecycleHooks = getLifecycleHooksForScalingMain(
'webhook',
executionId,
workflowData,
{
pushRef,
retryOf,
},
);
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, {}]);
expect(executionRepository.hardDelete).toHaveBeenCalledWith({
workflowId,
@@ -601,12 +588,17 @@ describe('Execution Lifecycle Hooks', () => {
saveDataSuccessExecution: 'all',
saveDataErrorExecution: 'none',
};
const hooks = getWorkflowHooksWorkerMain('webhook', executionId, workflowData, {
pushRef,
retryOf,
});
const lifecycleHooks = getLifecycleHooksForScalingMain(
'webhook',
executionId,
workflowData,
{
pushRef,
retryOf,
},
);
await hooks.executeHookFunctions('workflowExecuteAfter', [failedRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [failedRun, {}]);
expect(executionRepository.hardDelete).toHaveBeenCalledWith({
workflowId,
@@ -616,12 +608,15 @@ describe('Execution Lifecycle Hooks', () => {
});
});
describe('getWorkflowHooksWorkerExecuter', () => {
beforeEach(() => {
hooks = getWorkflowHooksWorkerExecuter(executionMode, executionId, workflowData, {
describe('getLifecycleHooksForScalingWorker', () => {
const createHooks = (executionMode: WorkflowExecuteMode = 'manual') =>
getLifecycleHooksForScalingWorker(executionMode, executionId, workflowData, {
pushRef,
retryOf,
});
beforeEach(() => {
lifecycleHooks = createHooks();
});
nodeEventsTests();
@@ -629,33 +624,31 @@ describe('Execution Lifecycle Hooks', () => {
statisticsTests();
it('should setup the correct set of hooks', () => {
expect(hooks).toBeInstanceOf(WorkflowHooks);
expect(hooks.mode).toBe('manual');
expect(hooks.executionId).toBe(executionId);
expect(hooks.workflowData).toEqual(workflowData);
expect(lifecycleHooks).toBeInstanceOf(ExecutionLifecycleHooks);
expect(lifecycleHooks.mode).toBe('manual');
expect(lifecycleHooks.executionId).toBe(executionId);
expect(lifecycleHooks.workflowData).toEqual(workflowData);
const { hookFunctions } = hooks;
expect(hookFunctions.nodeExecuteBefore).toHaveLength(2);
expect(hookFunctions.nodeExecuteAfter).toHaveLength(2);
expect(hookFunctions.workflowExecuteBefore).toHaveLength(2);
expect(hookFunctions.workflowExecuteAfter).toHaveLength(4);
expect(hookFunctions.nodeFetchedData).toHaveLength(1);
expect(hookFunctions.sendResponse).toHaveLength(0);
const { handlers } = lifecycleHooks;
expect(handlers.nodeExecuteBefore).toHaveLength(2);
expect(handlers.nodeExecuteAfter).toHaveLength(2);
expect(handlers.workflowExecuteBefore).toHaveLength(2);
expect(handlers.workflowExecuteAfter).toHaveLength(4);
expect(handlers.nodeFetchedData).toHaveLength(1);
expect(handlers.sendResponse).toHaveLength(0);
});
describe('saving static data', () => {
it('should skip saving static data for manual executions', async () => {
hooks.mode = 'manual';
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, staticData]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, staticData]);
expect(workflowStaticDataService.saveStaticDataById).not.toHaveBeenCalled();
});
it('should save static data for prod executions', async () => {
hooks.mode = 'trigger';
lifecycleHooks = createHooks('trigger');
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, staticData]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, staticData]);
expect(workflowStaticDataService.saveStaticDataById).toHaveBeenCalledWith(
workflowId,
@@ -664,11 +657,11 @@ describe('Execution Lifecycle Hooks', () => {
});
it('should handle static data saving errors', async () => {
hooks.mode = 'trigger';
lifecycleHooks = createHooks('trigger');
const error = new Error('Static data save failed');
workflowStaticDataService.saveStaticDataById.mockRejectedValueOnce(error);
await hooks.executeHookFunctions('workflowExecuteAfter', [successfulRun, staticData]);
await lifecycleHooks.runHook('workflowExecuteAfter', [successfulRun, staticData]);
expect(errorReporter.error).toHaveBeenCalledWith(error);
});
@@ -676,21 +669,19 @@ describe('Execution Lifecycle Hooks', () => {
describe('error workflow', () => {
it('should not execute error workflow for manual executions', async () => {
hooks.mode = 'manual';
await hooks.executeHookFunctions('workflowExecuteAfter', [failedRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [failedRun, {}]);
expect(workflowExecutionService.executeErrorWorkflow).not.toHaveBeenCalled();
});
it('should execute error workflow for failed non-manual executions', async () => {
hooks.mode = 'trigger';
lifecycleHooks = createHooks('trigger');
const errorWorkflow = 'error-workflow-id';
workflowData.settings = { errorWorkflow };
const project = mock<Project>();
ownershipService.getWorkflowProjectCached.calledWith(workflowId).mockResolvedValue(project);
await hooks.executeHookFunctions('workflowExecuteAfter', [failedRun, {}]);
await lifecycleHooks.runHook('workflowExecuteAfter', [failedRun, {}]);
expect(workflowExecutionService.executeErrorWorkflow).toHaveBeenCalledWith(
errorWorkflow,
@@ -714,9 +705,14 @@ describe('Execution Lifecycle Hooks', () => {
});
});
describe('getWorkflowHooksIntegrated', () => {
describe('getLifecycleHooksForSubExecutions', () => {
beforeEach(() => {
hooks = getWorkflowHooksIntegrated(executionMode, executionId, workflowData, undefined);
lifecycleHooks = getLifecycleHooksForSubExecutions(
'manual',
executionId,
workflowData,
undefined,
);
});
workflowEventTests();
@@ -725,18 +721,18 @@ describe('Execution Lifecycle Hooks', () => {
statisticsTests();
it('should setup the correct set of hooks', () => {
expect(hooks).toBeInstanceOf(WorkflowHooks);
expect(hooks.mode).toBe('manual');
expect(hooks.executionId).toBe(executionId);
expect(hooks.workflowData).toEqual(workflowData);
expect(lifecycleHooks).toBeInstanceOf(ExecutionLifecycleHooks);
expect(lifecycleHooks.mode).toBe('manual');
expect(lifecycleHooks.executionId).toBe(executionId);
expect(lifecycleHooks.workflowData).toEqual(workflowData);
const { hookFunctions } = hooks;
expect(hookFunctions.nodeExecuteBefore).toHaveLength(1);
expect(hookFunctions.nodeExecuteAfter).toHaveLength(1);
expect(hookFunctions.workflowExecuteBefore).toHaveLength(2);
expect(hookFunctions.workflowExecuteAfter).toHaveLength(4);
expect(hookFunctions.nodeFetchedData).toHaveLength(1);
expect(hookFunctions.sendResponse).toHaveLength(0);
const { handlers } = lifecycleHooks;
expect(handlers.nodeExecuteBefore).toHaveLength(1);
expect(handlers.nodeExecuteAfter).toHaveLength(1);
expect(handlers.workflowExecuteBefore).toHaveLength(2);
expect(handlers.workflowExecuteAfter).toHaveLength(4);
expect(handlers.nodeFetchedData).toHaveLength(1);
expect(handlers.sendResponse).toHaveLength(0);
});
});
});