refactor(core): Move execution engine code out of n8n-workflow (no-changelog) (#12147)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2024-12-12 13:54:44 +01:00
committed by GitHub
parent 73f0c4cca9
commit 5a055ed526
44 changed files with 1995 additions and 1795 deletions

View File

@@ -6,22 +6,14 @@ import type {
IBinaryKeyData,
IConnections,
IDataObject,
IExecuteData,
INode,
INodeExecuteFunctions,
INodeExecutionData,
INodeParameters,
INodeType,
INodeTypeDescription,
INodeTypes,
IRunExecutionData,
ITriggerFunctions,
ITriggerResponse,
IWorkflowExecuteAdditionalData,
NodeParameterValueType,
} from '@/Interfaces';
import * as NodeHelpers from '@/NodeHelpers';
import { Workflow, type WorkflowParameters } from '@/Workflow';
import { Workflow } from '@/Workflow';
process.env.TEST_VARIABLE_1 = 'valueEnvVariable1';
@@ -33,126 +25,6 @@ interface StubNode {
}
describe('Workflow', () => {
describe('checkIfWorkflowCanBeActivated', () => {
const disabledNode = mock<INode>({ type: 'triggerNode', disabled: true });
const unknownNode = mock<INode>({ type: 'unknownNode' });
const noTriggersNode = mock<INode>({ type: 'noTriggersNode' });
const pollNode = mock<INode>({ type: 'pollNode' });
const triggerNode = mock<INode>({ type: 'triggerNode' });
const webhookNode = mock<INode>({ type: 'webhookNode' });
const nodeTypes = mock<INodeTypes>();
nodeTypes.getByNameAndVersion.mockImplementation((type) => {
// TODO: getByNameAndVersion signature needs to be updated to allow returning undefined
if (type === 'unknownNode') return undefined as unknown as INodeType;
const partial: Partial<INodeType> = {
poll: undefined,
trigger: undefined,
webhook: undefined,
description: mock<INodeTypeDescription>({
properties: [],
}),
};
if (type === 'pollNode') partial.poll = jest.fn();
if (type === 'triggerNode') partial.trigger = jest.fn();
if (type === 'webhookNode') partial.webhook = jest.fn();
return mock(partial);
});
test.each([
['should skip disabled nodes', disabledNode, [], false],
['should skip nodes marked as ignored', triggerNode, ['triggerNode'], false],
['should skip unknown nodes', unknownNode, [], false],
['should skip nodes with no trigger method', noTriggersNode, [], false],
['should activate if poll method exists', pollNode, [], true],
['should activate if trigger method exists', triggerNode, [], true],
['should activate if webhook method exists', webhookNode, [], true],
])('%s', async (_, node, ignoredNodes, expected) => {
const params = mock<WorkflowParameters>({ nodeTypes });
params.nodes = [node];
const workflow = new Workflow(params);
expect(workflow.checkIfWorkflowCanBeActivated(ignoredNodes)).toBe(expected);
});
});
describe('checkReadyForExecution', () => {
const disabledNode = mock<INode>({ name: 'Disabled Node', disabled: true });
const startNode = mock<INode>({ name: 'Start Node' });
const unknownNode = mock<INode>({ name: 'Unknown Node', type: 'unknownNode' });
const nodeParamIssuesSpy = jest.spyOn(NodeHelpers, 'getNodeParametersIssues');
const nodeTypes = mock<INodeTypes>();
nodeTypes.getByNameAndVersion.mockImplementation((type) => {
// TODO: getByNameAndVersion signature needs to be updated to allow returning undefined
if (type === 'unknownNode') return undefined as unknown as INodeType;
return mock<INodeType>({
description: {
properties: [],
},
});
});
beforeEach(() => jest.clearAllMocks());
it('should return null if there are no nodes', () => {
const workflow = new Workflow({
nodes: [],
connections: {},
active: false,
nodeTypes,
});
const issues = workflow.checkReadyForExecution();
expect(issues).toBe(null);
expect(nodeTypes.getByNameAndVersion).not.toHaveBeenCalled();
expect(nodeParamIssuesSpy).not.toHaveBeenCalled();
});
it('should return null if there are no enabled nodes', () => {
const workflow = new Workflow({
nodes: [disabledNode],
connections: {},
active: false,
nodeTypes,
});
const issues = workflow.checkReadyForExecution({ startNode: disabledNode.name });
expect(issues).toBe(null);
expect(nodeTypes.getByNameAndVersion).toHaveBeenCalledTimes(1);
expect(nodeParamIssuesSpy).not.toHaveBeenCalled();
});
it('should return typeUnknown for unknown nodes', () => {
const workflow = new Workflow({
nodes: [unknownNode],
connections: {},
active: false,
nodeTypes,
});
const issues = workflow.checkReadyForExecution({ startNode: unknownNode.name });
expect(issues).toEqual({ [unknownNode.name]: { typeUnknown: true } });
expect(nodeTypes.getByNameAndVersion).toHaveBeenCalledTimes(2);
expect(nodeParamIssuesSpy).not.toHaveBeenCalled();
});
it('should return issues for regular nodes', () => {
const workflow = new Workflow({
nodes: [startNode],
connections: {},
active: false,
nodeTypes,
});
nodeParamIssuesSpy.mockReturnValue({ execution: false });
const issues = workflow.checkReadyForExecution({ startNode: startNode.name });
expect(issues).toEqual({ [startNode.name]: { execution: false } });
expect(nodeTypes.getByNameAndVersion).toHaveBeenCalledTimes(2);
expect(nodeParamIssuesSpy).toHaveBeenCalled();
});
});
describe('renameNodeInParameterValue', () => {
describe('for expressions', () => {
const tests = [
@@ -2023,69 +1895,6 @@ describe('Workflow', () => {
});
});
describe('runNode', () => {
const nodeTypes = mock<INodeTypes>();
const triggerNode = mock<INode>();
const triggerResponse = mock<ITriggerResponse>({
closeFunction: jest.fn(),
// This node should never trigger, or return
manualTriggerFunction: async () => await new Promise(() => {}),
});
const triggerNodeType = mock<INodeType>({
description: {
properties: [],
},
execute: undefined,
poll: undefined,
webhook: undefined,
async trigger(this: ITriggerFunctions) {
return triggerResponse;
},
});
nodeTypes.getByNameAndVersion.mockReturnValue(triggerNodeType);
const workflow = new Workflow({
nodeTypes,
nodes: [triggerNode],
connections: {},
active: false,
});
const executionData = mock<IExecuteData>();
const runExecutionData = mock<IRunExecutionData>();
const additionalData = mock<IWorkflowExecuteAdditionalData>();
const nodeExecuteFunctions = mock<INodeExecuteFunctions>();
const triggerFunctions = mock<ITriggerFunctions>();
nodeExecuteFunctions.getExecuteTriggerFunctions.mockReturnValue(triggerFunctions);
const abortController = new AbortController();
test('should call closeFunction when manual trigger is aborted', async () => {
const runPromise = workflow.runNode(
executionData,
runExecutionData,
0,
additionalData,
nodeExecuteFunctions,
'manual',
abortController.signal,
);
// Yield back to the event-loop to let async parts of `runNode` execute
await new Promise((resolve) => setImmediate(resolve));
let isSettled = false;
void runPromise.then(() => {
isSettled = true;
});
expect(isSettled).toBe(false);
expect(abortController.signal.aborted).toBe(false);
expect(triggerResponse.closeFunction).not.toHaveBeenCalled();
abortController.abort();
expect(triggerResponse.closeFunction).toHaveBeenCalled();
});
});
describe('__getConnectionsByDestination', () => {
it('should return empty object when there are no connections', () => {
const workflow = new Workflow({