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:
committed by
GitHub
parent
be172cb720
commit
6242cac53b
@@ -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';
|
||||
|
||||
@@ -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: {},
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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']);
|
||||
|
||||
Reference in New Issue
Block a user