-
Notifications
You must be signed in to change notification settings - Fork 8.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: Add tests for ActiveWorkflowRunner class (#5278)
- Loading branch information
Showing
4 changed files
with
357 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,265 @@ | ||
import { v4 as uuid } from 'uuid'; | ||
import { mocked } from 'jest-mock'; | ||
|
||
import { ICredentialTypes, LoggerProxy, NodeOperationError, Workflow } from 'n8n-workflow'; | ||
|
||
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner'; | ||
import * as Db from '@/Db'; | ||
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity'; | ||
import { SharedWorkflow } from '@/databases/entities/SharedWorkflow'; | ||
import { Role } from '@/databases/entities/Role'; | ||
import { User } from '@/databases/entities/User'; | ||
import { getLogger } from '@/Logger'; | ||
import { NodeTypes } from '@/NodeTypes'; | ||
import { CredentialTypes } from '@/CredentialTypes'; | ||
import { randomEmail, randomName } from '../integration/shared/random'; | ||
import * as Helpers from './Helpers'; | ||
import { WorkflowExecuteAdditionalData } from '@/index'; | ||
import { WorkflowRunner } from '@/WorkflowRunner'; | ||
|
||
/** | ||
* TODO: | ||
* - test workflow webhooks activation (that trigger `executeWebhook`and other webhook methods) | ||
* - test activation error catching and getters such as `getActivationError` (requires building a workflow that fails to activate) | ||
* - test queued workflow activation functions (might need to create a non-working workflow to test this) | ||
*/ | ||
|
||
let databaseActiveWorkflowsCount = 0; | ||
let databaseActiveWorkflowsList: WorkflowEntity[] = []; | ||
|
||
const generateWorkflows = (count: number): WorkflowEntity[] => { | ||
const workflows: WorkflowEntity[] = []; | ||
const ownerRole = new Role(); | ||
ownerRole.scope = 'workflow'; | ||
ownerRole.name = 'owner'; | ||
ownerRole.id = '1'; | ||
|
||
const owner = new User(); | ||
owner.id = uuid(); | ||
owner.firstName = randomName(); | ||
owner.lastName = randomName(); | ||
owner.email = randomEmail(); | ||
|
||
for (let i = 0; i < count; i++) { | ||
const workflow = new WorkflowEntity(); | ||
Object.assign(workflow, { | ||
id: i + 1, | ||
name: randomName(), | ||
active: true, | ||
createdAt: new Date(), | ||
updatedAt: new Date(), | ||
nodes: [ | ||
{ | ||
parameters: { | ||
rule: { | ||
interval: [{}], | ||
}, | ||
}, | ||
id: uuid(), | ||
name: 'Schedule Trigger', | ||
type: 'n8n-nodes-base.scheduleTrigger', | ||
typeVersion: 1, | ||
position: [900, 460], | ||
}, | ||
], | ||
connections: {}, | ||
tags: [], | ||
}); | ||
const sharedWorkflow = new SharedWorkflow(); | ||
sharedWorkflow.workflowId = workflow.id; | ||
sharedWorkflow.role = ownerRole; | ||
sharedWorkflow.user = owner; | ||
|
||
workflow.shared = [sharedWorkflow]; | ||
|
||
workflows.push(workflow); | ||
} | ||
databaseActiveWorkflowsList = workflows; | ||
return workflows; | ||
}; | ||
|
||
const MOCK_NODE_TYPES_DATA = Helpers.mockNodeTypesData(['scheduleTrigger'], { | ||
addTrigger: true, | ||
}); | ||
|
||
jest.mock('@/Db', () => { | ||
return { | ||
collections: { | ||
Workflow: { | ||
find: jest.fn(async () => Promise.resolve(generateWorkflows(databaseActiveWorkflowsCount))), | ||
findOne: jest.fn(async (searchParams) => { | ||
const foundWorkflow = databaseActiveWorkflowsList.find( | ||
(workflow) => workflow.id.toString() === searchParams.where.id.toString(), | ||
); | ||
return Promise.resolve(foundWorkflow); | ||
}), | ||
update: jest.fn(), | ||
createQueryBuilder: jest.fn(() => { | ||
const fakeQueryBuilder = { | ||
update: () => fakeQueryBuilder, | ||
set: () => fakeQueryBuilder, | ||
where: () => fakeQueryBuilder, | ||
execute: () => Promise.resolve(), | ||
}; | ||
return fakeQueryBuilder; | ||
}), | ||
}, | ||
Webhook: { | ||
clear: jest.fn(), | ||
delete: jest.fn(), | ||
}, | ||
}, | ||
}; | ||
}); | ||
|
||
const mockExternalHooksRunFunction = jest.fn(); | ||
|
||
jest.mock('@/ExternalHooks', () => { | ||
return { | ||
ExternalHooks: () => { | ||
return { | ||
run: () => mockExternalHooksRunFunction(), | ||
init: () => Promise.resolve(), | ||
}; | ||
}, | ||
}; | ||
}); | ||
|
||
const workflowCheckIfCanBeActivated = jest.fn(() => true); | ||
|
||
jest | ||
.spyOn(Workflow.prototype, 'checkIfWorkflowCanBeActivated') | ||
.mockImplementation(workflowCheckIfCanBeActivated); | ||
|
||
const removeFunction = jest.spyOn(ActiveWorkflowRunner.prototype, 'remove'); | ||
const removeWebhooksFunction = jest.spyOn(ActiveWorkflowRunner.prototype, 'removeWorkflowWebhooks'); | ||
const workflowRunnerRun = jest.spyOn(WorkflowRunner.prototype, 'run'); | ||
const workflowExecuteAdditionalDataExecuteErrorWorkflowSpy = jest.spyOn( | ||
WorkflowExecuteAdditionalData, | ||
'executeErrorWorkflow', | ||
); | ||
|
||
describe('ActiveWorkflowRunner', () => { | ||
let activeWorkflowRunner: ActiveWorkflowRunner; | ||
|
||
beforeAll(async () => { | ||
LoggerProxy.init(getLogger()); | ||
NodeTypes({ | ||
loaded: { | ||
nodes: MOCK_NODE_TYPES_DATA, | ||
credentials: {}, | ||
}, | ||
known: { nodes: {}, credentials: {} }, | ||
credentialTypes: {} as ICredentialTypes, | ||
}); | ||
CredentialTypes({ | ||
loaded: { | ||
nodes: MOCK_NODE_TYPES_DATA, | ||
credentials: {}, | ||
}, | ||
known: { nodes: {}, credentials: {} }, | ||
credentialTypes: {} as ICredentialTypes, | ||
}); | ||
}); | ||
|
||
beforeEach(() => { | ||
activeWorkflowRunner = new ActiveWorkflowRunner(); | ||
}); | ||
|
||
afterEach(async () => { | ||
await activeWorkflowRunner.removeAll(); | ||
databaseActiveWorkflowsCount = 0; | ||
jest.clearAllMocks(); | ||
}); | ||
|
||
test('Should initialize activeWorkflowRunner with empty list of active workflows and call External Hooks', async () => { | ||
void (await activeWorkflowRunner.init()); | ||
expect(await activeWorkflowRunner.getActiveWorkflows()).toHaveLength(0); | ||
expect(mocked(Db.collections.Workflow.find)).toHaveBeenCalled(); | ||
expect(mocked(Db.collections.Webhook.clear)).toHaveBeenCalled(); | ||
expect(mockExternalHooksRunFunction).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('Should initialize activeWorkflowRunner with one active workflow', async () => { | ||
databaseActiveWorkflowsCount = 1; | ||
void (await activeWorkflowRunner.init()); | ||
expect(await activeWorkflowRunner.getActiveWorkflows()).toHaveLength( | ||
databaseActiveWorkflowsCount, | ||
); | ||
expect(mocked(Db.collections.Workflow.find)).toHaveBeenCalled(); | ||
expect(mocked(Db.collections.Webhook.clear)).toHaveBeenCalled(); | ||
expect(mockExternalHooksRunFunction).toHaveBeenCalled(); | ||
}); | ||
|
||
test('Should make sure function checkIfWorkflowCanBeActivated was called for every workflow', async () => { | ||
databaseActiveWorkflowsCount = 2; | ||
void (await activeWorkflowRunner.init()); | ||
expect(workflowCheckIfCanBeActivated).toHaveBeenCalledTimes(databaseActiveWorkflowsCount); | ||
}); | ||
|
||
test('Call to removeAll should remove every workflow', async () => { | ||
databaseActiveWorkflowsCount = 2; | ||
void (await activeWorkflowRunner.init()); | ||
expect(await activeWorkflowRunner.getActiveWorkflows()).toHaveLength( | ||
databaseActiveWorkflowsCount, | ||
); | ||
void (await activeWorkflowRunner.removeAll()); | ||
expect(removeFunction).toHaveBeenCalledTimes(databaseActiveWorkflowsCount); | ||
}); | ||
|
||
test('Call to remove should also call removeWorkflowWebhooks', async () => { | ||
databaseActiveWorkflowsCount = 1; | ||
void (await activeWorkflowRunner.init()); | ||
expect(await activeWorkflowRunner.getActiveWorkflows()).toHaveLength( | ||
databaseActiveWorkflowsCount, | ||
); | ||
void (await activeWorkflowRunner.remove('1')); | ||
expect(removeWebhooksFunction).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('Call to isActive should return true for valid workflow', async () => { | ||
databaseActiveWorkflowsCount = 1; | ||
void (await activeWorkflowRunner.init()); | ||
expect(await activeWorkflowRunner.isActive('1')).toBe(true); | ||
}); | ||
|
||
test('Call to isActive should return false for invalid workflow', async () => { | ||
databaseActiveWorkflowsCount = 1; | ||
void (await activeWorkflowRunner.init()); | ||
expect(await activeWorkflowRunner.isActive('2')).toBe(false); | ||
}); | ||
|
||
test('Calling add should call checkIfWorkflowCanBeActivated', async () => { | ||
// Initialize with default (0) workflows | ||
void (await activeWorkflowRunner.init()); | ||
generateWorkflows(1); | ||
void (await activeWorkflowRunner.add('1', 'activate')); | ||
expect(workflowCheckIfCanBeActivated).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('runWorkflow should call run method in WorkflowRunner', async () => { | ||
void (await activeWorkflowRunner.init()); | ||
const workflow = generateWorkflows(1); | ||
const additionalData = await WorkflowExecuteAdditionalData.getBase('fake-user-id'); | ||
|
||
workflowRunnerRun.mockImplementationOnce(() => Promise.resolve('invalid-execution-id')); | ||
|
||
void (await activeWorkflowRunner.runWorkflow( | ||
workflow[0], | ||
workflow[0].nodes[0], | ||
[[]], | ||
additionalData, | ||
'trigger', | ||
)); | ||
|
||
expect(workflowRunnerRun).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
test('executeErrorWorkflow should call function with same name in WorkflowExecuteAdditionalData', async () => { | ||
const workflowData = generateWorkflows(1)[0]; | ||
const error = new NodeOperationError(workflowData.nodes[0], 'Fake error message'); | ||
void (await activeWorkflowRunner.init()); | ||
activeWorkflowRunner.executeErrorWorkflow(error, workflowData, 'trigger'); | ||
expect(workflowExecuteAdditionalDataExecuteErrorWorkflowSpy).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
Oops, something went wrong.