ci: Refactor cli tests to speed up CI (no-changelog) (#5718)

* ci: Refactor cli tests to speed up CI (no-changelog)

* upgrade jest to address memory leaks
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2023-03-17 17:24:05 +01:00
committed by GitHub
parent be172cb720
commit 6242cac53b
29 changed files with 4229 additions and 4762 deletions

View File

@@ -18,7 +18,7 @@ import { User } from '@/databases/entities/User';
import { getLogger } from '@/Logger';
import { randomEmail, randomName } from '../integration/shared/random';
import * as Helpers from './Helpers';
import { WorkflowExecuteAdditionalData } from '@/index';
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
import { WorkflowRunner } from '@/WorkflowRunner';
import { mock } from 'jest-mock-extended';

View File

@@ -11,20 +11,59 @@ import {
} from 'n8n-workflow';
import { CredentialsHelper } from '@/CredentialsHelper';
import { CredentialTypes } from '@/CredentialTypes';
import * as Helpers from './Helpers';
import { Container } from 'typedi';
import { NodeTypes } from '@/NodeTypes';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
const TEST_ENCRYPTION_KEY = 'test';
const mockNodesAndCredentials: INodesAndCredentials = {
loaded: { nodes: {}, credentials: {} },
known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
};
Container.set(LoadNodesAndCredentials, mockNodesAndCredentials);
describe('CredentialsHelper', () => {
const TEST_ENCRYPTION_KEY = 'test';
const mockNodesAndCredentials: INodesAndCredentials = {
loaded: {
nodes: {
'test.set': {
sourcePath: '',
type: {
description: {
displayName: 'Set',
name: 'set',
group: ['input'],
version: 1,
description: 'Sets a value',
defaults: {
name: 'Set',
color: '#0000FF',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Value1',
name: 'value1',
type: 'string',
default: 'default-value1',
},
{
displayName: 'Value2',
name: 'value2',
type: 'string',
default: 'default-value2',
},
],
},
},
},
},
credentials: {},
},
known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
};
Container.set(LoadNodesAndCredentials, mockNodesAndCredentials);
const nodeTypes = Container.get(NodeTypes);
describe('authenticate', () => {
const tests: Array<{
description: string;
@@ -219,8 +258,6 @@ describe('CredentialsHelper', () => {
qs: {},
};
const nodeTypes = Helpers.NodeTypes() as unknown as NodeTypes;
const workflow = new Workflow({
nodes: [node],
connections: {},

View File

@@ -1,32 +1,37 @@
import { LoggerProxy, WorkflowExecuteMode } from 'n8n-workflow';
import { QueryFailedError } from 'typeorm';
import { IRun, LoggerProxy, WorkflowExecuteMode } from 'n8n-workflow';
import { QueryFailedError, Repository } from 'typeorm';
import { mock } from 'jest-mock-extended';
import config from '@/config';
import { Db } from '@/index';
import * as Db from '@/Db';
import { User } from '@db/entities/User';
import { WorkflowStatistics } from '@db/entities/WorkflowStatistics';
import { nodeFetchedData, workflowExecutionCompleted } from '@/events/WorkflowStatistics';
import { getLogger } from '@/Logger';
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
import { getLogger } from '@/Logger';
import { InternalHooks } from '@/InternalHooks';
import { mockInstance } from '../integration/shared/utils';
const FAKE_USER_ID = 'abcde-fghij';
type WorkflowStatisticsRepository = Repository<WorkflowStatistics>;
jest.mock('@/Db', () => {
return {
collections: {
WorkflowStatistics: {
insert: jest.fn((...args) => {}),
update: jest.fn((...args) => {}),
},
WorkflowStatistics: mock<WorkflowStatisticsRepository>(),
},
};
});
jest.spyOn(UserManagementHelper, 'getWorkflowOwner').mockImplementation(async (_workflowId) => {
return { id: FAKE_USER_ID };
});
describe('Events', () => {
const fakeUser = Object.assign(new User(), { id: 'abcde-fghij' });
const internalHooks = mockInstance(InternalHooks);
jest.spyOn(UserManagementHelper, 'getWorkflowOwner').mockResolvedValue(fakeUser);
const workflowStatisticsRepository = Db.collections.WorkflowStatistics as ReturnType<
typeof mock<WorkflowStatisticsRepository>
>;
beforeAll(() => {
config.set('diagnostics.enabled', true);
config.set('deployment.type', 'n8n-testing');
@@ -57,8 +62,9 @@ describe('Events', () => {
nodes: [],
connections: {},
};
const runData = {
const runData: IRun = {
finished: true,
status: 'success',
data: { resultData: { runData: {} } },
mode: 'internal' as WorkflowExecuteMode,
startedAt: new Date(),
@@ -66,7 +72,7 @@ describe('Events', () => {
await workflowExecutionCompleted(workflow, runData);
expect(internalHooks.onFirstProductionWorkflowSuccess).toBeCalledTimes(1);
expect(internalHooks.onFirstProductionWorkflowSuccess).toHaveBeenNthCalledWith(1, {
user_id: FAKE_USER_ID,
user_id: fakeUser.id,
workflow_id: workflow.id,
});
});
@@ -82,8 +88,9 @@ describe('Events', () => {
nodes: [],
connections: {},
};
const runData = {
const runData: IRun = {
finished: false,
status: 'failed',
data: { resultData: { runData: {} } },
mode: 'internal' as WorkflowExecuteMode,
startedAt: new Date(),
@@ -94,7 +101,7 @@ describe('Events', () => {
test('should not send metrics for updated entries', async () => {
// Call the function with a fail insert, ensure update is called *and* metrics aren't sent
Db.collections.WorkflowStatistics.insert.mockImplementationOnce(() => {
workflowStatisticsRepository.insert.mockImplementationOnce(() => {
throw new QueryFailedError('invalid insert', [], '');
});
const workflow = {
@@ -106,8 +113,9 @@ describe('Events', () => {
nodes: [],
connections: {},
};
const runData = {
const runData: IRun = {
finished: true,
status: 'success',
data: { resultData: { runData: {} } },
mode: 'internal' as WorkflowExecuteMode,
startedAt: new Date(),
@@ -132,7 +140,7 @@ describe('Events', () => {
await nodeFetchedData(workflowId, node);
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
user_id: FAKE_USER_ID,
user_id: fakeUser.id,
workflow_id: workflowId,
node_type: node.type,
node_id: node.id,
@@ -159,7 +167,7 @@ describe('Events', () => {
await nodeFetchedData(workflowId, node);
expect(internalHooks.onFirstWorkflowDataLoad).toBeCalledTimes(1);
expect(internalHooks.onFirstWorkflowDataLoad).toHaveBeenNthCalledWith(1, {
user_id: FAKE_USER_ID,
user_id: fakeUser.id,
workflow_id: workflowId,
node_type: node.type,
node_id: node.id,
@@ -170,7 +178,7 @@ describe('Events', () => {
test('should not send metrics for entries that already have the flag set', async () => {
// Fetch data for workflow 2 which is set up to not be altered in the mocks
Db.collections.WorkflowStatistics.insert.mockImplementationOnce(() => {
workflowStatisticsRepository.insert.mockImplementationOnce(() => {
throw new QueryFailedError('invalid insert', [], '');
});
const workflowId = '1';

View File

@@ -1,108 +1,4 @@
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import {
INodeType,
INodeTypeData,
INodeTypes,
IVersionedNodeType,
NodeHelpers,
} from 'n8n-workflow';
// TODO: delete this
class NodeTypesClass implements INodeTypes {
nodeTypes: INodeTypeData = {
'test.set': {
sourcePath: '',
type: {
description: {
displayName: 'Set',
name: 'set',
group: ['input'],
version: 1,
description: 'Sets a value',
defaults: {
name: 'Set',
color: '#0000FF',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Value1',
name: 'value1',
type: 'string',
default: 'default-value1',
},
{
displayName: 'Value2',
name: 'value2',
type: 'string',
default: 'default-value2',
},
],
},
},
},
'fake-scheduler': {
sourcePath: '',
type: {
description: {
displayName: 'Schedule',
name: 'set',
group: ['input'],
version: 1,
description: 'Schedules execuitons',
defaults: {
name: 'Set',
color: '#0000FF',
},
inputs: ['main'],
outputs: ['main'],
properties: [
{
displayName: 'Value1',
name: 'value1',
type: 'string',
default: 'default-value1',
},
{
displayName: 'Value2',
name: 'value2',
type: 'string',
default: 'default-value2',
},
],
},
trigger: () => {
return Promise.resolve(undefined);
},
},
},
};
constructor(nodesAndCredentials?: LoadNodesAndCredentials) {
if (nodesAndCredentials?.loaded?.nodes) {
this.nodeTypes = nodesAndCredentials?.loaded?.nodes;
}
}
getByName(nodeType: string): INodeType | IVersionedNodeType {
return this.nodeTypes[nodeType].type;
}
getByNameAndVersion(nodeType: string, version?: number): INodeType {
return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version);
}
}
let nodeTypesInstance: NodeTypesClass | undefined;
export function NodeTypes(nodesAndCredentials?: LoadNodesAndCredentials): NodeTypesClass {
if (nodeTypesInstance === undefined) {
nodeTypesInstance = new NodeTypesClass(nodesAndCredentials);
}
return nodeTypesInstance;
}
import { INodeTypeData } from 'n8n-workflow';
/**
* Ensure all pending promises settle. The promise's `resolve` is placed in

View File

@@ -1,46 +1,47 @@
import { v4 as uuid } from 'uuid';
import {
ICredentialTypes,
INodeTypeData,
INodeTypes,
SubworkflowOperationError,
Workflow,
} from 'n8n-workflow';
import { Container } from 'typedi';
import { ICredentialTypes, INodeTypes, SubworkflowOperationError, Workflow } from 'n8n-workflow';
import config from '@/config';
import * as Db from '@/Db';
import * as testDb from '../integration/shared/testDb';
import { mockNodeTypesData, NodeTypes as MockNodeTypes } from './Helpers';
import { Role } from '@db/entities/Role';
import { User } from '@db/entities/User';
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { NodeTypes } from '@/NodeTypes';
import { UserService } from '@/user/user.service';
import { PermissionChecker } from '@/UserManagement/PermissionChecker';
import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
import { WorkflowsService } from '@/workflows/workflows.services';
import {
randomCredentialPayload as randomCred,
randomPositiveDigit,
} from '../integration/shared/random';
import { Role } from '@db/entities/Role';
import * as testDb from '../integration/shared/testDb';
import { mockNodeTypesData } from './Helpers';
import type { SaveCredentialFunction } from '../integration/shared/types';
import { User } from '@db/entities/User';
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { mockInstance } from '../integration/shared/utils';
let mockNodeTypes: INodeTypes;
let credentialOwnerRole: Role;
let workflowOwnerRole: Role;
let saveCredential: SaveCredentialFunction;
const MOCK_NODE_TYPES_DATA = mockNodeTypesData(['start', 'actionNetwork']);
mockInstance(LoadNodesAndCredentials, {
loaded: {
nodes: MOCK_NODE_TYPES_DATA,
credentials: {},
},
known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
});
beforeAll(async () => {
await testDb.init();
mockNodeTypes = MockNodeTypes({
loaded: {
nodes: MOCK_NODE_TYPES_DATA,
credentials: {},
},
known: { nodes: {}, credentials: {} },
credentialTypes: {} as ICredentialTypes,
});
mockNodeTypes = Container.get(NodeTypes);
credentialOwnerRole = await testDb.getCredentialOwnerRole();
workflowOwnerRole = await testDb.getWorkflowOwnerRole();
@@ -241,7 +242,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
nodes: [],
connections: {},
active: false,
nodeTypes: MockNodeTypes(),
nodeTypes: mockNodeTypes,
id: '2',
});
await expect(
@@ -263,7 +264,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
nodes: [],
connections: {},
active: false,
nodeTypes: MockNodeTypes(),
nodeTypes: mockNodeTypes,
id: '2',
});
await expect(
@@ -301,7 +302,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
nodes: [],
connections: {},
active: false,
nodeTypes: MockNodeTypes(),
nodeTypes: mockNodeTypes,
id: '2',
settings: {
callerPolicy: 'workflowsFromAList',
@@ -327,7 +328,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
nodes: [],
connections: {},
active: false,
nodeTypes: MockNodeTypes(),
nodeTypes: mockNodeTypes,
id: '2',
});
await expect(
@@ -350,7 +351,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
nodes: [],
connections: {},
active: false,
nodeTypes: MockNodeTypes(),
nodeTypes: mockNodeTypes,
id: '2',
settings: {
callerPolicy: 'workflowsFromAList',
@@ -376,7 +377,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
nodes: [],
connections: {},
active: false,
nodeTypes: MockNodeTypes(),
nodeTypes: mockNodeTypes,
id: '2',
settings: {
callerPolicy: 'any',
@@ -387,5 +388,3 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
).resolves.not.toThrow();
});
});
const MOCK_NODE_TYPES_DATA = mockNodeTypesData(['start', 'actionNetwork']);