import { mockLogger } from '@n8n/backend-test-utils'; import type { WorkflowEntity } from '@n8n/db'; import type { WorkflowRepository } from '@n8n/db'; import { mock } from 'jest-mock-extended'; import type { InstanceSettings } from 'n8n-core'; import type { WorkflowParameters, INode, INodeType, INodeTypeDescription, WorkflowActivateMode, } from 'n8n-workflow'; import { Workflow } from 'n8n-workflow'; import { ActiveWorkflowManager } from '@/active-workflow-manager'; import type { NodeTypes } from '@/node-types'; describe('ActiveWorkflowManager', () => { let activeWorkflowManager: ActiveWorkflowManager; const instanceSettings = mock({ isMultiMain: false }); const nodeTypes = mock(); const workflowRepository = mock(); beforeEach(() => { jest.clearAllMocks(); activeWorkflowManager = new ActiveWorkflowManager( mockLogger(), mock(), mock(), mock(), mock(), nodeTypes, mock(), workflowRepository, mock(), mock(), mock(), mock(), mock(), instanceSettings, mock(), mock(), mock(), ); }); describe('checkIfWorkflowCanBeActivated', () => { const disabledNode = mock({ type: 'triggerNode', disabled: true }); const unknownNode = mock({ type: 'unknownNode' }); const noTriggersNode = mock({ type: 'noTriggersNode' }); const pollNode = mock({ type: 'pollNode' }); const triggerNode = mock({ type: 'triggerNode' }); const webhookNode = mock({ type: 'webhookNode' }); 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 = { poll: undefined, trigger: undefined, webhook: undefined, description: mock({ 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 workflow = new Workflow(mock({ nodeTypes, nodes: [node] })); const canBeActivated = activeWorkflowManager.checkIfWorkflowCanBeActivated( workflow, ignoredNodes, ); expect(canBeActivated).toBe(expected); }); }); describe('shouldAddWebhooks', () => { describe('if leader', () => { beforeAll(() => { Object.assign(instanceSettings, { isLeader: true, isFollower: false }); }); test('should return `true` for `init`', () => { // ensure webhooks are populated on init: https://github.com/n8n-io/n8n/pull/8830 const result = activeWorkflowManager.shouldAddWebhooks('init'); expect(result).toBe(true); }); test('should return `false` for `leadershipChange`', () => { const result = activeWorkflowManager.shouldAddWebhooks('leadershipChange'); expect(result).toBe(false); }); test('should return `true` for `update` or `activate`', () => { const modes = ['update', 'activate'] as WorkflowActivateMode[]; for (const mode of modes) { const result = activeWorkflowManager.shouldAddWebhooks(mode); expect(result).toBe(true); } }); }); describe('if follower', () => { beforeAll(() => { Object.assign(instanceSettings, { isLeader: false, isFollower: true }); }); test('should return `false` for `update` or `activate`', () => { const modes = ['update', 'activate'] as WorkflowActivateMode[]; for (const mode of modes) { const result = activeWorkflowManager.shouldAddWebhooks(mode); expect(result).toBe(false); } }); }); describe('add', () => { test.each<[WorkflowActivateMode]>([['init'], ['leadershipChange']])( 'should skip inactive workflow in `%s` activation mode', async (mode) => { const checkSpy = jest.spyOn(activeWorkflowManager, 'checkIfWorkflowCanBeActivated'); const addWebhooksSpy = jest.spyOn(activeWorkflowManager, 'addWebhooks'); const addTriggersAndPollersSpy = jest.spyOn( activeWorkflowManager, 'addTriggersAndPollers', ); workflowRepository.findById.mockResolvedValue(mock({ active: false })); const added = await activeWorkflowManager.add('some-id', mode); expect(checkSpy).not.toHaveBeenCalled(); expect(addWebhooksSpy).not.toHaveBeenCalled(); expect(addTriggersAndPollersSpy).not.toHaveBeenCalled(); expect(added).toEqual({ triggersAndPollers: false, webhooks: false }); }, ); }); }); });