refactor(core): Reduce boilterplate code in between tests 🧹, and fix the tests in node.js 20 (no-changelog) (#6654)

refactor(core): Reduce boilterplate code in between tests

also cleaned up some imports, and fixed the tests in node.js 20
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™
2023-07-13 10:14:48 +02:00
committed by GitHub
parent 3e07ffa73e
commit b895ba438a
78 changed files with 1197 additions and 1597 deletions

View File

@@ -2,6 +2,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { Container, Service } from 'typedi';
import type {
IDeferredPromise,
IExecuteResponsePromiseData,
@@ -19,8 +20,7 @@ import type {
IWorkflowExecutionDataProcess,
} from '@/Interfaces';
import { isWorkflowIdValid } from '@/utils';
import Container, { Service } from 'typedi';
import { ExecutionRepository } from './databases/repositories';
import { ExecutionRepository } from '@db/repositories';
@Service()
export class ActiveExecutions {

View File

@@ -13,6 +13,7 @@ import type {
WorkflowExecuteMode,
} from 'n8n-workflow';
import { validate } from 'class-validator';
import { Container } from 'typedi';
import { Like } from 'typeorm';
import config from '@/config';
import * as Db from '@/Db';
@@ -23,8 +24,7 @@ import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { TagEntity } from '@db/entities/TagEntity';
import type { User } from '@db/entities/User';
import type { UserUpdatePayload } from '@/requests';
import Container from 'typedi';
import { ExecutionRepository } from './databases/repositories';
import { ExecutionRepository } from '@db/repositories';
/**
* Returns the base URL n8n is reachable from

View File

@@ -30,8 +30,8 @@ import { eventBus } from './eventbus';
import type { User } from '@db/entities/User';
import { N8N_VERSION } from '@/constants';
import { NodeTypes } from './NodeTypes';
import type { ExecutionMetadata } from './databases/entities/ExecutionMetadata';
import { ExecutionRepository } from './databases/repositories';
import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata';
import { ExecutionRepository } from '@db/repositories';
function userToPayload(user: User): {
userId: string;

View File

@@ -1,11 +1,11 @@
import type { DeleteResult, FindOptionsWhere } from 'typeorm';
import { In, Not, Raw, LessThan } from 'typeorm';
import { Container } from 'typedi';
import type { ExecutionStatus } from 'n8n-workflow';
import * as Db from '@/Db';
import type { IExecutionBase, IExecutionFlattedDb } from '@/Interfaces';
import type { ExecutionStatus } from 'n8n-workflow';
import Container from 'typedi';
import { ExecutionRepository } from '@/databases/repositories';
import { ExecutionRepository } from '@db/repositories';
function getStatusCondition(status: ExecutionStatus) {
const condition: Pick<FindOptionsWhere<IExecutionFlattedDb>, 'status'> = {};

View File

@@ -1,9 +1,9 @@
import type express from 'express';
import { Container } from 'typedi';
import type { StatusResult } from 'simple-git';
import type { PublicSourceControlRequest } from '../../../types';
import { authorize } from '../../shared/middlewares/global.middleware';
import type { ImportResult } from '@/environments/sourceControl/types/importResult';
import Container from 'typedi';
import { SourceControlService } from '@/environments/sourceControl/sourceControl.service.ee';
import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee';
import { isSourceControlLicensed } from '@/environments/sourceControl/sourceControlHelper.ee';

View File

@@ -1,4 +1,5 @@
import type express from 'express';
import { Container } from 'typedi';
import { clean, getAllUsersAndCount, getUser } from './users.service.ee';
@@ -10,7 +11,6 @@ import {
} from '../../shared/middlewares/global.middleware';
import type { UserRequest } from '@/requests';
import { InternalHooks } from '@/InternalHooks';
import Container from 'typedi';
export = {
getUser: [

View File

@@ -168,9 +168,8 @@ import {
import { isSourceControlLicensed } from '@/environments/sourceControl/sourceControlHelper.ee';
import { SourceControlService } from '@/environments/sourceControl/sourceControl.service.ee';
import { SourceControlController } from '@/environments/sourceControl/sourceControl.controller.ee';
import { SourceControlPreferencesService } from './environments/sourceControl/sourceControlPreferences.service.ee';
import { ExecutionRepository } from './databases/repositories';
import type { ExecutionEntity } from './databases/entities/ExecutionEntity';
import { ExecutionRepository } from '@db/repositories';
import type { ExecutionEntity } from '@db/entities/ExecutionEntity';
const exec = promisify(callbackExec);
@@ -470,9 +469,6 @@ export class Server extends AbstractServer {
const internalHooks = Container.get(InternalHooks);
const mailer = Container.get(UserManagementMailer);
const postHog = this.postHog;
const samlService = Container.get(SamlService);
const sourceControlService = Container.get(SourceControlService);
const sourceControlPreferencesService = Container.get(SourceControlPreferencesService);
const controllers: object[] = [
new EventBusController(),
@@ -500,8 +496,8 @@ export class Server extends AbstractServer {
logger,
postHog,
}),
new SamlController(samlService),
new SourceControlController(sourceControlService, sourceControlPreferencesService),
Container.get(SamlController),
Container.get(SourceControlController),
];
if (isLdapEnabled()) {

View File

@@ -8,7 +8,7 @@ import {
LoggerProxy as Logger,
WorkflowOperationError,
} from 'n8n-workflow';
import Container, { Service } from 'typedi';
import { Container, Service } from 'typedi';
import type { FindManyOptions, ObjectLiteral } from 'typeorm';
import { Not, LessThanOrEqual } from 'typeorm';
import { DateUtils } from 'typeorm/util/DateUtils';
@@ -23,8 +23,8 @@ import type {
import { WorkflowRunner } from '@/WorkflowRunner';
import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
import { recoverExecutionDataFromEventLogMessages } from './eventbus/MessageEventBus/recoverEvents';
import { ExecutionRepository } from './databases/repositories';
import type { ExecutionEntity } from './databases/entities/ExecutionEntity';
import { ExecutionRepository } from '@db/repositories';
import type { ExecutionEntity } from '@db/entities/ExecutionEntity';
@Service()
export class WaitTracker {

View File

@@ -12,7 +12,7 @@ import { NodeTypes } from '@/NodeTypes';
import type { IExecutionResponse, IResponseCallbackData, IWorkflowDb } from '@/Interfaces';
import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData';
import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
import { ExecutionRepository } from './databases/repositories';
import { ExecutionRepository } from '@db/repositories';
@Service()
export class WaitingWebhooks {

View File

@@ -70,7 +70,7 @@ import { WorkflowsService } from './workflows/workflows.services';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import type { ExecutionMetadata } from '@db/entities/ExecutionMetadata';
import { ExecutionRepository } from './databases/repositories';
import { ExecutionRepository } from '@db/repositories';
const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType');

View File

@@ -35,8 +35,8 @@ import omit from 'lodash/omit';
import { PermissionChecker } from './UserManagement/PermissionChecker';
import { isWorkflowIdValid } from './utils';
import { UserService } from './user/user.service';
import type { SharedWorkflow } from './databases/entities/SharedWorkflow';
import type { RoleNames } from './databases/entities/Role';
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
import type { RoleNames } from '@db/entities/Role';
const ERROR_TRIGGER_TYPE = config.getEnv('nodes.errorTriggerType');

View File

@@ -54,7 +54,7 @@ import { eventBus } from './eventbus';
import { recoverExecutionDataFromEventLogMessages } from './eventbus/MessageEventBus/recoverEvents';
import { Container } from 'typedi';
import { InternalHooks } from './InternalHooks';
import { ExecutionRepository } from './databases/repositories';
import { ExecutionRepository } from '@db/repositories';
export class WorkflowRunner {
activeExecutions: ActiveExecutions;

View File

@@ -1,13 +1,13 @@
import type { FindOperator } from 'typeorm';
import { MoreThanOrEqual } from 'typeorm';
import { DateUtils } from 'typeorm/util/DateUtils';
import { Container } from 'typedi';
import * as Db from '@/Db';
import config from '@/config';
import { CREDENTIALS_REPORT } from '@/audit/constants';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { Risk } from '@/audit/types';
import Container from 'typedi';
import { ExecutionRepository } from '@/databases/repositories';
import { ExecutionRepository } from '@db/repositories';
async function getAllCredsInUse(workflows: WorkflowEntity[]) {
const credsInAnyUse = new Set<string>();

View File

@@ -17,7 +17,7 @@ import { disableAutoGeneratedIds } from '@db/utils/commandHelpers';
import type { ICredentialsDb, IWorkflowToImport } from '@/Interfaces';
import { replaceInvalidCredentials } from '@/WorkflowHelpers';
import { BaseCommand, UM_FIX_INSTRUCTION } from '../BaseCommand';
import { generateNanoId } from '@/databases/utils/generators';
import { generateNanoId } from '@db/utils/generators';
function assertHasWorkflowsToImport(workflows: unknown): asserts workflows is IWorkflowToImport[] {
if (!Array.isArray(workflows)) {

View File

@@ -22,7 +22,7 @@ import { getWorkflowOwner } from '@/UserManagement/UserManagementHelper';
import { generateFailedExecutionFromError } from '@/WorkflowHelpers';
import { N8N_VERSION } from '@/constants';
import { BaseCommand } from './BaseCommand';
import { ExecutionRepository } from '@/databases/repositories';
import { ExecutionRepository } from '@db/repositories';
export class Worker extends BaseCommand {
static description = '\nStarts a n8n worker';

View File

@@ -1,4 +1,6 @@
import validator from 'validator';
import { In } from 'typeorm';
import { Container } from 'typedi';
import { Authorized, Get, Post, RestController } from '@/decorators';
import {
AuthError,
@@ -13,7 +15,6 @@ import { Request, Response } from 'express';
import type { ILogger } from 'n8n-workflow';
import type { User } from '@db/entities/User';
import { LoginRequest, UserRequest } from '@/requests';
import { In } from 'typeorm';
import type { Config } from '@/config';
import type {
PublicUser,
@@ -30,7 +31,6 @@ import {
} from '@/sso/ssoHelpers';
import type { UserRepository } from '@db/repositories';
import { InternalHooks } from '../InternalHooks';
import Container from 'typedi';
import { License } from '@/License';
@RestController()

View File

@@ -1,5 +1,5 @@
import type { MigrationContext, ReversibleMigration } from '@db/types';
import { StatisticsNames } from '@/databases/entities/WorkflowStatistics';
import { StatisticsNames } from '@db/entities/WorkflowStatistics';
export class RemoveWorkflowDataLoadedFlag1671726148420 implements ReversibleMigration {
async up({ queryRunner, tablePrefix }: MigrationContext) {

View File

@@ -1,4 +1,4 @@
import type { MigrationContext, IrreversibleMigration } from '@/databases/types';
import type { MigrationContext, IrreversibleMigration } from '@db/types';
import { runInBatches } from '@db/utils/migrationHelpers';
import { addJsonKeyToPinDataColumn } from '../sqlite/1659888469333-AddJsonKeyPinData';

View File

@@ -1,5 +1,5 @@
import type { MigrationContext, ReversibleMigration } from '@db/types';
import { StatisticsNames } from '@/databases/entities/WorkflowStatistics';
import { StatisticsNames } from '@db/entities/WorkflowStatistics';
export class RemoveWorkflowDataLoadedFlag1671726148421 implements ReversibleMigration {
async up({ queryRunner, tablePrefix }: MigrationContext) {

View File

@@ -1,5 +1,5 @@
import type { MigrationContext, ReversibleMigration } from '@db/types';
import { StatisticsNames } from '@/databases/entities/WorkflowStatistics';
import { StatisticsNames } from '@db/entities/WorkflowStatistics';
export class RemoveWorkflowDataLoadedFlag1671726148419 implements ReversibleMigration {
async up({ queryRunner, tablePrefix }: MigrationContext) {

View File

@@ -3,7 +3,7 @@ import path from 'path';
import { UserSettings } from 'n8n-core';
import type { MigrationContext, IrreversibleMigration } from '@db/types';
import config from '@/config';
import { copyTable } from '@/databases/utils/migrationHelpers';
import { copyTable } from '@db/utils/migrationHelpers';
export class MigrateIntegerKeysToString1690000000002 implements IrreversibleMigration {
transaction = false as const;
@@ -199,11 +199,11 @@ function getSqliteDbFileSize(): number {
return size;
}
const pruneExecutionsData = async ({ queryRunner, tablePrefix }: MigrationContext) => {
const pruneExecutionsData = async ({ queryRunner, tablePrefix, logger }: MigrationContext) => {
if (migrationsPruningEnabled) {
const dbFileSize = getSqliteDbFileSize();
if (dbFileSize < DESIRED_DATABASE_FILE_SIZE) {
console.log(`DB Size not large enough to prune: ${dbFileSize}`);
logger.debug(`DB Size not large enough to prune: ${dbFileSize}`);
return;
}
@@ -224,6 +224,6 @@ const pruneExecutionsData = async ({ queryRunner, tablePrefix }: MigrationContex
await queryRunner.query(removalQuery);
console.timeEnd('pruningData');
} else {
console.log('Pruning was requested, but was not enabled');
logger.debug('Pruning was requested, but was not enabled');
}
};

View File

@@ -1,5 +1,5 @@
import type { MigrationContext, ReversibleMigration } from '@/databases/types';
import { copyTable } from '@/databases/utils/migrationHelpers';
import type { MigrationContext, ReversibleMigration } from '@db/types';
import { copyTable } from '@db/utils/migrationHelpers';
export class SeparateExecutionData1690000000010 implements ReversibleMigration {
async up({ queryRunner, tablePrefix }: MigrationContext): Promise<void> {

View File

@@ -1,6 +1,6 @@
import type { RequestHandler } from 'express';
import { Container } from 'typedi';
import { isSourceControlLicensed } from '../sourceControlHelper.ee';
import Container from 'typedi';
import { SourceControlPreferencesService } from '../sourceControlPreferences.service.ee';
export const sourceControlLicensedAndEnabledMiddleware: RequestHandler = (req, res, next) => {

View File

@@ -1,3 +1,6 @@
import express from 'express';
import { Service } from 'typedi';
import type { PullResult, PushResult, StatusResult } from 'simple-git';
import { Authorized, Get, Post, Patch, RestController } from '@/decorators';
import {
sourceControlLicensedMiddleware,
@@ -7,13 +10,12 @@ import { SourceControlService } from './sourceControl.service.ee';
import { SourceControlRequest } from './types/requests';
import type { SourceControlPreferences } from './types/sourceControlPreferences';
import { BadRequestError } from '@/ResponseHelper';
import type { PullResult, PushResult, StatusResult } from 'simple-git';
import express from 'express';
import type { ImportResult } from './types/importResult';
import { SourceControlPreferencesService } from './sourceControlPreferences.service.ee';
import type { SourceControlledFile } from './types/sourceControlledFile';
import { SOURCE_CONTROL_API_ROOT, SOURCE_CONTROL_DEFAULT_BRANCH } from './constants';
@Service()
@RestController(`/${SOURCE_CONTROL_API_ROOT}`)
export class SourceControlController {
constructor(

View File

@@ -32,8 +32,9 @@ import type {
import { SourceControlPreferencesService } from './sourceControlPreferences.service.ee';
import { writeFileSync } from 'fs';
import { SourceControlImportService } from './sourceControlImport.service.ee';
import type { WorkflowEntity } from '../../databases/entities/WorkflowEntity';
import type { CredentialsEntity } from '../../databases/entities/CredentialsEntity';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
@Service()
export class SourceControlService {
private sshKeyName: string;

View File

@@ -18,7 +18,7 @@ import type { IWorkflowToImport } from '@/Interfaces';
import type { ExportableWorkflow } from './types/exportableWorkflow';
import type { ExportableCredential } from './types/exportableCredential';
import type { ExportResult } from './types/exportResult';
import type { SharedWorkflow } from '@/databases/entities/SharedWorkflow';
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { sourceControlFoldersExistCheck } from './sourceControlHelper.ee';
@Service()

View File

@@ -1,10 +1,10 @@
import Container from 'typedi';
import { License } from '../../License';
import { Container } from 'typedi';
import { generateKeyPairSync } from 'crypto';
import sshpk from 'sshpk';
import type { KeyPair } from './types/keyPair';
import { constants as fsConstants, mkdirSync, accessSync } from 'fs';
import { LoggerProxy } from 'n8n-workflow';
import { License } from '@/License';
import type { KeyPair } from './types/keyPair';
import { SOURCE_CONTROL_GIT_KEY_COMMENT } from './constants';
export function sourceControlFoldersExistCheck(folders: string[]) {

View File

@@ -1,4 +1,4 @@
import Container, { Service } from 'typedi';
import { Container, Service } from 'typedi';
import path from 'path';
import {
SOURCE_CONTROL_CREDENTIAL_EXPORT_FOLDER,
@@ -15,12 +15,12 @@ import { readFile as fsReadFile } from 'fs/promises';
import { Credentials, UserSettings } from 'n8n-core';
import type { IWorkflowToImport } from '@/Interfaces';
import type { ExportableCredential } from './types/exportableCredential';
import { Variables } from '@/databases/entities/Variables';
import { Variables } from '@db/entities/Variables';
import type { ImportResult } from './types/importResult';
import { UM_FIX_INSTRUCTION } from '@/commands/BaseCommand';
import { SharedCredentials } from '@/databases/entities/SharedCredentials';
import type { WorkflowTagMapping } from '@/databases/entities/WorkflowTagMapping';
import type { TagEntity } from '@/databases/entities/TagEntity';
import { SharedCredentials } from '@db/entities/SharedCredentials';
import type { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping';
import type { TagEntity } from '@db/entities/TagEntity';
import { ActiveWorkflowRunner } from '../../ActiveWorkflowRunner';
import type { SourceControllPullOptions } from './types/sourceControlPullWorkFolder';
import { In } from 'typeorm';

View File

@@ -1,5 +1,5 @@
import type { TagEntity } from '@/databases/entities/TagEntity';
import type { WorkflowTagMapping } from '@/databases/entities/WorkflowTagMapping';
import type { TagEntity } from '@db/entities/TagEntity';
import type { WorkflowTagMapping } from '@db/entities/WorkflowTagMapping';
export interface ImportResult {
workflows: Array<{

View File

@@ -1,5 +1,5 @@
import { Container } from 'typedi';
import { License } from '@/License';
import Container from 'typedi';
export function isVariablesEnabled(): boolean {
const license = Container.get(License);

View File

@@ -1,10 +1,10 @@
import type { Variables } from '@/databases/entities/Variables';
import { Container } from 'typedi';
import type { Variables } from '@db/entities/Variables';
import { collections } from '@/Db';
import { InternalHooks } from '@/InternalHooks';
import Container from 'typedi';
import { generateNanoId } from '@db/utils/generators';
import { canCreateNewVariable } from './enviromentHelpers';
import { VariablesService } from './variables.service';
import { generateNanoId } from '../../databases/utils/generators';
export class VariablesLicenseError extends Error {}
export class VariablesValidationError extends Error {}

View File

@@ -1,4 +1,4 @@
import type { Variables } from '@/databases/entities/Variables';
import type { Variables } from '@db/entities/Variables';
import { collections } from '@/Db';
export class VariablesService {

View File

@@ -9,7 +9,7 @@ import { eventBus } from './MessageEventBus';
import { Container } from 'typedi';
import { InternalHooks } from '@/InternalHooks';
import { getWorkflowHooksMain } from '@/WorkflowExecuteAdditionalData';
import { ExecutionRepository } from '@/databases/repositories';
import { ExecutionRepository } from '@db/repositories';
export async function recoverExecutionDataFromEventLogMessages(
executionId: string,

View File

@@ -25,7 +25,7 @@ import * as Db from '@/Db';
import * as GenericHelpers from '@/GenericHelpers';
import { Container } from 'typedi';
import { getStatusUsingPreviousExecutionStatusMethod } from './executionHelpers';
import { ExecutionRepository } from '@/databases/repositories';
import { ExecutionRepository } from '@db/repositories';
export interface IGetExecutionsQueryFilter {
id?: FindOperator<string> | string;

View File

@@ -1,4 +1,5 @@
import express from 'express';
import { Container, Service } from 'typedi';
import { getInstanceBaseUrl } from '@/UserManagement/UserManagementHelper';
import { Authorized, Get, NoAuthRequired, Post, RestController } from '@/decorators';
import { SamlUrls } from '../constants';
@@ -23,9 +24,9 @@ import {
} from '../serviceProvider.ee';
import { getSamlConnectionTestSuccessView } from '../views/samlConnectionTestSuccess';
import { getSamlConnectionTestFailedView } from '../views/samlConnectionTestFailed';
import Container from 'typedi';
import { InternalHooks } from '@/InternalHooks';
@Service()
@RestController('/sso/saml')
export class SamlController {
constructor(private samlService: SamlService) {}

View File

@@ -12,6 +12,7 @@ import * as ResponseHelper from '@/ResponseHelper';
import * as WorkflowHelpers from '@/WorkflowHelpers';
import config from '@/config';
import type { SharedWorkflow } from '@db/entities/SharedWorkflow';
import type { RoleNames } from '@db/entities/Role';
import type { User } from '@db/entities/User';
import type { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { validateEntity } from '@/GenericHelpers';
@@ -27,7 +28,6 @@ import { getSharedWorkflowIds } from '@/WorkflowHelpers';
import { isSharingEnabled, whereClause } from '@/UserManagement/UserManagementHelper';
import type { WorkflowForList } from '@/workflows/workflows.types';
import { InternalHooks } from '@/InternalHooks';
import type { RoleNames } from '../databases/entities/Role';
export type IGetWorkflowsQueryFilter = Pick<
FindOptionsWhere<WorkflowEntity>,

View File

@@ -5,7 +5,7 @@ import { audit } from '@/audit';
import { CREDENTIALS_REPORT } from '@/audit/constants';
import { getRiskSection } from './utils';
import * as testDb from '../shared/testDb';
import { generateNanoId } from '@/databases/utils/generators';
import { generateNanoId } from '@db/utils/generators';
beforeAll(async () => {
await testDb.init();

View File

@@ -8,7 +8,7 @@ import {
} from '@/audit/constants';
import { getRiskSection, saveManualTriggerWorkflow } from './utils';
import * as testDb from '../shared/testDb';
import { generateNanoId } from '@/databases/utils/generators';
import { generateNanoId } from '@db/utils/generators';
beforeAll(async () => {
await testDb.init();

View File

@@ -4,7 +4,7 @@ import { audit } from '@/audit';
import { FILESYSTEM_INTERACTION_NODE_TYPES, FILESYSTEM_REPORT } from '@/audit/constants';
import { getRiskSection, saveManualTriggerWorkflow } from './utils';
import * as testDb from '../shared/testDb';
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
beforeAll(async () => {
await testDb.init();

View File

@@ -12,7 +12,7 @@ import {
import * as testDb from '../shared/testDb';
import { toReportTitle } from '@/audit/utils';
import config from '@/config';
import { generateNanoId } from '@/databases/utils/generators';
import { generateNanoId } from '@db/utils/generators';
beforeAll(async () => {
await testDb.init();

View File

@@ -6,10 +6,10 @@ import { OFFICIAL_RISKY_NODE_TYPES, NODES_REPORT } from '@/audit/constants';
import { getRiskSection, MOCK_PACKAGE, saveManualTriggerWorkflow } from './utils';
import * as testDb from '../shared/testDb';
import { toReportTitle } from '@/audit/utils';
import { mockInstance } from '../shared/utils';
import { mockInstance } from '../shared/utils/';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { NodeTypes } from '@/NodeTypes';
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
const nodesAndCredentials = mockInstance(LoadNodesAndCredentials);
nodesAndCredentials.getCustomDirectories.mockReturnValue([]);

View File

@@ -1,4 +1,3 @@
import type { Application } from 'express';
import type { SuperAgentTest } from 'supertest';
import { Container } from 'typedi';
import { License } from '@/License';
@@ -11,37 +10,27 @@ import type { User } from '@db/entities/User';
import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants';
import { randomValidPassword } from './shared/random';
import * as testDb from './shared/testDb';
import type { AuthAgent } from './shared/types';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
let app: Application;
let globalOwnerRole: Role;
let globalMemberRole: Role;
let owner: User;
let authAgent: AuthAgent;
let authlessAgent: SuperAgentTest;
let authOwnerAgent: SuperAgentTest;
const ownerPassword = randomValidPassword();
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['auth'] });
authAgent = utils.createAuthAgent(app);
const testServer = utils.setupTestServer({ endpointGroups: ['auth'] });
beforeAll(async () => {
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
});
beforeEach(async () => {
await testDb.truncate(['User']);
authlessAgent = utils.createAgent(app);
config.set('ldap.disabled', true);
await utils.setInstanceOwnerSetUp(true);
});
afterAll(async () => {
await testDb.terminate();
});
describe('POST /login', () => {
beforeEach(async () => {
owner = await testDb.createUser({
@@ -51,7 +40,7 @@ describe('POST /login', () => {
});
test('should log user in', async () => {
const response = await authlessAgent.post('/login').send({
const response = await testServer.authlessAgent.post('/login').send({
email: owner.email,
password: ownerPassword,
});
@@ -91,7 +80,7 @@ describe('POST /login', () => {
jest.spyOn(Container.get(License), 'isWithinUsersLimit').mockReturnValueOnce(false);
const member = await testDb.createUserShell(globalMemberRole);
const response = await authAgent(member).get('/login');
const response = await testServer.authAgentFor(member).get('/login');
expect(response.statusCode).toBe(401);
});
@@ -103,14 +92,14 @@ describe('POST /login', () => {
isOwner: true,
});
const response = await authAgent(ownerUser).get('/login');
const response = await testServer.authAgentFor(ownerUser).get('/login');
expect(response.statusCode).toBe(200);
});
});
describe('GET /login', () => {
test('should return 401 Unauthorized if no cookie', async () => {
const response = await authlessAgent.get('/login');
const response = await testServer.authlessAgent.get('/login');
expect(response.statusCode).toBe(401);
@@ -122,7 +111,7 @@ describe('GET /login', () => {
await testDb.createUserShell(globalOwnerRole);
await utils.setInstanceOwnerSetUp(false);
const response = await authlessAgent.get('/login');
const response = await testServer.authlessAgent.get('/login');
expect(response.statusCode).toBe(200);
@@ -131,9 +120,9 @@ describe('GET /login', () => {
});
test('should return 401 Unauthorized if invalid cookie', async () => {
authlessAgent.jar.setCookie(`${AUTH_COOKIE_NAME}=invalid`);
testServer.authlessAgent.jar.setCookie(`${AUTH_COOKIE_NAME}=invalid`);
const response = await authlessAgent.get('/login');
const response = await testServer.authlessAgent.get('/login');
expect(response.statusCode).toBe(401);
@@ -144,7 +133,7 @@ describe('GET /login', () => {
test('should return logged-in owner shell', async () => {
const ownerShell = await testDb.createUserShell(globalOwnerRole);
const response = await authAgent(ownerShell).get('/login');
const response = await testServer.authAgentFor(ownerShell).get('/login');
expect(response.statusCode).toBe(200);
@@ -180,7 +169,7 @@ describe('GET /login', () => {
test('should return logged-in member shell', async () => {
const memberShell = await testDb.createUserShell(globalMemberRole);
const response = await authAgent(memberShell).get('/login');
const response = await testServer.authAgentFor(memberShell).get('/login');
expect(response.statusCode).toBe(200);
@@ -216,7 +205,7 @@ describe('GET /login', () => {
test('should return logged-in owner', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const response = await authAgent(owner).get('/login');
const response = await testServer.authAgentFor(owner).get('/login');
expect(response.statusCode).toBe(200);
@@ -252,7 +241,7 @@ describe('GET /login', () => {
test('should return logged-in member', async () => {
const member = await testDb.createUser({ globalRole: globalMemberRole });
const response = await authAgent(member).get('/login');
const response = await testServer.authAgentFor(member).get('/login');
expect(response.statusCode).toBe(200);
@@ -292,7 +281,7 @@ describe('GET /resolve-signup-token', () => {
password: ownerPassword,
globalRole: globalOwnerRole,
});
authOwnerAgent = authAgent(owner);
authOwnerAgent = testServer.authAgentFor(owner);
});
test('should validate invite token', async () => {
@@ -361,7 +350,7 @@ describe('POST /logout', () => {
test('should log user out', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const response = await authAgent(owner).post('/logout');
const response = await testServer.authAgentFor(owner).post('/logout');
expect(response.statusCode).toBe(200);
expect(response.body).toEqual(LOGGED_OUT_RESPONSE_BODY);

View File

@@ -1,50 +1,51 @@
import type { SuperAgentTest } from 'supertest';
import {
ROUTES_REQUIRING_AUTHENTICATION,
ROUTES_REQUIRING_AUTHORIZATION,
} from './shared/constants';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils';
import config from '@/config';
import * as utils from './shared/utils/';
let authlessAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
describe('Auth Middleware', () => {
const testServer = utils.setupTestServer({ endpointGroups: ['me', 'auth', 'owner', 'users'] });
beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['me', 'auth', 'owner', 'users'] });
const globalMemberRole = await testDb.getGlobalMemberRole();
const member = await testDb.createUser({ globalRole: globalMemberRole });
/** Routes requiring a valid `n8n-auth` cookie for a user, either owner or member. */
const ROUTES_REQUIRING_AUTHENTICATION: Readonly<Array<[string, string]>> = [
['PATCH', '/me'],
['PATCH', '/me/password'],
['POST', '/me/survey'],
['POST', '/owner/setup'],
['GET', '/non-existent'],
];
authlessAgent = utils.createAgent(app);
authMemberAgent = utils.createAuthAgent(app)(member);
/** Routes requiring a valid `n8n-auth` cookie for an owner. */
const ROUTES_REQUIRING_AUTHORIZATION: Readonly<Array<[string, string]>> = [
['POST', '/users'],
['DELETE', '/users/123'],
['POST', '/users/123/reinvite'],
['POST', '/owner/setup'],
];
config.set('userManagement.isInstanceOwnerSetUp', true);
});
describe('Routes requiring Authentication', () => {
ROUTES_REQUIRING_AUTHENTICATION.concat(ROUTES_REQUIRING_AUTHORIZATION).forEach(
([method, endpoint]) => {
test(`${method} ${endpoint} should return 401 Unauthorized if no cookie`, async () => {
const { statusCode } = await testServer.authlessAgent[method.toLowerCase()](endpoint);
expect(statusCode).toBe(401);
});
},
);
});
afterAll(async () => {
await testDb.terminate();
});
describe('Routes requiring Authorization', () => {
let authMemberAgent: SuperAgentTest;
beforeAll(async () => {
const globalMemberRole = await testDb.getGlobalMemberRole();
const member = await testDb.createUser({ globalRole: globalMemberRole });
authMemberAgent = testServer.authAgentFor(member);
});
ROUTES_REQUIRING_AUTHENTICATION.concat(ROUTES_REQUIRING_AUTHORIZATION).forEach((route) => {
const [method, endpoint] = getMethodAndEndpoint(route);
test(`${route} should return 401 Unauthorized if no cookie`, async () => {
const { statusCode } = await authlessAgent[method](endpoint);
expect(statusCode).toBe(401);
ROUTES_REQUIRING_AUTHORIZATION.forEach(async ([method, endpoint]) => {
test(`${method} ${endpoint} should return 403 Forbidden for member`, async () => {
const { statusCode } = await authMemberAgent[method.toLowerCase()](endpoint);
expect(statusCode).toBe(403);
});
});
});
});
ROUTES_REQUIRING_AUTHORIZATION.forEach(async (route) => {
const [method, endpoint] = getMethodAndEndpoint(route);
test(`${route} should return 403 Forbidden for member`, async () => {
const { statusCode } = await authMemberAgent[method](endpoint);
expect(statusCode).toBe(403);
});
});
function getMethodAndEndpoint(route: string) {
return route.split(' ').map((segment, index) => {
return index % 2 === 0 ? segment.toLowerCase() : segment;
});
}

View File

@@ -1,5 +1,5 @@
import * as testDb from '../shared/testDb';
import { mockInstance } from '../shared/utils';
import { mockInstance } from '../shared/utils/';
import { InternalHooks } from '@/InternalHooks';
import { ImportWorkflowsCommand } from '@/commands/import/workflow';
import * as Config from '@oclif/config';

View File

@@ -2,7 +2,7 @@ import * as Db from '@/Db';
import { Reset } from '@/commands/user-management/reset';
import type { Role } from '@db/entities/Role';
import * as testDb from '../shared/testDb';
import { mockInstance } from '../shared/utils';
import { mockInstance } from '../shared/utils/';
import { InternalHooks } from '@/InternalHooks';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { NodeTypes } from '@/NodeTypes';

View File

@@ -11,23 +11,19 @@ import type { Role } from '@db/entities/Role';
import type { User } from '@db/entities/User';
import { randomCredentialPayload } from './shared/random';
import * as testDb from './shared/testDb';
import type { AuthAgent, SaveCredentialFunction } from './shared/types';
import * as utils from './shared/utils';
import config from '@/config';
import type { SaveCredentialFunction } from './shared/types';
import * as utils from './shared/utils/';
const sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
const testServer = utils.setupTestServer({ endpointGroups: ['credentials'] });
let globalMemberRole: Role;
let owner: User;
let member: User;
let authOwnerAgent: SuperAgentTest;
let authAgent: AuthAgent;
let saveCredential: SaveCredentialFunction;
let sharingSpy: jest.SpyInstance<boolean>;
beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['credentials'] });
await utils.initConfigFile();
const globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
@@ -35,22 +31,15 @@ beforeAll(async () => {
owner = await testDb.createUser({ globalRole: globalOwnerRole });
member = await testDb.createUser({ globalRole: globalMemberRole });
authAgent = utils.createAuthAgent(app);
authOwnerAgent = authAgent(owner);
authOwnerAgent = testServer.authAgentFor(owner);
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
config.set('userManagement.isInstanceOwnerSetUp', true);
});
beforeEach(async () => {
await testDb.truncate(['SharedCredentials', 'Credentials']);
});
afterAll(async () => {
await testDb.terminate();
});
// ----------------------------------------
// dynamic router switching
// ----------------------------------------
@@ -163,7 +152,7 @@ describe('GET /credentials', () => {
await testDb.shareCredentialWithUsers(savedMemberCredential, [member2]);
const response = await authAgent(member1).get('/credentials');
const response = await testServer.authAgentFor(member1).get('/credentials');
expect(response.statusCode).toBe(200);
expect(response.body.data).toHaveLength(1); // member retrieved only member cred
@@ -270,7 +259,7 @@ describe('GET /credentials/:id', () => {
const [member1, member2, member3] = await testDb.createManyUsers(3, {
globalRole: globalMemberRole,
});
const authMemberAgent = authAgent(member1);
const authMemberAgent = testServer.authAgentFor(member1);
const savedCredential = await saveCredential(randomCredentialPayload(), { user: member1 });
await testDb.shareCredentialWithUsers(savedCredential, [member2, member3]);
@@ -307,7 +296,9 @@ describe('GET /credentials/:id', () => {
test('should not retrieve non-owned cred for member', async () => {
const savedCredential = await saveCredential(randomCredentialPayload(), { user: owner });
const response = await authAgent(member).get(`/credentials/${savedCredential.id}`);
const response = await testServer
.authAgentFor(member)
.get(`/credentials/${savedCredential.id}`);
expect(response.statusCode).toBe(403);
expect(response.body.data).toBeUndefined(); // owner's cred not returned

View File

@@ -1,4 +1,3 @@
import type { Application } from 'express';
import type { SuperAgentTest } from 'supertest';
import { UserSettings } from 'n8n-core';
@@ -11,14 +10,13 @@ import type { Role } from '@db/entities/Role';
import type { User } from '@db/entities/User';
import { randomCredentialPayload, randomName, randomString } from './shared/random';
import * as testDb from './shared/testDb';
import type { AuthAgent, SaveCredentialFunction } from './shared/types';
import * as utils from './shared/utils';
import type { SaveCredentialFunction } from './shared/types';
import * as utils from './shared/utils/';
// mock that credentialsSharing is not enabled
const mockIsCredentialsSharingEnabled = jest.spyOn(UserManagementHelpers, 'isSharingEnabled');
mockIsCredentialsSharingEnabled.mockReturnValue(false);
jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(false);
const testServer = utils.setupTestServer({ endpointGroups: ['credentials'] });
let app: Application;
let globalOwnerRole: Role;
let globalMemberRole: Role;
let owner: User;
@@ -26,13 +24,8 @@ let member: User;
let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
let saveCredential: SaveCredentialFunction;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['credentials'] });
await utils.initConfigFile();
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
@@ -42,21 +35,14 @@ beforeAll(async () => {
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
authAgent = utils.createAuthAgent(app);
authOwnerAgent = authAgent(owner);
authMemberAgent = authAgent(member);
config.set('userManagement.isInstanceOwnerSetUp', true);
authOwnerAgent = testServer.authAgentFor(owner);
authMemberAgent = testServer.authAgentFor(member);
});
beforeEach(async () => {
await testDb.truncate(['SharedCredentials', 'Credentials']);
});
afterAll(async () => {
await testDb.terminate();
});
// ----------------------------------------
// GET /credentials - fetch all credentials
// ----------------------------------------
@@ -90,7 +76,7 @@ describe('GET /credentials', () => {
saveCredential(randomCredentialPayload(), { user: member2 }),
]);
const response = await authAgent(member1).get('/credentials');
const response = await testServer.authAgentFor(member1).get('/credentials');
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(1); // member retrieved only own cred

View File

@@ -1,23 +1,18 @@
import { Container } from 'typedi';
import type { SuperAgentTest } from 'supertest';
import type { User } from '@db/entities/User';
import { License } from '@/License';
import * as testDb from '../shared/testDb';
import * as utils from '../shared/utils';
import { SOURCE_CONTROL_API_ROOT } from '@/environments/sourceControl/constants';
import * as testDb from '../shared/testDb';
import * as utils from '../shared/utils/';
let owner: User;
let authOwnerAgent: SuperAgentTest;
beforeAll(async () => {
Container.get(License).isSourceControlLicensed = () => true;
const app = await utils.initTestServer({ endpointGroups: ['sourceControl'] });
owner = await testDb.createOwner();
authOwnerAgent = utils.createAuthAgent(app)(owner);
const testServer = utils.setupTestServer({
endpointGroups: ['sourceControl'],
enabledFeatures: ['feat:sourceControl'],
});
afterAll(async () => {
await testDb.terminate();
beforeAll(async () => {
const owner = await testDb.createOwner();
authOwnerAgent = testServer.authAgentFor(owner);
});
describe('GET /sourceControl/preferences', () => {

View File

@@ -1,11 +1,9 @@
import type express from 'express';
import config from '@/config';
import axios from 'axios';
import syslog from 'syslog-client';
import { v4 as uuid } from 'uuid';
import { Container } from 'typedi';
import type { SuperAgentTest } from 'supertest';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
import * as testDb from './shared/testDb';
import type { Role } from '@db/entities/Role';
import type { User } from '@db/entities/User';
@@ -26,7 +24,6 @@ import type { MessageEventBusDestinationWebhook } from '@/eventbus/MessageEventB
import type { MessageEventBusDestinationSentry } from '@/eventbus/MessageEventBusDestination/MessageEventBusDestinationSentry.ee';
import { EventMessageAudit } from '@/eventbus/EventMessageClasses/EventMessageAudit';
import type { EventNamesTypes } from '@/eventbus/EventMessageClasses';
import { License } from '@/License';
jest.unmock('@/eventbus/MessageEventBus/MessageEventBus');
jest.mock('axios');
@@ -34,10 +31,8 @@ const mockedAxios = axios as jest.Mocked<typeof axios>;
jest.mock('syslog-client');
const mockedSyslog = syslog as jest.Mocked<typeof syslog>;
let app: express.Application;
let globalOwnerRole: Role;
let owner: User;
let unAuthOwnerAgent: SuperAgentTest;
let authOwnerAgent: SuperAgentTest;
const testSyslogDestination: MessageEventBusDestinationSyslogOptions = {
@@ -80,40 +75,27 @@ async function confirmIdSent(id: string) {
expect(sent.find((msg) => msg.id === id)).toBeTruthy();
}
beforeAll(async () => {
Container.get(License).isLogStreamingEnabled = () => true;
app = await utils.initTestServer({ endpointGroups: ['eventBus'] });
const testServer = utils.setupTestServer({
endpointGroups: ['eventBus'],
enabledFeatures: ['feat:logStreaming'],
});
beforeAll(async () => {
globalOwnerRole = await testDb.getGlobalOwnerRole();
owner = await testDb.createUser({ globalRole: globalOwnerRole });
unAuthOwnerAgent = utils.createAgent(app, {
apiPath: 'internal',
auth: false,
user: owner,
version: 1,
});
authOwnerAgent = utils.createAgent(app, {
apiPath: 'internal',
auth: true,
user: owner,
version: 1,
});
authOwnerAgent = testServer.authAgentFor(owner);
mockedSyslog.createClient.mockImplementation(() => new syslog.Client());
await utils.initConfigFile();
await utils.initEncryptionKey();
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
config.set('eventBus.logWriter.keepLogCount', 1);
config.set('userManagement.isInstanceOwnerSetUp', true);
await eventBus.initialize();
});
afterAll(async () => {
jest.mock('@/eventbus/MessageEventBus/MessageEventBus');
await testDb.terminate();
await eventBus.close();
});
@@ -138,41 +120,47 @@ test('should have logwriter log messages', async () => {
});
});
test('GET /eventbus/destination should fail due to missing authentication', async () => {
const response = await unAuthOwnerAgent.get('/eventbus/destination');
expect(response.statusCode).toBe(401);
describe('GET /eventbus/destination', () => {
test('should fail due to missing authentication', async () => {
const response = await testServer.authlessAgent.get('/eventbus/destination');
expect(response.statusCode).toBe(401);
});
test('all returned destinations should exist in eventbus', async () => {
const response = await authOwnerAgent.get('/eventbus/destination');
expect(response.statusCode).toBe(200);
const data = response.body.data;
expect(data).toBeTruthy();
expect(Array.isArray(data)).toBeTruthy();
for (let index = 0; index < data.length; index++) {
const destination = data[index];
const foundDestinations = await eventBus.findDestination(destination.id);
expect(Array.isArray(foundDestinations)).toBeTruthy();
expect(foundDestinations.length).toBe(1);
expect(foundDestinations[0].label).toBe(destination.label);
}
});
});
test('POST /eventbus/destination create syslog destination', async () => {
const response = await authOwnerAgent.post('/eventbus/destination').send(testSyslogDestination);
expect(response.statusCode).toBe(200);
});
describe('POST /eventbus/destination', () => {
test('create syslog destination', async () => {
const response = await authOwnerAgent.post('/eventbus/destination').send(testSyslogDestination);
expect(response.statusCode).toBe(200);
});
test('POST /eventbus/destination create sentry destination', async () => {
const response = await authOwnerAgent.post('/eventbus/destination').send(testSentryDestination);
expect(response.statusCode).toBe(200);
});
test('create sentry destination', async () => {
const response = await authOwnerAgent.post('/eventbus/destination').send(testSentryDestination);
expect(response.statusCode).toBe(200);
});
test('POST /eventbus/destination create webhook destination', async () => {
const response = await authOwnerAgent.post('/eventbus/destination').send(testWebhookDestination);
expect(response.statusCode).toBe(200);
});
test('GET /eventbus/destination all returned destinations should exist in eventbus', async () => {
const response = await authOwnerAgent.get('/eventbus/destination');
expect(response.statusCode).toBe(200);
const data = response.body.data;
expect(data).toBeTruthy();
expect(Array.isArray(data)).toBeTruthy();
for (let index = 0; index < data.length; index++) {
const destination = data[index];
const foundDestinations = await eventBus.findDestination(destination.id);
expect(Array.isArray(foundDestinations)).toBeTruthy();
expect(foundDestinations.length).toBe(1);
expect(foundDestinations[0].label).toBe(destination.label);
}
test('create webhook destination', async () => {
const response = await authOwnerAgent
.post('/eventbus/destination')
.send(testWebhookDestination);
expect(response.statusCode).toBe(200);
});
});
// this test (presumably the mocking) is causing the test suite to randomly fail
@@ -388,7 +376,7 @@ test('should send message to sentry ', async () => {
});
});
test('DEL /eventbus/destination delete all destinations by id', async () => {
test('DELETE /eventbus/destination delete all destinations by id', async () => {
const existingDestinationIds = [...Object.keys(eventBus.destinations)];
await Promise.all(

View File

@@ -1,7 +1,6 @@
import type express from 'express';
import type { SuperAgentTest } from 'supertest';
import type { Entry as LdapUser } from 'ldapts';
import { Not } from 'typeorm';
import { Container } from 'typedi';
import { jsonParse } from 'n8n-workflow';
import config from '@/config';
import * as Db from '@/Db';
@@ -14,19 +13,17 @@ import { encryptPassword, saveLdapSynchronization } from '@/Ldap/helpers';
import type { LdapConfig } from '@/Ldap/types';
import { sanitizeUser } from '@/UserManagement/UserManagementHelper';
import { getCurrentAuthenticationMethod, setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
import { License } from '@/License';
import { randomEmail, randomName, uniqueId } from './../shared/random';
import * as testDb from './../shared/testDb';
import type { AuthAgent } from '../shared/types';
import * as utils from '../shared/utils';
import * as utils from '../shared/utils/';
jest.mock('@/telemetry');
jest.mock('@/UserManagement/email/NodeMailer');
let app: express.Application;
let globalMemberRole: Role;
let owner: User;
let authAgent: AuthAgent;
let authOwnerAgent: SuperAgentTest;
const defaultLdapConfig = {
...LDAP_DEFAULT_CONFIGURATION,
@@ -42,23 +39,24 @@ const defaultLdapConfig = {
bindingAdminPassword: 'adminPassword',
};
beforeAll(async () => {
Container.get(License).isLdapEnabled = () => true;
app = await utils.initTestServer({ endpointGroups: ['auth', 'ldap'] });
const testServer = utils.setupTestServer({
endpointGroups: ['auth', 'ldap'],
enabledFeatures: ['feat:ldap'],
});
beforeAll(async () => {
const [globalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
globalMemberRole = fetchedGlobalMemberRole;
owner = await testDb.createUser({ globalRole: globalOwnerRole });
authAgent = utils.createAuthAgent(app);
authOwnerAgent = testServer.authAgentFor(owner);
defaultLdapConfig.bindingAdminPassword = await encryptPassword(
defaultLdapConfig.bindingAdminPassword,
);
await utils.initConfigFile();
await utils.initEncryptionKey();
await setCurrentAuthenticationMethod('email');
});
@@ -81,10 +79,6 @@ beforeEach(async () => {
config.set('userManagement.emails.mode', '');
});
afterAll(async () => {
await testDb.terminate();
});
const createLdapConfig = async (attributes: Partial<LdapConfig> = {}): Promise<LdapConfig> => {
const { value: ldapConfig } = await Db.collections.Settings.save({
key: LDAP_FEATURE_NAME,
@@ -99,21 +93,12 @@ const createLdapConfig = async (attributes: Partial<LdapConfig> = {}): Promise<L
test('Member role should not be able to access ldap routes', async () => {
const member = await testDb.createUser({ globalRole: globalMemberRole });
let response = await authAgent(member).get('/ldap/config');
expect(response.statusCode).toBe(403);
response = await authAgent(member).put('/ldap/config');
expect(response.statusCode).toBe(403);
response = await authAgent(member).post('/ldap/test-connection');
expect(response.statusCode).toBe(403);
response = await authAgent(member).post('/ldap/sync');
expect(response.statusCode).toBe(403);
response = await authAgent(member).get('/ldap/sync');
expect(response.statusCode).toBe(403);
const authAgent = testServer.authAgentFor(member);
await authAgent.get('/ldap/config').expect(403);
await authAgent.put('/ldap/config').expect(403);
await authAgent.post('/ldap/test-connection').expect(403);
await authAgent.post('/ldap/sync').expect(403);
await authAgent.get('/ldap/sync').expect(403);
});
describe('PUT /ldap/config', () => {
@@ -142,7 +127,7 @@ describe('PUT /ldap/config', () => {
];
for (const invalidPayload of invalidPayloads) {
const response = await authAgent(owner).put('/ldap/config').send(invalidPayload);
const response = await authOwnerAgent.put('/ldap/config').send(invalidPayload);
expect(response.statusCode).toBe(400);
expect(response.body).toHaveProperty('message');
}
@@ -155,7 +140,7 @@ describe('PUT /ldap/config', () => {
loginLabel: '',
};
const response = await authAgent(owner).put('/ldap/config').send(validPayload);
const response = await authOwnerAgent.put('/ldap/config').send(validPayload);
expect(response.statusCode).toBe(200);
expect(response.body.data.loginEnabled).toBe(true);
@@ -171,9 +156,7 @@ describe('PUT /ldap/config', () => {
const configuration = ldapConfig;
// disable the login, so the strategy is applied
await authAgent(owner)
.put('/ldap/config')
.send({ ...configuration, loginEnabled: false });
await authOwnerAgent.put('/ldap/config').send({ ...configuration, loginEnabled: false });
const emailUser = await Db.collections.User.findOneByOrFail({ id: member.id });
const localLdapIdentities = await testDb.getLdapIdentities();
@@ -193,11 +176,11 @@ test('GET /ldap/config route should retrieve current configuration', async () =>
loginLabel: '',
};
let response = await authAgent(owner).put('/ldap/config').send(validPayload);
let response = await authOwnerAgent.put('/ldap/config').send(validPayload);
expect(response.statusCode).toBe(200);
expect(getCurrentAuthenticationMethod()).toBe('ldap');
response = await authAgent(owner).get('/ldap/config');
response = await authOwnerAgent.get('/ldap/config');
expect(response.body.data).toMatchObject(validPayload);
});
@@ -206,8 +189,7 @@ describe('POST /ldap/test-connection', () => {
test('route should success', async () => {
jest.spyOn(LdapService.prototype, 'testConnection').mockResolvedValue();
const response = await authAgent(owner).post('/ldap/test-connection');
expect(response.statusCode).toBe(200);
await authOwnerAgent.post('/ldap/test-connection').expect(200);
});
test('route should fail', async () => {
@@ -215,7 +197,7 @@ describe('POST /ldap/test-connection', () => {
jest.spyOn(LdapService.prototype, 'testConnection').mockRejectedValue(new Error(errorMessage));
const response = await authAgent(owner).post('/ldap/test-connection');
const response = await authOwnerAgent.post('/ldap/test-connection');
expect(response.statusCode).toBe(400);
expect(response.body).toHaveProperty('message');
expect(response.body.message).toStrictEqual(errorMessage);
@@ -237,9 +219,7 @@ describe('POST /ldap/sync', () => {
const runTest = async (ldapUsers: LdapUser[]) => {
jest.spyOn(LdapService.prototype, 'searchWithAdminBinding').mockResolvedValue(ldapUsers);
const response = await authAgent(owner).post('/ldap/sync').send({ type: 'dry' });
expect(response.statusCode).toBe(200);
await authOwnerAgent.post('/ldap/sync').send({ type: 'dry' }).expect(200);
const synchronization = await Db.collections.AuthProviderSyncHistory.findOneByOrFail({});
@@ -332,9 +312,7 @@ describe('POST /ldap/sync', () => {
const runTest = async (ldapUsers: LdapUser[]) => {
jest.spyOn(LdapService.prototype, 'searchWithAdminBinding').mockResolvedValue(ldapUsers);
const response = await authAgent(owner).post('/ldap/sync').send({ type: 'live' });
expect(response.statusCode).toBe(200);
await authOwnerAgent.post('/ldap/sync').send({ type: 'live' }).expect(200);
const synchronization = await Db.collections.AuthProviderSyncHistory.findOneByOrFail({});
@@ -460,9 +438,9 @@ describe('POST /ldap/sync', () => {
jest.spyOn(LdapService.prototype, 'searchWithAdminBinding').mockResolvedValue([]);
await authAgent(owner).post('/ldap/sync').send({ type: 'live' });
await authOwnerAgent.post('/ldap/sync').send({ type: 'live' });
const response = await authAgent(member).get('/login');
const response = await testServer.authAgentFor(member).get('/login');
expect(response.body.code).toBe(401);
});
});
@@ -483,10 +461,10 @@ test('GET /ldap/sync should return paginated synchronizations', async () => {
});
}
let response = await authAgent(owner).get('/ldap/sync?perPage=1&page=0');
let response = await authOwnerAgent.get('/ldap/sync?perPage=1&page=0');
expect(response.body.data.length).toBe(1);
response = await authAgent(owner).get('/ldap/sync?perPage=1&page=1');
response = await authOwnerAgent.get('/ldap/sync?perPage=1&page=1');
expect(response.body.data.length).toBe(1);
});
@@ -495,13 +473,11 @@ describe('POST /login', () => {
const ldapConfig = await createLdapConfig();
LdapManager.updateConfig(ldapConfig);
const authlessAgent = utils.createAgent(app);
jest.spyOn(LdapService.prototype, 'searchWithAdminBinding').mockResolvedValue([ldapUser]);
jest.spyOn(LdapService.prototype, 'validUser').mockResolvedValue();
const response = await authlessAgent
const response = await testServer.authlessAgent
.post('/login')
.send({ email: ldapUser.mail, password: 'password' });
@@ -581,7 +557,7 @@ describe('Instance owner should able to delete LDAP users', () => {
const member = await testDb.createLdapUser({ globalRole: globalMemberRole }, uniqueId());
await authAgent(owner).post(`/users/${member.id}`);
await authOwnerAgent.post(`/users/${member.id}`);
});
test('transfer workflows and credentials', async () => {
@@ -591,7 +567,7 @@ describe('Instance owner should able to delete LDAP users', () => {
const member = await testDb.createLdapUser({ globalRole: globalMemberRole }, uniqueId());
// delete the LDAP member and transfer its workflows/credentials to instance owner
await authAgent(owner).post(`/users/${member.id}?transferId=${owner.id}`);
await authOwnerAgent.post(`/users/${member.id}?transferId=${owner.id}`);
});
});

View File

@@ -4,7 +4,7 @@ import type { User } from '@db/entities/User';
import type { ILicensePostResponse, ILicenseReadResponse } from '@/Interfaces';
import { License } from '@/License';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
const MOCK_SERVER_URL = 'https://server.com/v1';
const MOCK_RENEW_OFFSET = 259200;
@@ -14,19 +14,17 @@ let member: User;
let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['license'] });
const testServer = utils.setupTestServer({ endpointGroups: ['license'] });
beforeAll(async () => {
const globalOwnerRole = await testDb.getGlobalOwnerRole();
const globalMemberRole = await testDb.getGlobalMemberRole();
owner = await testDb.createUserShell(globalOwnerRole);
member = await testDb.createUserShell(globalMemberRole);
const authAgent = utils.createAuthAgent(app);
authOwnerAgent = authAgent(owner);
authMemberAgent = authAgent(member);
authOwnerAgent = testServer.authAgentFor(owner);
authMemberAgent = testServer.authAgentFor(member);
config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('license.serverUrl', MOCK_SERVER_URL);
config.set('license.autoRenewEnabled', true);
config.set('license.autoRenewOffset', MOCK_RENEW_OFFSET);
@@ -36,10 +34,6 @@ afterEach(async () => {
await testDb.truncate(['Settings']);
});
afterAll(async () => {
await testDb.terminate();
});
describe('GET /license', () => {
test('should return license information to the instance owner', async () => {
// No license defined so we just expect the result to be the defaults

View File

@@ -1,4 +1,3 @@
import type { Application } from 'express';
import type { SuperAgentTest } from 'supertest';
import { IsNull } from 'typeorm';
import validator from 'validator';
@@ -14,31 +13,22 @@ import {
randomValidPassword,
} from './shared/random';
import * as testDb from './shared/testDb';
import type { AuthAgent } from './shared/types';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
const testServer = utils.setupTestServer({ endpointGroups: ['me'] });
let app: Application;
let globalOwnerRole: Role;
let globalMemberRole: Role;
let authAgent: AuthAgent;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['me'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
authAgent = utils.createAuthAgent(app);
});
beforeEach(async () => {
await testDb.truncate(['User']);
});
afterAll(async () => {
await testDb.terminate();
});
describe('Owner shell', () => {
let ownerShell: User;
let authOwnerShellAgent: SuperAgentTest;
@@ -46,7 +36,7 @@ describe('Owner shell', () => {
beforeEach(async () => {
ownerShell = await testDb.createUserShell(globalOwnerRole);
await testDb.addApiKey(ownerShell);
authOwnerShellAgent = authAgent(ownerShell);
authOwnerShellAgent = testServer.authAgentFor(ownerShell);
});
test('PATCH /me should succeed with valid inputs', async () => {
@@ -108,22 +98,20 @@ describe('Owner shell', () => {
const validPayloads = [validPasswordPayload, ...INVALID_PASSWORD_PAYLOADS];
await Promise.all(
validPayloads.map(async (payload) => {
const response = await authOwnerShellAgent.patch('/me/password').send(payload);
expect([400, 500].includes(response.statusCode)).toBe(true);
for (const payload of validPayloads) {
const response = await authOwnerShellAgent.patch('/me/password').send(payload);
expect([400, 500].includes(response.statusCode)).toBe(true);
const storedMember = await Db.collections.User.findOneByOrFail({});
const storedMember = await Db.collections.User.findOneByOrFail({});
if (payload.newPassword) {
expect(storedMember.password).not.toBe(payload.newPassword);
}
if (payload.newPassword) {
expect(storedMember.password).not.toBe(payload.newPassword);
}
if (payload.currentPassword) {
expect(storedMember.password).not.toBe(payload.currentPassword);
}
}),
);
if (payload.currentPassword) {
expect(storedMember.password).not.toBe(payload.currentPassword);
}
}
const storedOwnerShell = await Db.collections.User.findOneByOrFail({});
expect(storedOwnerShell.password).toBeNull();
@@ -191,7 +179,7 @@ describe('Member', () => {
globalRole: globalMemberRole,
apiKey: randomApiKey(),
});
authMemberAgent = authAgent(member);
authMemberAgent = testServer.authAgentFor(member);
await utils.setInstanceOwnerSetUp(true);
});
@@ -295,7 +283,7 @@ describe('Member', () => {
});
test('POST /me/api-key should create an api key', async () => {
const response = await authAgent(member).post('/me/api-key');
const response = await testServer.authAgentFor(member).post('/me/api-key');
expect(response.statusCode).toBe(200);
expect(response.body.data.apiKey).toBeDefined();
@@ -307,14 +295,14 @@ describe('Member', () => {
});
test('GET /me/api-key should fetch the api key', async () => {
const response = await authAgent(member).get('/me/api-key');
const response = await testServer.authAgentFor(member).get('/me/api-key');
expect(response.statusCode).toBe(200);
expect(response.body.data.apiKey).toEqual(member.apiKey);
});
test('DELETE /me/api-key should fetch the api key', async () => {
const response = await authAgent(member).delete('/me/api-key');
const response = await testServer.authAgentFor(member).delete('/me/api-key');
expect(response.statusCode).toBe(200);
@@ -331,7 +319,7 @@ describe('Owner', () => {
test('PATCH /me should succeed with valid inputs', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = authAgent(owner);
const authOwnerAgent = testServer.authAgentFor(owner);
for (const validPayload of VALID_PATCH_ME_PAYLOADS) {
const response = await authOwnerAgent.patch('/me').send(validPayload);

View File

@@ -16,7 +16,7 @@ import type { InstalledNodes } from '@db/entities/InstalledNodes';
import { NodeTypes } from '@/NodeTypes';
import { Push } from '@/push';
import { COMMUNITY_PACKAGE_VERSION } from './shared/constants';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
import * as testDb from './shared/testDb';
const mockLoadNodesAndCredentials = utils.mockInstance(LoadNodesAndCredentials);
@@ -44,17 +44,15 @@ jest.mock('@/CommunityNodes/packageModel', () => {
const mockedEmptyPackage = mocked(utils.emptyPackage);
const testServer = utils.setupTestServer({ endpointGroups: ['nodes'] });
let ownerShell: User;
let authOwnerShellAgent: SuperAgentTest;
beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['nodes'] });
const globalOwnerRole = await testDb.getGlobalOwnerRole();
ownerShell = await testDb.createUserShell(globalOwnerRole);
authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell);
await utils.initConfigFile();
authOwnerShellAgent = testServer.authAgentFor(ownerShell);
});
beforeEach(async () => {
@@ -64,10 +62,6 @@ beforeEach(async () => {
mocked(findInstalledPackage).mockReset();
});
afterAll(async () => {
await testDb.terminate();
});
describe('GET /nodes', () => {
test('should respond 200 if no nodes are installed', async () => {
const {

View File

@@ -1,4 +1,3 @@
import type { Application } from 'express';
import validator from 'validator';
import type { SuperAgentTest } from 'supertest';
@@ -13,32 +12,28 @@ import {
randomValidPassword,
} from './shared/random';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
const testServer = utils.setupTestServer({ endpointGroups: ['owner'] });
let app: Application;
let globalOwnerRole: Role;
let ownerShell: User;
let authOwnerShellAgent: SuperAgentTest;
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['owner'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
});
beforeEach(async () => {
config.set('userManagement.isInstanceOwnerSetUp', false);
ownerShell = await testDb.createUserShell(globalOwnerRole);
authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell);
authOwnerShellAgent = testServer.authAgentFor(ownerShell);
config.set('userManagement.isInstanceOwnerSetUp', false);
});
afterEach(async () => {
await testDb.truncate(['User']);
});
afterAll(async () => {
await testDb.terminate();
});
describe('POST /owner/setup', () => {
test('should create owner and enable isInstanceOwnerSetUp', async () => {
const newOwnerData = {
@@ -159,13 +154,9 @@ describe('POST /owner/setup', () => {
];
test('should fail with invalid inputs', async () => {
const authOwnerAgent = authOwnerShellAgent;
await Promise.all(
INVALID_POST_OWNER_PAYLOADS.map(async (invalidPayload) => {
const response = await authOwnerAgent.post('/owner/setup').send(invalidPayload);
expect(response.statusCode).toBe(400);
}),
);
for (const invalidPayload of INVALID_POST_OWNER_PAYLOADS) {
const response = await authOwnerShellAgent.post('/owner/setup').send(invalidPayload);
expect(response.statusCode).toBe(400);
}
});
});

View File

@@ -1,4 +1,3 @@
import type { SuperAgentTest } from 'supertest';
import { v4 as uuid } from 'uuid';
import { compare } from 'bcryptjs';
@@ -6,7 +5,7 @@ import * as Db from '@/Db';
import config from '@/config';
import type { Role } from '@db/entities/Role';
import type { User } from '@db/entities/User';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
import {
randomEmail,
randomInvalidPassword,
@@ -22,30 +21,21 @@ jest.mock('@/UserManagement/email/NodeMailer');
let globalOwnerRole: Role;
let globalMemberRole: Role;
let owner: User;
let authlessAgent: SuperAgentTest;
const externalHooks = utils.mockInstance(ExternalHooks);
const testServer = utils.setupTestServer({ endpointGroups: ['passwordReset'] });
beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['passwordReset'] });
globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole();
authlessAgent = utils.createAgent(app);
});
beforeEach(async () => {
await testDb.truncate(['User']);
owner = await testDb.createUser({ globalRole: globalOwnerRole });
config.set('userManagement.isInstanceOwnerSetUp', true);
externalHooks.run.mockReset();
});
afterAll(async () => {
await testDb.terminate();
});
describe('POST /forgot-password', () => {
test('should send password reset email', async () => {
const member = await testDb.createUser({
@@ -57,7 +47,7 @@ describe('POST /forgot-password', () => {
await Promise.all(
[{ email: owner.email }, { email: member.email.toUpperCase() }].map(async (payload) => {
const response = await authlessAgent.post('/forgot-password').send(payload);
const response = await testServer.authlessAgent.post('/forgot-password').send(payload);
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({});
@@ -72,7 +62,10 @@ describe('POST /forgot-password', () => {
test('should fail if emailing is not set up', async () => {
config.set('userManagement.emails.mode', '');
await authlessAgent.post('/forgot-password').send({ email: owner.email }).expect(500);
await testServer.authlessAgent
.post('/forgot-password')
.send({ email: owner.email })
.expect(500);
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
expect(storedOwner.resetPasswordToken).toBeNull();
@@ -86,7 +79,10 @@ describe('POST /forgot-password', () => {
globalRole: globalMemberRole,
});
await authlessAgent.post('/forgot-password').send({ email: member.email }).expect(403);
await testServer.authlessAgent
.post('/forgot-password')
.send({ email: member.email })
.expect(403);
const storedOwner = await Db.collections.User.findOneByOrFail({ email: member.email });
expect(storedOwner.resetPasswordToken).toBeNull();
@@ -97,7 +93,9 @@ describe('POST /forgot-password', () => {
await setCurrentAuthenticationMethod('saml');
config.set('userManagement.emails.mode', 'smtp');
const response = await authlessAgent.post('/forgot-password').send({ email: owner.email });
const response = await testServer.authlessAgent
.post('/forgot-password')
.send({ email: owner.email });
expect(response.statusCode).toBe(200);
expect(response.body).toEqual({});
@@ -118,21 +116,21 @@ describe('POST /forgot-password', () => {
[{ email: randomName() }],
];
await Promise.all(
invalidPayloads.map(async (invalidPayload) => {
const response = await authlessAgent.post('/forgot-password').send(invalidPayload);
expect(response.statusCode).toBe(400);
for (const invalidPayload of invalidPayloads) {
const response = await testServer.authlessAgent.post('/forgot-password').send(invalidPayload);
expect(response.statusCode).toBe(400);
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
expect(storedOwner.resetPasswordToken).toBeNull();
}),
);
const storedOwner = await Db.collections.User.findOneByOrFail({ email: owner.email });
expect(storedOwner.resetPasswordToken).toBeNull();
}
});
test('should fail if user is not found', async () => {
config.set('userManagement.emails.mode', 'smtp');
const response = await authlessAgent.post('/forgot-password').send({ email: randomEmail() });
const response = await testServer.authlessAgent
.post('/forgot-password')
.send({ email: randomEmail() });
expect(response.statusCode).toBe(200); // expect 200 to remain vague
});
@@ -152,7 +150,7 @@ describe('GET /resolve-password-token', () => {
resetPasswordTokenExpiration,
});
const response = await authlessAgent
const response = await testServer.authlessAgent
.get('/resolve-password-token')
.query({ userId: owner.id, token: resetPasswordToken });
@@ -160,9 +158,13 @@ describe('GET /resolve-password-token', () => {
});
test('should fail with invalid inputs', async () => {
const first = await authlessAgent.get('/resolve-password-token').query({ token: uuid() });
const first = await testServer.authlessAgent
.get('/resolve-password-token')
.query({ token: uuid() });
const second = await authlessAgent.get('/resolve-password-token').query({ userId: owner.id });
const second = await testServer.authlessAgent
.get('/resolve-password-token')
.query({ userId: owner.id });
for (const response of [first, second]) {
expect(response.statusCode).toBe(400);
@@ -170,7 +172,7 @@ describe('GET /resolve-password-token', () => {
});
test('should fail if user is not found', async () => {
const response = await authlessAgent
const response = await testServer.authlessAgent
.get('/resolve-password-token')
.query({ userId: owner.id, token: uuid() });
@@ -186,7 +188,7 @@ describe('GET /resolve-password-token', () => {
resetPasswordTokenExpiration,
});
const response = await authlessAgent
const response = await testServer.authlessAgent
.get('/resolve-password-token')
.query({ userId: owner.id, token: resetPasswordToken });
@@ -206,7 +208,7 @@ describe('POST /change-password', () => {
resetPasswordTokenExpiration,
});
const response = await authlessAgent.post('/change-password').send({
const response = await testServer.authlessAgent.post('/change-password').send({
token: resetPasswordToken,
userId: owner.id,
password: passwordToStore,
@@ -258,15 +260,15 @@ describe('POST /change-password', () => {
},
];
await Promise.all(
invalidPayloads.map(async (invalidPayload) => {
const response = await authlessAgent.post('/change-password').query(invalidPayload);
expect(response.statusCode).toBe(400);
for (const invalidPayload of invalidPayloads) {
const response = await testServer.authlessAgent
.post('/change-password')
.query(invalidPayload);
expect(response.statusCode).toBe(400);
const { password: storedPassword } = await Db.collections.User.findOneByOrFail({});
expect(owner.password).toBe(storedPassword);
}),
);
const { password: storedPassword } = await Db.collections.User.findOneByOrFail({});
expect(owner.password).toBe(storedPassword);
}
});
test('should fail when token has expired', async () => {
@@ -277,7 +279,7 @@ describe('POST /change-password', () => {
resetPasswordTokenExpiration,
});
const response = await authlessAgent.post('/change-password').send({
const response = await testServer.authlessAgent.post('/change-password').send({
token: resetPasswordToken,
userId: owner.id,
password: passwordToStore,

View File

@@ -4,8 +4,9 @@ import * as Db from '@/Db';
import type { Role } from '@db/entities/Role';
import type { User } from '@db/entities/User';
import { RESPONSE_ERROR_MESSAGES } from '@/constants';
import { randomApiKey, randomName, randomString } from '../shared/random';
import * as utils from '../shared/utils';
import * as utils from '../shared/utils/';
import type { CredentialPayload, SaveCredentialFunction } from '../shared/types';
import * as testDb from '../shared/testDb';
@@ -18,14 +19,11 @@ let authMemberAgent: SuperAgentTest;
let saveCredential: SaveCredentialFunction;
beforeAll(async () => {
const app = await utils.initTestServer({
endpointGroups: ['publicApi'],
applyAuth: false,
enablePublicAPI: true,
});
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
await utils.initConfigFile();
beforeAll(async () => {
// TODO: mock encryption key
await utils.initEncryptionKey();
const [globalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
await testDb.getAllRoles();
@@ -36,18 +34,8 @@ beforeAll(async () => {
owner = await testDb.addApiKey(await testDb.createUserShell(globalOwnerRole));
member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
});
authMemberAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: member,
});
authOwnerAgent = testServer.publicApiAgentFor(owner);
authMemberAgent = testServer.publicApiAgentFor(member);
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
@@ -58,10 +46,6 @@ beforeEach(async () => {
await testDb.truncate(['SharedCredentials', 'Credentials']);
});
afterAll(async () => {
await testDb.terminate();
});
describe('POST /credentials', () => {
test('should create credentials', async () => {
const payload = {

View File

@@ -1,14 +1,11 @@
import type { Application } from 'express';
import type { SuperAgentTest } from 'supertest';
import config from '@/config';
import type { User } from '@db/entities/User';
import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import { randomApiKey } from '../shared/random';
import * as utils from '../shared/utils';
import * as utils from '../shared/utils/';
import * as testDb from '../shared/testDb';
let app: Application;
let owner: User;
let user1: User;
let user2: User;
@@ -17,19 +14,16 @@ let authUser1Agent: SuperAgentTest;
let authUser2Agent: SuperAgentTest;
let workflowRunner: ActiveWorkflowRunner;
beforeAll(async () => {
app = await utils.initTestServer({
endpointGroups: ['publicApi'],
applyAuth: false,
enablePublicAPI: true,
});
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
beforeAll(async () => {
const globalOwnerRole = await testDb.getGlobalOwnerRole();
const globalUserRole = await testDb.getGlobalMemberRole();
owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
user1 = await testDb.createUser({ globalRole: globalUserRole, apiKey: randomApiKey() });
user2 = await testDb.createUser({ globalRole: globalUserRole, apiKey: randomApiKey() });
// TODO: mock BinaryDataManager instead
await utils.initBinaryManager();
await utils.initNodeTypes();
@@ -46,38 +40,15 @@ beforeEach(async () => {
'Settings',
]);
authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
auth: true,
user: owner,
version: 1,
});
authUser1Agent = utils.createAgent(app, {
apiPath: 'public',
auth: true,
user: user1,
version: 1,
});
authUser2Agent = utils.createAgent(app, {
apiPath: 'public',
auth: true,
user: user2,
version: 1,
});
config.set('userManagement.isInstanceOwnerSetUp', true);
authOwnerAgent = testServer.publicApiAgentFor(owner);
authUser1Agent = testServer.publicApiAgentFor(user1);
authUser2Agent = testServer.publicApiAgentFor(user2);
});
afterEach(async () => {
await workflowRunner?.removeAll();
});
afterAll(async () => {
await testDb.terminate();
});
const testWithAPIKey =
(method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => {
void authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey });

View File

@@ -1,124 +1,141 @@
import type express from 'express';
import type { SuperAgentTest } from 'supertest';
import validator from 'validator';
import { v4 as uuid } from 'uuid';
import config from '@/config';
import type { Role } from '@/databases/entities/Role';
import { randomApiKey } from '../shared/random';
import * as utils from '../shared/utils';
import * as testDb from '../shared/testDb';
import type { Role } from '@db/entities/Role';
import { License } from '@/License';
let app: express.Application;
import { randomApiKey } from '../shared/random';
import * as utils from '../shared/utils/';
import * as testDb from '../shared/testDb';
utils.mockInstance(License, {
getUsersLimit: jest.fn().mockReturnValue(-1),
});
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
let globalOwnerRole: Role;
let globalMemberRole: Role;
const licenseLike = {
getUsersLimit: jest.fn().mockReturnValue(-1),
};
utils.mockInstance(License, licenseLike);
beforeAll(async () => {
app = await utils.initTestServer({
endpointGroups: ['publicApi'],
applyAuth: false,
enablePublicAPI: true,
});
await testDb.init();
const [fetchedGlobalOwnerRole, fetchedGlobalMemberRole] = await testDb.getAllRoles();
globalOwnerRole = fetchedGlobalOwnerRole;
globalMemberRole = fetchedGlobalMemberRole;
[globalOwnerRole, globalMemberRole] = await testDb.getAllRoles();
});
beforeEach(async () => {
// do not combine calls - shared tables must be cleared first and separately
await testDb.truncate(['SharedCredentials', 'SharedWorkflow']);
await testDb.truncate(['User', 'Workflow', 'Credentials']);
config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('userManagement.emails.mode', 'smtp');
});
afterAll(async () => {
await testDb.terminate();
await testDb.truncate(['SharedCredentials', 'SharedWorkflow', 'Workflow', 'Credentials', 'User']);
});
describe('With license unlimited quota:users', () => {
test('GET /users should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
describe('GET /users', () => {
test('should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = testServer.publicApiAgentFor(owner);
await authOwnerAgent.get('/users').expect(401);
});
await testDb.createUser();
test('should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
owner.apiKey = 'invalid-key';
const authOwnerAgent = testServer.publicApiAgentFor(owner);
await authOwnerAgent.get('/users').expect(401);
});
const response = await authOwnerAgent.get('/users');
test('should fail due to member trying to access owner only endpoint', async () => {
const member = await testDb.createUser({ apiKey: randomApiKey() });
const authMemberAgent = testServer.publicApiAgentFor(member);
await authMemberAgent.get('/users').expect(403);
});
expect(response.statusCode).toBe(401);
test('should return all users', async () => {
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
const authOwnerAgent = testServer.publicApiAgentFor(owner);
await testDb.createUser();
const response = await authOwnerAgent.get('/users').expect(200);
expect(response.body.data.length).toBe(2);
expect(response.body.nextCursor).toBeNull();
for (const user of response.body.data) {
const {
id,
email,
firstName,
lastName,
personalizationAnswers,
globalRole,
password,
resetPasswordToken,
isPending,
createdAt,
updatedAt,
} = user;
expect(validator.isUUID(id)).toBe(true);
expect(email).toBeDefined();
expect(firstName).toBeDefined();
expect(lastName).toBeDefined();
expect(personalizationAnswers).toBeUndefined();
expect(password).toBeUndefined();
expect(resetPasswordToken).toBeUndefined();
expect(isPending).toBe(false);
expect(globalRole).toBeUndefined();
expect(createdAt).toBeDefined();
expect(updatedAt).toBeDefined();
}
});
});
test('GET /users should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = null;
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
describe('GET /users/:id', () => {
test('should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = testServer.publicApiAgentFor(owner);
await authOwnerAgent.get(`/users/${owner.id}`).expect(401);
});
const response = await authOwnerAgent.get('/users');
expect(response.statusCode).toBe(401);
});
test('GET /users should fail due to member trying to access owner only endpoint', async () => {
const member = await testDb.createUser({ apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: member,
test('should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
owner.apiKey = 'invalid-key';
const authOwnerAgent = testServer.publicApiAgentFor(owner);
await authOwnerAgent.get(`/users/${owner.id}`).expect(401);
});
const response = await authOwnerAgent.get('/users');
expect(response.statusCode).toBe(403);
});
test('GET /users should return all users', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
test('should fail due to member trying to access owner only endpoint', async () => {
const member = await testDb.createUser({ apiKey: randomApiKey() });
const authMemberAgent = testServer.publicApiAgentFor(member);
await authMemberAgent.get(`/users/${member.id}`).expect(403);
});
test('should return 404 for non-existing id ', async () => {
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
const authOwnerAgent = testServer.publicApiAgentFor(owner);
await authOwnerAgent.get(`/users/${uuid()}`).expect(404);
});
await testDb.createUser();
test('should return a pending user', async () => {
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
const response = await authOwnerAgent.get('/users');
const { id: memberId } = await testDb.createUserShell(globalMemberRole);
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2);
expect(response.body.nextCursor).toBeNull();
const authOwnerAgent = testServer.publicApiAgentFor(owner);
const response = await authOwnerAgent.get(`/users/${memberId}`).expect(200);
for (const user of response.body.data) {
const {
id,
email,
@@ -131,7 +148,55 @@ describe('With license unlimited quota:users', () => {
isPending,
createdAt,
updatedAt,
} = user;
} = response.body;
expect(validator.isUUID(id)).toBe(true);
expect(email).toBeDefined();
expect(firstName).toBeDefined();
expect(lastName).toBeDefined();
expect(personalizationAnswers).toBeUndefined();
expect(password).toBeUndefined();
expect(resetPasswordToken).toBeUndefined();
expect(globalRole).toBeUndefined();
expect(createdAt).toBeDefined();
expect(isPending).toBeDefined();
expect(isPending).toBeTruthy();
expect(updatedAt).toBeDefined();
});
});
describe('GET /users/:email', () => {
test('with non-existing email should return 404', async () => {
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
const authOwnerAgent = testServer.publicApiAgentFor(owner);
await authOwnerAgent.get('/users/jhondoe@gmail.com').expect(404);
});
test('should return a user', async () => {
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
const authOwnerAgent = testServer.publicApiAgentFor(owner);
const response = await authOwnerAgent.get(`/users/${owner.email}`).expect(200);
const {
id,
email,
firstName,
lastName,
personalizationAnswers,
globalRole,
password,
resetPasswordToken,
isPending,
createdAt,
updatedAt,
} = response.body;
expect(validator.isUUID(id)).toBe(true);
expect(email).toBeDefined();
@@ -144,200 +209,28 @@ describe('With license unlimited quota:users', () => {
expect(globalRole).toBeUndefined();
expect(createdAt).toBeDefined();
expect(updatedAt).toBeDefined();
}
});
test('GET /users/:identifier should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
});
await testDb.createUser();
const response = await authOwnerAgent.get(`/users/${owner.id}`);
expect(response.statusCode).toBe(401);
});
test('GET /users/:identifier should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = null;
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
});
const response = await authOwnerAgent.get(`/users/${owner.id}`);
expect(response.statusCode).toBe(401);
});
test('GET /users/:identifier should fail due to member trying to access owner only endpoint', async () => {
const member = await testDb.createUser({ apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: member,
});
const response = await authOwnerAgent.get(`/users/${member.id}`);
expect(response.statusCode).toBe(403);
});
test('GET /users/:email with non-existing email should return 404', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
});
const response = await authOwnerAgent.get('/users/jhondoe@gmail.com');
expect(response.statusCode).toBe(404);
});
test('GET /users/:id with non-existing id should return 404', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
});
const response = await authOwnerAgent.get(`/users/${uuid()}`);
expect(response.statusCode).toBe(404);
});
test('GET /users/:email should return a user', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
});
const response = await authOwnerAgent.get(`/users/${owner.email}`);
expect(response.statusCode).toBe(200);
const {
id,
email,
firstName,
lastName,
personalizationAnswers,
globalRole,
password,
resetPasswordToken,
isPending,
createdAt,
updatedAt,
} = response.body;
expect(validator.isUUID(id)).toBe(true);
expect(email).toBeDefined();
expect(firstName).toBeDefined();
expect(lastName).toBeDefined();
expect(personalizationAnswers).toBeUndefined();
expect(password).toBeUndefined();
expect(resetPasswordToken).toBeUndefined();
expect(isPending).toBe(false);
expect(globalRole).toBeUndefined();
expect(createdAt).toBeDefined();
expect(updatedAt).toBeDefined();
});
test('GET /users/:id should return a pending user', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const { id: memberId } = await testDb.createUserShell(globalMemberRole);
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: owner,
});
const response = await authOwnerAgent.get(`/users/${memberId}`);
expect(response.statusCode).toBe(200);
const {
id,
email,
firstName,
lastName,
personalizationAnswers,
globalRole,
password,
resetPasswordToken,
isPending,
createdAt,
updatedAt,
} = response.body;
expect(validator.isUUID(id)).toBe(true);
expect(email).toBeDefined();
expect(firstName).toBeDefined();
expect(lastName).toBeDefined();
expect(personalizationAnswers).toBeUndefined();
expect(password).toBeUndefined();
expect(resetPasswordToken).toBeUndefined();
expect(globalRole).toBeUndefined();
expect(createdAt).toBeDefined();
expect(isPending).toBeDefined();
expect(isPending).toBeTruthy();
expect(updatedAt).toBeDefined();
});
});
describe('With license without quota:users', () => {
let authOwnerAgent: SuperAgentTest;
beforeEach(async () => {
utils.mockInstance(License, { getUsersLimit: jest.fn().mockReturnValue(null) });
const owner = await testDb.createUser({
globalRole: globalOwnerRole,
apiKey: randomApiKey(),
});
authOwnerAgent = testServer.publicApiAgentFor(owner);
});
test('GET /users should fail due to invalid license', async () => {
const member = await testDb.createUser({ apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: member,
});
const response = await authOwnerAgent.get('/users');
expect(response.statusCode).toBe(403);
await authOwnerAgent.get('/users').expect(403);
});
test('GET /users/:id should fail due to invalid license', async () => {
const member = await testDb.createUser({ apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
version: 1,
auth: true,
user: member,
});
const response = await authOwnerAgent.get(`/users/${member.id}`);
expect(response.statusCode).toBe(403);
await authOwnerAgent.get(`/users/${uuid()}`).expect(403);
});
});

View File

@@ -1,18 +1,14 @@
import type { Application } from 'express';
import type { SuperAgentTest } from 'supertest';
import * as Db from '@/Db';
import config from '@/config';
import type { Role } from '@db/entities/Role';
import type { TagEntity } from '@db/entities/TagEntity';
import type { User } from '@db/entities/User';
import type { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import { randomApiKey } from '../shared/random';
import * as utils from '../shared/utils';
import * as utils from '../shared/utils/';
import * as testDb from '../shared/testDb';
// import { generateNanoId } from '@/databases/utils/generators';
let app: Application;
let workflowOwnerRole: Role;
let owner: User;
let member: User;
@@ -20,13 +16,9 @@ let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
let workflowRunner: ActiveWorkflowRunner;
beforeAll(async () => {
app = await utils.initTestServer({
endpointGroups: ['publicApi'],
applyAuth: false,
enablePublicAPI: true,
});
const testServer = utils.setupTestServer({ endpointGroups: ['publicApi'] });
beforeAll(async () => {
const [globalOwnerRole, globalMemberRole, fetchedWorkflowOwnerRole] = await testDb.getAllRoles();
workflowOwnerRole = fetchedWorkflowOwnerRole;
@@ -41,7 +33,7 @@ beforeAll(async () => {
apiKey: randomApiKey(),
});
await utils.initConfigFile();
await utils.initEncryptionKey();
await utils.initNodeTypes();
workflowRunner = await utils.initActiveWorkflowRunner();
});
@@ -49,31 +41,14 @@ beforeAll(async () => {
beforeEach(async () => {
await testDb.truncate(['SharedCredentials', 'SharedWorkflow', 'Tag', 'Workflow', 'Credentials']);
authOwnerAgent = utils.createAgent(app, {
apiPath: 'public',
auth: true,
user: owner,
version: 1,
});
authMemberAgent = utils.createAgent(app, {
apiPath: 'public',
auth: true,
user: member,
version: 1,
});
config.set('userManagement.isInstanceOwnerSetUp', true);
authOwnerAgent = testServer.publicApiAgentFor(owner);
authMemberAgent = testServer.publicApiAgentFor(member);
});
afterEach(async () => {
await workflowRunner?.removeAll();
});
afterAll(async () => {
await testDb.terminate();
});
const testWithAPIKey =
(method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => {
void authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey });

View File

@@ -1,23 +1,21 @@
import { Container } from 'typedi';
import type { SuperAgentTest } from 'supertest';
import type { AuthenticationMethod } from 'n8n-workflow';
import type { User } from '@db/entities/User';
import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers';
import { getCurrentAuthenticationMethod, setCurrentAuthenticationMethod } from '@/sso/ssoHelpers';
import { SamlUrls } from '@/sso/saml/constants';
import { License } from '@/License';
import { randomEmail, randomName, randomValidPassword } from '../shared/random';
import * as testDb from '../shared/testDb';
import * as utils from '../shared/utils';
import { sampleConfig } from './sampleMetadata';
import { InternalHooks } from '@/InternalHooks';
import { SamlService } from '@/sso/saml/saml.service.ee';
import config from '@/config';
import type { SamlUserAttributes } from '@/sso/saml/types/samlUserAttributes';
import type { AuthenticationMethod } from 'n8n-workflow';
import { randomEmail, randomName, randomValidPassword } from '../shared/random';
import * as testDb from '../shared/testDb';
import * as utils from '../shared/utils/';
import { sampleConfig } from './sampleMetadata';
let someUser: User;
let owner: User;
let noAuthMemberAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
let authOwnerAgent: SuperAgentTest;
@@ -25,20 +23,16 @@ async function enableSaml(enable: boolean) {
await setSamlLoginEnabled(enable);
}
beforeAll(async () => {
Container.get(License).isSamlEnabled = () => true;
const app = await utils.initTestServer({ endpointGroups: ['me', 'saml'] });
owner = await testDb.createOwner();
someUser = await testDb.createUser();
authOwnerAgent = utils.createAuthAgent(app)(owner);
authMemberAgent = utils.createAgent(app, { auth: true, user: someUser });
noAuthMemberAgent = utils.createAgent(app, { auth: false, user: someUser });
config.set('userManagement.isInstanceOwnerSetUp', true);
const testServer = utils.setupTestServer({
endpointGroups: ['me', 'saml'],
enabledFeatures: ['feat:saml'],
});
afterAll(async () => {
await testDb.terminate();
beforeAll(async () => {
owner = await testDb.createOwner();
someUser = await testDb.createUser();
authOwnerAgent = testServer.authAgentFor(owner);
authMemberAgent = testServer.authAgentFor(someUser);
});
describe('Instance owner', () => {
@@ -184,7 +178,7 @@ describe('Check endpoint permissions', () => {
});
test(`should be able to access GET ${SamlUrls.initSSO}`, async () => {
const response = await authOwnerAgent.get(`/sso/saml${SamlUrls.initSSO}`).expect(200);
await authOwnerAgent.get(`/sso/saml${SamlUrls.initSSO}`).expect(200);
});
test(`should be able to access GET ${SamlUrls.configTest}`, async () => {
@@ -223,7 +217,7 @@ describe('Check endpoint permissions', () => {
});
test(`should be able to access GET ${SamlUrls.initSSO}`, async () => {
const response = await authMemberAgent.get(`/sso/saml${SamlUrls.initSSO}`).expect(200);
await authMemberAgent.get(`/sso/saml${SamlUrls.initSSO}`).expect(200);
});
test(`should NOT be able to access GET ${SamlUrls.configTest}`, async () => {
@@ -232,41 +226,43 @@ describe('Check endpoint permissions', () => {
});
describe('Non-Authenticated User', () => {
test(`should be able to access ${SamlUrls.metadata}`, async () => {
await noAuthMemberAgent.get(`/sso/saml${SamlUrls.metadata}`).expect(200);
await testServer.authlessAgent.get(`/sso/saml${SamlUrls.metadata}`).expect(200);
});
test(`should NOT be able to access GET ${SamlUrls.config}`, async () => {
await noAuthMemberAgent.get(`/sso/saml${SamlUrls.config}`).expect(401);
await testServer.authlessAgent.get(`/sso/saml${SamlUrls.config}`).expect(401);
});
test(`should NOT be able to access POST ${SamlUrls.config}`, async () => {
await noAuthMemberAgent.post(`/sso/saml${SamlUrls.config}`).expect(401);
await testServer.authlessAgent.post(`/sso/saml${SamlUrls.config}`).expect(401);
});
test(`should NOT be able to access POST ${SamlUrls.configToggleEnabled}`, async () => {
await noAuthMemberAgent.post(`/sso/saml${SamlUrls.configToggleEnabled}`).expect(401);
await testServer.authlessAgent.post(`/sso/saml${SamlUrls.configToggleEnabled}`).expect(401);
});
test(`should be able to access GET ${SamlUrls.acs}`, async () => {
// Note that 401 here is coming from the missing SAML object,
// not from not being able to access the endpoint, so this is expected!
const response = await noAuthMemberAgent.get(`/sso/saml${SamlUrls.acs}`).expect(401);
const response = await testServer.authlessAgent.get(`/sso/saml${SamlUrls.acs}`).expect(401);
expect(response.text).toContain('SAML Authentication failed');
});
test(`should be able to access POST ${SamlUrls.acs}`, async () => {
// Note that 401 here is coming from the missing SAML object,
// not from not being able to access the endpoint, so this is expected!
const response = await noAuthMemberAgent.post(`/sso/saml${SamlUrls.acs}`).expect(401);
const response = await testServer.authlessAgent.post(`/sso/saml${SamlUrls.acs}`).expect(401);
expect(response.text).toContain('SAML Authentication failed');
});
test(`should be able to access GET ${SamlUrls.initSSO}`, async () => {
const response = await noAuthMemberAgent.get(`/sso/saml${SamlUrls.initSSO}`).expect(200);
const response = await testServer.authlessAgent
.get(`/sso/saml${SamlUrls.initSSO}`)
.expect(200);
});
test(`should NOT be able to access GET ${SamlUrls.configTest}`, async () => {
await noAuthMemberAgent.get(`/sso/saml${SamlUrls.configTest}`).expect(401);
await testServer.authlessAgent.get(`/sso/saml${SamlUrls.configTest}`).expect(401);
});
});
});

View File

@@ -24,27 +24,6 @@ export const LOGGED_OUT_RESPONSE_BODY = {
},
};
/**
* Routes requiring a valid `n8n-auth` cookie for a user, either owner or member.
*/
export const ROUTES_REQUIRING_AUTHENTICATION: Readonly<string[]> = [
'PATCH /me',
'PATCH /me/password',
'POST /me/survey',
'POST /owner/setup',
'GET /non-existent',
];
/**
* Routes requiring a valid `n8n-auth` cookie for an owner.
*/
export const ROUTES_REQUIRING_AUTHORIZATION: Readonly<string[]> = [
'POST /users',
'DELETE /users/123',
'POST /users/123/reinvite',
'POST /owner/setup',
];
export const COMMUNITY_PACKAGE_VERSION = {
CURRENT: '0.1.0',
UPDATED: '0.2.0',

View File

@@ -32,8 +32,8 @@ import type {
InstalledPackagePayload,
PostgresSchemaSection,
} from './types';
import type { ExecutionData } from '@/databases/entities/ExecutionData';
import { generateNanoId } from '@/databases/utils/generators';
import type { ExecutionData } from '@db/entities/ExecutionData';
import { generateNanoId } from '@db/utils/generators';
export type TestDBType = 'postgres' | 'mysql';

View File

@@ -1,17 +1,15 @@
import type { Application } from 'express';
import type { ICredentialDataDecryptedObject, ICredentialNodeAccess } from 'n8n-workflow';
import type { SuperAgentTest } from 'supertest';
import type { Server } from 'http';
import type { CredentialsEntity } from '@db/entities/CredentialsEntity';
import type { User } from '@db/entities/User';
import type { ICredentialsDb, IDatabaseCollections } from '@/Interfaces';
import type { BooleanLicenseFeature, ICredentialsDb, IDatabaseCollections } from '@/Interfaces';
export type CollectionName = keyof IDatabaseCollections;
export type ApiPath = 'internal' | 'public';
export type AuthAgent = (user: User) => SuperAgentTest;
type EndpointGroup =
export type EndpointGroup =
| 'me'
| 'users'
| 'auth'
@@ -28,6 +26,20 @@ type EndpointGroup =
| 'license'
| 'variables';
export interface SetupProps {
applyAuth?: boolean;
endpointGroups?: EndpointGroup[];
enabledFeatures?: BooleanLicenseFeature[];
}
export interface TestServer {
app: Application;
httpServer: Server;
authAgentFor: (user: User) => SuperAgentTest;
publicApiAgentFor: (user: User) => SuperAgentTest;
authlessAgent: SuperAgentTest;
}
export type CredentialPayload = {
name: string;
type: string;

View File

@@ -0,0 +1,29 @@
import { NODE_PACKAGE_PREFIX } from '@/constants';
import { InstalledPackages } from '@db/entities/InstalledPackages';
import { randomName } from '../random';
import { COMMUNITY_NODE_VERSION, COMMUNITY_PACKAGE_VERSION } from '../constants';
import type { InstalledNodePayload, InstalledPackagePayload } from '../types';
export function installedPackagePayload(): InstalledPackagePayload {
return {
packageName: NODE_PACKAGE_PREFIX + randomName(),
installedVersion: COMMUNITY_PACKAGE_VERSION.CURRENT,
};
}
export function installedNodePayload(packageName: string): InstalledNodePayload {
const nodeName = randomName();
return {
name: nodeName,
type: nodeName,
latestVersion: COMMUNITY_NODE_VERSION.CURRENT,
package: packageName,
};
}
export const emptyPackage = async () => {
const installedPackage = new InstalledPackages();
installedPackage.installedNodes = [];
return installedPackage;
};

View File

@@ -1,11 +1,7 @@
import { Container } from 'typedi';
import { randomBytes } from 'crypto';
import { existsSync } from 'fs';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import { CronJob } from 'cron';
import express from 'express';
import set from 'lodash/set';
import { BinaryDataManager, UserSettings } from 'n8n-core';
import type {
@@ -19,276 +15,20 @@ import type {
TriggerTime,
} from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow';
import { LoggerProxy, NodeHelpers, toCronExpression } from 'n8n-workflow';
import type superagent from 'superagent';
import request from 'supertest';
import { URL } from 'url';
import { mock } from 'jest-mock-extended';
import type { DeepPartial } from 'ts-essentials';
import { NodeHelpers, toCronExpression } from 'n8n-workflow';
import type request from 'supertest';
import { v4 as uuid } from 'uuid';
import config from '@/config';
import * as Db from '@/Db';
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
import { ExternalHooks } from '@/ExternalHooks';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import { workflowsController } from '@/workflows/workflows.controller';
import { AUTH_COOKIE_NAME, NODE_PACKAGE_PREFIX } from '@/constants';
import { credentialsController } from '@/credentials/credentials.controller';
import { InstalledPackages } from '@db/entities/InstalledPackages';
import type { User } from '@db/entities/User';
import { getLogger } from '@/Logger';
import { loadPublicApiVersions } from '@/PublicApi/';
import { issueJWT } from '@/auth/jwt';
import { UserManagementMailer } from '@/UserManagement/email/UserManagementMailer';
import {
AUTHLESS_ENDPOINTS,
COMMUNITY_NODE_VERSION,
COMMUNITY_PACKAGE_VERSION,
PUBLIC_API_REST_PATH_SEGMENT,
REST_PATH_SEGMENT,
} from './constants';
import { randomName } from './random';
import type {
ApiPath,
EndpointGroup,
InstalledNodePayload,
InstalledPackagePayload,
} from './types';
import { licenseController } from '@/license/license.controller';
import { registerController } from '@/decorators';
import {
AuthController,
LdapController,
MeController,
NodesController,
OwnerController,
PasswordResetController,
UsersController,
} from '@/controllers';
import { setupAuthMiddlewares } from '@/middlewares';
import * as testDb from '../shared/testDb';
import { AUTH_COOKIE_NAME } from '@/constants';
import { v4 as uuid } from 'uuid';
import { InternalHooks } from '@/InternalHooks';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { PostHogClient } from '@/posthog';
import { variablesController } from '@/environments/variables/variables.controller';
import { LdapManager } from '@/Ldap/LdapManager.ee';
import { handleLdapInit } from '@/Ldap/helpers';
import { Push } from '@/push';
import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers';
import { SamlService } from '@/sso/saml/saml.service.ee';
import { SamlController } from '@/sso/saml/routes/saml.controller.ee';
import { EventBusController } from '@/eventbus/eventBus.controller';
import { License } from '@/License';
import { SourceControlService } from '@/environments/sourceControl/sourceControl.service.ee';
import { SourceControlController } from '@/environments/sourceControl/sourceControl.controller.ee';
import { SourceControlPreferencesService } from '@/environments/sourceControl/sourceControlPreferences.service.ee';
export const mockInstance = <T>(
ctor: new (...args: any[]) => T,
data: DeepPartial<T> | undefined = undefined,
) => {
const instance = mock<T>(data);
Container.set(ctor, instance);
return instance;
};
/**
* Initialize a test server.
*/
export async function initTestServer({
applyAuth = true,
endpointGroups,
enablePublicAPI = false,
}: {
applyAuth?: boolean;
endpointGroups?: EndpointGroup[];
enablePublicAPI?: boolean;
}) {
await testDb.init();
const testServer = {
app: express(),
restEndpoint: REST_PATH_SEGMENT,
publicApiEndpoint: PUBLIC_API_REST_PATH_SEGMENT,
externalHooks: {},
};
const logger = getLogger();
LoggerProxy.init(logger);
// Mock all telemetry.
mockInstance(InternalHooks);
mockInstance(PostHogClient);
testServer.app.use(bodyParser.json());
testServer.app.use(bodyParser.urlencoded({ extended: true }));
testServer.app.use(cookieParser());
config.set('userManagement.jwtSecret', 'My JWT secret');
config.set('userManagement.isInstanceOwnerSetUp', false);
if (applyAuth) {
setupAuthMiddlewares(
testServer.app,
AUTHLESS_ENDPOINTS,
REST_PATH_SEGMENT,
Db.collections.User,
);
}
if (!endpointGroups) return testServer.app;
if (
endpointGroups.includes('credentials') ||
endpointGroups.includes('me') ||
endpointGroups.includes('users') ||
endpointGroups.includes('passwordReset')
) {
testServer.externalHooks = Container.get(ExternalHooks);
}
const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups);
if (routerEndpoints.length) {
const map: Record<string, express.Router | express.Router[] | any> = {
credentials: { controller: credentialsController, path: 'credentials' },
workflows: { controller: workflowsController, path: 'workflows' },
license: { controller: licenseController, path: 'license' },
variables: { controller: variablesController, path: 'variables' },
};
if (enablePublicAPI) {
const { apiRouters } = await loadPublicApiVersions(testServer.publicApiEndpoint);
map.publicApi = apiRouters;
}
for (const group of routerEndpoints) {
if (group === 'publicApi') {
testServer.app.use(...(map[group] as express.Router[]));
} else {
testServer.app.use(`/${testServer.restEndpoint}/${map[group].path}`, map[group].controller);
}
}
}
if (functionEndpoints.length) {
const externalHooks = Container.get(ExternalHooks);
const internalHooks = Container.get(InternalHooks);
const mailer = Container.get(UserManagementMailer);
const repositories = Db.collections;
for (const group of functionEndpoints) {
switch (group) {
case 'eventBus':
registerController(testServer.app, config, new EventBusController());
break;
case 'auth':
registerController(
testServer.app,
config,
new AuthController({ config, logger, internalHooks, repositories }),
);
break;
case 'ldap':
Container.get(License).isLdapEnabled = () => true;
await handleLdapInit();
const { service, sync } = LdapManager.getInstance();
registerController(
testServer.app,
config,
new LdapController(service, sync, internalHooks),
);
break;
case 'saml':
await setSamlLoginEnabled(true);
const samlService = Container.get(SamlService);
registerController(testServer.app, config, new SamlController(samlService));
break;
case 'sourceControl':
const sourceControlService = Container.get(SourceControlService);
const sourceControlPreferencesService = Container.get(SourceControlPreferencesService);
registerController(
testServer.app,
config,
new SourceControlController(sourceControlService, sourceControlPreferencesService),
);
break;
case 'nodes':
registerController(
testServer.app,
config,
new NodesController(
config,
Container.get(LoadNodesAndCredentials),
Container.get(Push),
internalHooks,
),
);
case 'me':
registerController(
testServer.app,
config,
new MeController({ logger, externalHooks, internalHooks, repositories }),
);
break;
case 'passwordReset':
registerController(
testServer.app,
config,
new PasswordResetController({
config,
logger,
externalHooks,
internalHooks,
mailer,
repositories,
}),
);
break;
case 'owner':
registerController(
testServer.app,
config,
new OwnerController({ config, logger, internalHooks, repositories }),
);
break;
case 'users':
registerController(
testServer.app,
config,
new UsersController({
config,
mailer,
externalHooks,
internalHooks,
repositories,
activeWorkflowRunner: Container.get(ActiveWorkflowRunner),
logger,
}),
);
}
}
}
return testServer.app;
}
/**
* Classify endpoint groups into `routerEndpoints` (newest, using `express.Router`),
* and `functionEndpoints` (legacy, namespaced inside a function).
*/
const classifyEndpointGroups = (endpointGroups: EndpointGroup[]) => {
const routerEndpoints: EndpointGroup[] = [];
const functionEndpoints: EndpointGroup[] = [];
const ROUTER_GROUP = ['credentials', 'workflows', 'publicApi', 'license', 'variables'];
endpointGroups.forEach((group) =>
(ROUTER_GROUP.includes(group) ? routerEndpoints : functionEndpoints).push(group),
);
return [routerEndpoints, functionEndpoints];
};
export { mockInstance } from './mocking';
export { setupTestServer } from './testServer';
// ----------------------------------
// initializers
@@ -658,7 +398,8 @@ export async function initBinaryManager() {
/**
* Initialize a user settings config file if non-existent.
*/
export async function initConfigFile() {
// TODO: this should be mocked
export async function initEncryptionKey() {
const settingsPath = UserSettings.getUserSettingsPath();
if (!existsSync(settingsPath)) {
@@ -667,64 +408,6 @@ export async function initConfigFile() {
}
}
// ----------------------------------
// request agent
// ----------------------------------
/**
* Create a request agent, optionally with an auth cookie.
*/
export function createAgent(
app: express.Application,
options?: { auth: boolean; user: User; apiPath?: ApiPath; version?: string | number },
) {
const agent = request.agent(app);
if (options?.apiPath === undefined || options?.apiPath === 'internal') {
void agent.use(prefix(REST_PATH_SEGMENT));
if (options?.auth && options?.user) {
try {
const { token } = issueJWT(options.user);
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
} catch {}
}
}
if (options?.apiPath === 'public') {
void agent.use(prefix(`${PUBLIC_API_REST_PATH_SEGMENT}/v${options?.version}`));
if (options?.auth && options?.user.apiKey) {
void agent.set({ 'X-N8N-API-KEY': options.user.apiKey });
}
}
return agent;
}
export function createAuthAgent(app: express.Application) {
return (user: User) => createAgent(app, { auth: true, user });
}
/**
* Plugin to prefix a path segment into a request URL pathname.
*
* Example: http://127.0.0.1:62100/me/password → http://127.0.0.1:62100/rest/me/password
*/
export function prefix(pathSegment: string) {
return async function (request: superagent.SuperAgentRequest) {
const url = new URL(request.url);
// enforce consistency at call sites
if (url.pathname[0] !== '/') {
throw new Error('Pathname must start with a forward slash');
}
url.pathname = pathSegment + url.pathname;
request.url = url.toString();
return request;
};
}
/**
* Extract the value (token) of the auth cookie in a response.
*/
@@ -769,29 +452,7 @@ export const setInstanceOwnerSetUp = async (value: boolean) => {
// community nodes
// ----------------------------------
export function installedPackagePayload(): InstalledPackagePayload {
return {
packageName: NODE_PACKAGE_PREFIX + randomName(),
installedVersion: COMMUNITY_PACKAGE_VERSION.CURRENT,
};
}
export function installedNodePayload(packageName: string): InstalledNodePayload {
const nodeName = randomName();
return {
name: nodeName,
type: nodeName,
latestVersion: COMMUNITY_NODE_VERSION.CURRENT,
package: packageName,
};
}
export const emptyPackage = async () => {
const installedPackage = new InstalledPackages();
installedPackage.installedNodes = [];
return installedPackage;
};
export * from './communityNodes';
// ----------------------------------
// workflow

View File

@@ -0,0 +1,12 @@
import { Container } from 'typedi';
import { mock } from 'jest-mock-extended';
import type { DeepPartial } from 'ts-essentials';
export const mockInstance = <T>(
ctor: new (...args: unknown[]) => T,
data: DeepPartial<T> | undefined = undefined,
) => {
const instance = mock<T>(data);
Container.set(ctor, instance);
return instance;
};

View File

@@ -0,0 +1,275 @@
import { Container } from 'typedi';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
import express from 'express';
import { LoggerProxy } from 'n8n-workflow';
import type superagent from 'superagent';
import request from 'supertest';
import { URL } from 'url';
import config from '@/config';
import * as Db from '@/Db';
import { ExternalHooks } from '@/ExternalHooks';
import { ActiveWorkflowRunner } from '@/ActiveWorkflowRunner';
import { workflowsController } from '@/workflows/workflows.controller';
import { AUTH_COOKIE_NAME } from '@/constants';
import { credentialsController } from '@/credentials/credentials.controller';
import type { User } from '@db/entities/User';
import { getLogger } from '@/Logger';
import { loadPublicApiVersions } from '@/PublicApi/';
import { issueJWT } from '@/auth/jwt';
import { UserManagementMailer } from '@/UserManagement/email/UserManagementMailer';
import { licenseController } from '@/license/license.controller';
import { registerController } from '@/decorators';
import {
AuthController,
LdapController,
MeController,
NodesController,
OwnerController,
PasswordResetController,
UsersController,
} from '@/controllers';
import { setupAuthMiddlewares } from '@/middlewares';
import { InternalHooks } from '@/InternalHooks';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { PostHogClient } from '@/posthog';
import { variablesController } from '@/environments/variables/variables.controller';
import { LdapManager } from '@/Ldap/LdapManager.ee';
import { handleLdapInit } from '@/Ldap/helpers';
import { Push } from '@/push';
import { setSamlLoginEnabled } from '@/sso/saml/samlHelpers';
import { SamlController } from '@/sso/saml/routes/saml.controller.ee';
import { EventBusController } from '@/eventbus/eventBus.controller';
import { License } from '@/License';
import { SourceControlController } from '@/environments/sourceControl/sourceControl.controller.ee';
import * as testDb from '../../shared/testDb';
import { AUTHLESS_ENDPOINTS, PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } from '../constants';
import type { EndpointGroup, SetupProps, TestServer } from '../types';
import { mockInstance } from './mocking';
/**
* Plugin to prefix a path segment into a request URL pathname.
*
* Example: http://127.0.0.1:62100/me/password → http://127.0.0.1:62100/rest/me/password
*/
function prefix(pathSegment: string) {
return async function (request: superagent.SuperAgentRequest) {
const url = new URL(request.url);
// enforce consistency at call sites
if (url.pathname[0] !== '/') {
throw new Error('Pathname must start with a forward slash');
}
url.pathname = pathSegment + url.pathname;
request.url = url.toString();
return request;
};
}
/**
* Classify endpoint groups into `routerEndpoints` (newest, using `express.Router`),
* and `functionEndpoints` (legacy, namespaced inside a function).
*/
const classifyEndpointGroups = (endpointGroups: EndpointGroup[]) => {
const routerEndpoints: EndpointGroup[] = [];
const functionEndpoints: EndpointGroup[] = [];
const ROUTER_GROUP = ['credentials', 'workflows', 'publicApi', 'license', 'variables'];
endpointGroups.forEach((group) =>
(ROUTER_GROUP.includes(group) ? routerEndpoints : functionEndpoints).push(group),
);
return [routerEndpoints, functionEndpoints];
};
function createAgent(app: express.Application, options?: { auth: boolean; user: User }) {
const agent = request.agent(app);
void agent.use(prefix(REST_PATH_SEGMENT));
if (options?.auth && options?.user) {
try {
const { token } = issueJWT(options.user);
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
} catch {}
}
return agent;
}
function publicApiAgent(
app: express.Application,
{ user, version = 1 }: { user: User; version?: number },
) {
const agent = request.agent(app);
void agent.use(prefix(`${PUBLIC_API_REST_PATH_SEGMENT}/v${version}`));
if (user.apiKey) {
void agent.set({ 'X-N8N-API-KEY': user.apiKey });
}
return agent;
}
export const setupTestServer = ({
endpointGroups,
applyAuth = true,
enabledFeatures,
}: SetupProps): TestServer => {
const app = express();
const testServer: TestServer = {
app,
httpServer: app.listen(0),
authAgentFor: (user: User) => createAgent(app, { auth: true, user }),
authlessAgent: createAgent(app),
publicApiAgentFor: (user) => publicApiAgent(app, { user }),
};
beforeAll(async () => {
await testDb.init();
const logger = getLogger();
LoggerProxy.init(logger);
// Mock all telemetry.
mockInstance(InternalHooks);
mockInstance(PostHogClient);
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
config.set('userManagement.jwtSecret', 'My JWT secret');
config.set('userManagement.isInstanceOwnerSetUp', true);
if (enabledFeatures) {
Container.get(License).isFeatureEnabled = (feature) => enabledFeatures.includes(feature);
}
const enablePublicAPI = endpointGroups?.includes('publicApi');
if (applyAuth && !enablePublicAPI) {
setupAuthMiddlewares(app, AUTHLESS_ENDPOINTS, REST_PATH_SEGMENT);
}
if (!endpointGroups) return;
const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups);
if (routerEndpoints.length) {
const map: Record<string, express.Router | express.Router[] | any> = {
credentials: { controller: credentialsController, path: 'credentials' },
workflows: { controller: workflowsController, path: 'workflows' },
license: { controller: licenseController, path: 'license' },
variables: { controller: variablesController, path: 'variables' },
};
if (enablePublicAPI) {
const { apiRouters } = await loadPublicApiVersions(PUBLIC_API_REST_PATH_SEGMENT);
map.publicApi = apiRouters;
}
for (const group of routerEndpoints) {
if (group === 'publicApi') {
app.use(...(map[group] as express.Router[]));
} else {
app.use(`/${REST_PATH_SEGMENT}/${map[group].path}`, map[group].controller);
}
}
}
if (functionEndpoints.length) {
const externalHooks = Container.get(ExternalHooks);
const internalHooks = Container.get(InternalHooks);
const mailer = Container.get(UserManagementMailer);
const repositories = Db.collections;
for (const group of functionEndpoints) {
switch (group) {
case 'eventBus':
registerController(app, config, new EventBusController());
break;
case 'auth':
registerController(
app,
config,
new AuthController({ config, logger, internalHooks, repositories }),
);
break;
case 'ldap':
Container.get(License).isLdapEnabled = () => true;
await handleLdapInit();
const { service, sync } = LdapManager.getInstance();
registerController(app, config, new LdapController(service, sync, internalHooks));
break;
case 'saml':
await setSamlLoginEnabled(true);
registerController(app, config, Container.get(SamlController));
break;
case 'sourceControl':
registerController(app, config, Container.get(SourceControlController));
break;
case 'nodes':
registerController(
app,
config,
new NodesController(
config,
Container.get(LoadNodesAndCredentials),
Container.get(Push),
internalHooks,
),
);
case 'me':
registerController(
app,
config,
new MeController({ logger, externalHooks, internalHooks, repositories }),
);
break;
case 'passwordReset':
registerController(
app,
config,
new PasswordResetController({
config,
logger,
externalHooks,
internalHooks,
mailer,
repositories,
}),
);
break;
case 'owner':
registerController(
app,
config,
new OwnerController({ config, logger, internalHooks, repositories }),
);
break;
case 'users':
registerController(
app,
config,
new UsersController({
config,
mailer,
externalHooks,
internalHooks,
repositories,
activeWorkflowRunner: Container.get(ActiveWorkflowRunner),
logger,
}),
);
}
}
}
});
afterAll(async () => {
await testDb.terminate();
testServer.httpServer.close();
});
return testServer;
};

View File

@@ -21,7 +21,7 @@ import {
randomValidPassword,
} from './shared/random';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
jest.mock('@/UserManagement/email/NodeMailer');
@@ -29,13 +29,11 @@ let globalMemberRole: Role;
let workflowOwnerRole: Role;
let credentialOwnerRole: Role;
let owner: User;
let authlessAgent: SuperAgentTest;
let authOwnerAgent: SuperAgentTest;
let authAgentFor: (user: User) => SuperAgentTest;
const testServer = utils.setupTestServer({ endpointGroups: ['users'] });
beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['users'] });
const [
globalOwnerRole,
fetchedGlobalMemberRole,
@@ -49,9 +47,7 @@ beforeAll(async () => {
owner = await testDb.createUser({ globalRole: globalOwnerRole });
authlessAgent = utils.createAgent(app);
authAgentFor = utils.createAuthAgent(app);
authOwnerAgent = authAgentFor(owner);
authOwnerAgent = testServer.authAgentFor(owner);
});
beforeEach(async () => {
@@ -60,15 +56,10 @@ beforeEach(async () => {
jest.mock('@/config');
config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('userManagement.emails.mode', 'smtp');
config.set('userManagement.emails.smtp.host', '');
});
afterAll(async () => {
await testDb.terminate();
});
describe('GET /users', () => {
test('should return all users (for owner)', async () => {
await testDb.createUser({ globalRole: globalMemberRole });
@@ -107,7 +98,7 @@ describe('GET /users', () => {
test('should return all users (for member)', async () => {
const member = await testDb.createUser({ globalRole: globalMemberRole });
const response = await authAgentFor(member).get('/users');
const response = await testServer.authAgentFor(member).get('/users');
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2);
@@ -252,7 +243,9 @@ describe('POST /users/:id', () => {
password: randomValidPassword(),
};
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(memberData);
const response = await testServer.authlessAgent
.post(`/users/${memberShell.id}`)
.send(memberData);
const {
id,
@@ -324,20 +317,20 @@ describe('POST /users/:id', () => {
},
];
await Promise.all(
invalidPayloads.map(async (invalidPayload) => {
const response = await authlessAgent.post(`/users/${memberShell.id}`).send(invalidPayload);
expect(response.statusCode).toBe(400);
for (const invalidPayload of invalidPayloads) {
const response = await testServer.authlessAgent
.post(`/users/${memberShell.id}`)
.send(invalidPayload);
expect(response.statusCode).toBe(400);
const storedUser = await Db.collections.User.findOneOrFail({
where: { email: memberShellEmail },
});
const storedUser = await Db.collections.User.findOneOrFail({
where: { email: memberShellEmail },
});
expect(storedUser.firstName).toBeNull();
expect(storedUser.lastName).toBeNull();
expect(storedUser.password).toBeNull();
}),
);
expect(storedUser.firstName).toBeNull();
expect(storedUser.lastName).toBeNull();
expect(storedUser.password).toBeNull();
}
});
test('should fail with already accepted invite', async () => {
@@ -350,7 +343,7 @@ describe('POST /users/:id', () => {
password: randomValidPassword(),
};
const response = await authlessAgent.post(`/users/${member.id}`).send(newMemberData);
const response = await testServer.authlessAgent.post(`/users/${member.id}`).send(newMemberData);
expect(response.statusCode).toBe(400);

View File

@@ -1,36 +1,28 @@
import type { Application } from 'express';
import type { User } from '@/databases/entities/User';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils';
import config from '@/config';
import type { AuthAgent } from './shared/types';
import type { SuperAgentTest } from 'supertest';
import type { Variables } from '@db/entities/Variables';
import { License } from '@/License';
import * as testDb from './shared/testDb';
import * as utils from './shared/utils/';
let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
// mock that credentialsSharing is not enabled
let app: Application;
let ownerUser: User;
let memberUser: User;
let authAgent: AuthAgent;
let variablesSpy: jest.SpyInstance<boolean>;
const licenseLike = {
isVariablesEnabled: jest.fn().mockReturnValue(true),
getVariablesLimit: jest.fn().mockReturnValue(-1),
isWithinUsersLimit: jest.fn().mockReturnValue(true),
};
beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['variables'] });
const testServer = utils.setupTestServer({ endpointGroups: ['variables'] });
await utils.initConfigFile();
beforeAll(async () => {
await utils.initEncryptionKey();
utils.mockInstance(License, licenseLike);
ownerUser = await testDb.createOwner();
memberUser = await testDb.createUser();
authAgent = utils.createAuthAgent(app);
config.set('userManagement.isInstanceOwnerSetUp', true);
const owner = await testDb.createOwner();
authOwnerAgent = testServer.authAgentFor(owner);
const member = await testDb.createUser();
authMemberAgent = testServer.authAgentFor(member);
});
beforeEach(async () => {
@@ -39,343 +31,306 @@ beforeEach(async () => {
licenseLike.getVariablesLimit.mockReturnValue(-1);
});
afterAll(async () => {
await testDb.terminate();
});
// ----------------------------------------
// GET /variables - fetch all variables
// ----------------------------------------
describe('GET /variables', () => {
beforeEach(async () => {
await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
]);
});
test('GET /variables should return all variables for an owner', async () => {
await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
]);
test('should return all variables for an owner', async () => {
const response = await authOwnerAgent.get('/variables');
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2);
});
const response = await authAgent(ownerUser).get('/variables');
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2);
});
test('GET /variables should return all variables for a member', async () => {
await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
]);
const response = await authAgent(memberUser).get('/variables');
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2);
test('should return all variables for a member', async () => {
const response = await authMemberAgent.get('/variables');
expect(response.statusCode).toBe(200);
expect(response.body.data.length).toBe(2);
});
});
// ----------------------------------------
// GET /variables/:id - get a single variable
// ----------------------------------------
describe('GET /variables/:id', () => {
let var1: Variables, var2: Variables;
beforeEach(async () => {
[var1, var2] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
]);
});
test('GET /variables/:id should return a single variable for an owner', async () => {
const [var1, var2] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
]);
test('should return a single variable for an owner', async () => {
const response1 = await authOwnerAgent.get(`/variables/${var1.id}`);
expect(response1.statusCode).toBe(200);
expect(response1.body.data.key).toBe('test1');
const response1 = await authAgent(ownerUser).get(`/variables/${var1.id}`);
expect(response1.statusCode).toBe(200);
expect(response1.body.data.key).toBe('test1');
const response2 = await authOwnerAgent.get(`/variables/${var2.id}`);
expect(response2.statusCode).toBe(200);
expect(response2.body.data.key).toBe('test2');
});
const response2 = await authAgent(ownerUser).get(`/variables/${var2.id}`);
expect(response2.statusCode).toBe(200);
expect(response2.body.data.key).toBe('test2');
});
test('should return a single variable for a member', async () => {
const response1 = await authMemberAgent.get(`/variables/${var1.id}`);
expect(response1.statusCode).toBe(200);
expect(response1.body.data.key).toBe('test1');
test('GET /variables/:id should return a single variable for a member', async () => {
const [var1, var2] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
]);
const response1 = await authAgent(memberUser).get(`/variables/${var1.id}`);
expect(response1.statusCode).toBe(200);
expect(response1.body.data.key).toBe('test1');
const response2 = await authAgent(memberUser).get(`/variables/${var2.id}`);
expect(response2.statusCode).toBe(200);
expect(response2.body.data.key).toBe('test2');
const response2 = await authMemberAgent.get(`/variables/${var2.id}`);
expect(response2.statusCode).toBe(200);
expect(response2.body.data.key).toBe('test2');
});
});
// ----------------------------------------
// POST /variables - create a new variable
// ----------------------------------------
test('POST /variables should create a new credential and return it for an owner', async () => {
const toCreate = {
key: 'create1',
value: 'createvalue1',
};
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(200);
expect(response.body.data.key).toBe(toCreate.key);
expect(response.body.data.value).toBe(toCreate.value);
const [byId, byKey] = await Promise.all([
testDb.getVariableById(response.body.data.id),
testDb.getVariableByKey(toCreate.key),
]);
expect(byId).not.toBeNull();
expect(byId!.key).toBe(toCreate.key);
expect(byId!.value).toBe(toCreate.value);
expect(byKey).not.toBeNull();
expect(byKey!.id).toBe(response.body.data.id);
expect(byKey!.value).toBe(toCreate.value);
});
test('POST /variables should not create a new credential and return it for a member', async () => {
const toCreate = {
key: 'create1',
value: 'createvalue1',
};
const response = await authAgent(memberUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(401);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
const byKey = await testDb.getVariableByKey(toCreate.key);
expect(byKey).toBeNull();
});
test("POST /variables should not create a new credential and return it if the instance doesn't have a license", async () => {
licenseLike.isVariablesEnabled.mockReturnValue(false);
const toCreate = {
key: 'create1',
value: 'createvalue1',
};
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
const byKey = await testDb.getVariableByKey(toCreate.key);
expect(byKey).toBeNull();
});
test('POST /variables should fail to create a new credential and if one with the same key exists', async () => {
const toCreate = {
key: 'create1',
value: 'createvalue1',
};
await testDb.createVariable(toCreate.key, toCreate.value);
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(500);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
test('POST /variables should not fail if variable limit not reached', async () => {
licenseLike.getVariablesLimit.mockReturnValue(5);
let i = 1;
let toCreate = {
describe('POST /variables', () => {
const generatePayload = (i = 1) => ({
key: `create${i}`,
value: `createvalue${i}`,
};
while (i < 3) {
});
const toCreate = generatePayload();
test('should create a new credential and return it for an owner', async () => {
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(200);
expect(response.body.data.key).toBe(toCreate.key);
expect(response.body.data.value).toBe(toCreate.value);
const [byId, byKey] = await Promise.all([
testDb.getVariableById(response.body.data.id),
testDb.getVariableByKey(toCreate.key),
]);
expect(byId).not.toBeNull();
expect(byId!.key).toBe(toCreate.key);
expect(byId!.value).toBe(toCreate.value);
expect(byKey).not.toBeNull();
expect(byKey!.id).toBe(response.body.data.id);
expect(byKey!.value).toBe(toCreate.value);
});
test('should not create a new credential and return it for a member', async () => {
const response = await authMemberAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(401);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
const byKey = await testDb.getVariableByKey(toCreate.key);
expect(byKey).toBeNull();
});
test("POST /variables should not create a new credential and return it if the instance doesn't have a license", async () => {
licenseLike.isVariablesEnabled.mockReturnValue(false);
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
const byKey = await testDb.getVariableByKey(toCreate.key);
expect(byKey).toBeNull();
});
test('should fail to create a new credential and if one with the same key exists', async () => {
await testDb.createVariable(toCreate.key, toCreate.value);
i++;
toCreate = {
key: `create${i}`,
value: `createvalue${i}`,
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(500);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
test('should not fail if variable limit not reached', async () => {
licenseLike.getVariablesLimit.mockReturnValue(5);
let i = 1;
let toCreate = generatePayload(i);
while (i < 3) {
await testDb.createVariable(toCreate.key, toCreate.value);
i++;
toCreate = generatePayload(i);
}
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(200);
expect(response.body.data?.key).toBe(toCreate.key);
expect(response.body.data?.value).toBe(toCreate.value);
});
test('should fail if variable limit reached', async () => {
licenseLike.getVariablesLimit.mockReturnValue(5);
let i = 1;
let toCreate = generatePayload(i);
while (i < 6) {
await testDb.createVariable(toCreate.key, toCreate.value);
i++;
toCreate = generatePayload(i);
}
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
test('should fail if key too long', async () => {
const toCreate = {
// 51 'a's
key: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
value: 'value',
};
}
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(200);
expect(response.body.data?.key).toBe(toCreate.key);
expect(response.body.data?.value).toBe(toCreate.value);
});
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
test('POST /variables should fail if variable limit reached', async () => {
licenseLike.getVariablesLimit.mockReturnValue(5);
let i = 1;
let toCreate = {
key: `create${i}`,
value: `createvalue${i}`,
};
while (i < 6) {
await testDb.createVariable(toCreate.key, toCreate.value);
i++;
toCreate = {
key: `create${i}`,
value: `createvalue${i}`,
test('should fail if value too long', async () => {
const toCreate = {
key: 'key',
// 256 'a's
value:
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
};
}
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
test('POST /variables should fail if key too long', async () => {
const toCreate = {
// 51 'a's
key: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
value: 'value',
};
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
test('POST /variables should fail if value too long', async () => {
const toCreate = {
key: 'key',
// 256 'a's
value:
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
};
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
test("POST /variables should fail if key contain's prohibited characters", async () => {
const toCreate = {
// 51 'a's
key: 'te$t',
value: 'value',
};
const response = await authAgent(ownerUser).post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
test("should fail if key contain's prohibited characters", async () => {
const toCreate = {
// 51 'a's
key: 'te$t',
value: 'value',
};
const response = await authOwnerAgent.post('/variables').send(toCreate);
expect(response.statusCode).toBe(400);
expect(response.body.data?.key).not.toBe(toCreate.key);
expect(response.body.data?.value).not.toBe(toCreate.value);
});
});
// ----------------------------------------
// PATCH /variables/:id - change a variable
// ----------------------------------------
test('PATCH /variables/:id should modify existing credential if use is an owner', async () => {
const variable = await testDb.createVariable('test1', 'value1');
describe('PATCH /variables/:id', () => {
const toModify = {
key: 'create1',
value: 'createvalue1',
};
const response = await authAgent(ownerUser).patch(`/variables/${variable.id}`).send(toModify);
expect(response.statusCode).toBe(200);
expect(response.body.data.key).toBe(toModify.key);
expect(response.body.data.value).toBe(toModify.value);
const [byId, byKey] = await Promise.all([
testDb.getVariableById(response.body.data.id),
testDb.getVariableByKey(toModify.key),
]);
test('should modify existing credential if use is an owner', async () => {
const variable = await testDb.createVariable('test1', 'value1');
const response = await authOwnerAgent.patch(`/variables/${variable.id}`).send(toModify);
expect(response.statusCode).toBe(200);
expect(response.body.data.key).toBe(toModify.key);
expect(response.body.data.value).toBe(toModify.value);
expect(byId).not.toBeNull();
expect(byId!.key).toBe(toModify.key);
expect(byId!.value).toBe(toModify.value);
const [byId, byKey] = await Promise.all([
testDb.getVariableById(response.body.data.id),
testDb.getVariableByKey(toModify.key),
]);
expect(byKey).not.toBeNull();
expect(byKey!.id).toBe(response.body.data.id);
expect(byKey!.value).toBe(toModify.value);
});
expect(byId).not.toBeNull();
expect(byId!.key).toBe(toModify.key);
expect(byId!.value).toBe(toModify.value);
test('PATCH /variables/:id should modify existing credential if use is an owner', async () => {
const variable = await testDb.createVariable('test1', 'value1');
const toModify = {
key: 'create1',
value: 'createvalue1',
};
const response = await authAgent(ownerUser).patch(`/variables/${variable.id}`).send(toModify);
expect(response.statusCode).toBe(200);
expect(response.body.data.key).toBe(toModify.key);
expect(response.body.data.value).toBe(toModify.value);
expect(byKey).not.toBeNull();
expect(byKey!.id).toBe(response.body.data.id);
expect(byKey!.value).toBe(toModify.value);
});
const [byId, byKey] = await Promise.all([
testDb.getVariableById(response.body.data.id),
testDb.getVariableByKey(toModify.key),
]);
test('should modify existing credential if use is an owner', async () => {
const variable = await testDb.createVariable('test1', 'value1');
const response = await authOwnerAgent.patch(`/variables/${variable.id}`).send(toModify);
expect(response.statusCode).toBe(200);
expect(response.body.data.key).toBe(toModify.key);
expect(response.body.data.value).toBe(toModify.value);
expect(byId).not.toBeNull();
expect(byId!.key).toBe(toModify.key);
expect(byId!.value).toBe(toModify.value);
const [byId, byKey] = await Promise.all([
testDb.getVariableById(response.body.data.id),
testDb.getVariableByKey(toModify.key),
]);
expect(byKey).not.toBeNull();
expect(byKey!.id).toBe(response.body.data.id);
expect(byKey!.value).toBe(toModify.value);
});
expect(byId).not.toBeNull();
expect(byId!.key).toBe(toModify.key);
expect(byId!.value).toBe(toModify.value);
test('PATCH /variables/:id should not modify existing credential if use is a member', async () => {
const variable = await testDb.createVariable('test1', 'value1');
const toModify = {
key: 'create1',
value: 'createvalue1',
};
const response = await authAgent(memberUser).patch(`/variables/${variable.id}`).send(toModify);
expect(response.statusCode).toBe(401);
expect(response.body.data?.key).not.toBe(toModify.key);
expect(response.body.data?.value).not.toBe(toModify.value);
expect(byKey).not.toBeNull();
expect(byKey!.id).toBe(response.body.data.id);
expect(byKey!.value).toBe(toModify.value);
});
const byId = await testDb.getVariableById(variable.id);
expect(byId).not.toBeNull();
expect(byId!.key).not.toBe(toModify.key);
expect(byId!.value).not.toBe(toModify.value);
});
test('should not modify existing credential if use is a member', async () => {
const variable = await testDb.createVariable('test1', 'value1');
const response = await authMemberAgent.patch(`/variables/${variable.id}`).send(toModify);
expect(response.statusCode).toBe(401);
expect(response.body.data?.key).not.toBe(toModify.key);
expect(response.body.data?.value).not.toBe(toModify.value);
test('PATCH /variables/:id should not modify existing credential if one with the same key exists', async () => {
const toModify = {
key: 'create1',
value: 'createvalue1',
};
const [var1, var2] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable(toModify.key, toModify.value),
]);
const response = await authAgent(ownerUser).patch(`/variables/${var1.id}`).send(toModify);
expect(response.statusCode).toBe(500);
expect(response.body.data?.key).not.toBe(toModify.key);
expect(response.body.data?.value).not.toBe(toModify.value);
const byId = await testDb.getVariableById(variable.id);
expect(byId).not.toBeNull();
expect(byId!.key).not.toBe(toModify.key);
expect(byId!.value).not.toBe(toModify.value);
});
const byId = await testDb.getVariableById(var1.id);
expect(byId).not.toBeNull();
expect(byId!.key).toBe(var1.key);
expect(byId!.value).toBe(var1.value);
test('should not modify existing credential if one with the same key exists', async () => {
const [var1, var2] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable(toModify.key, toModify.value),
]);
const response = await authOwnerAgent.patch(`/variables/${var1.id}`).send(toModify);
expect(response.statusCode).toBe(500);
expect(response.body.data?.key).not.toBe(toModify.key);
expect(response.body.data?.value).not.toBe(toModify.value);
const byId = await testDb.getVariableById(var1.id);
expect(byId).not.toBeNull();
expect(byId!.key).toBe(var1.key);
expect(byId!.value).toBe(var1.value);
});
});
// ----------------------------------------
// DELETE /variables/:id - change a variable
// ----------------------------------------
describe('DELETE /variables/:id', () => {
test('should delete a single credential for an owner', async () => {
const [var1, var2, var3] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
testDb.createVariable('test3', 'value3'),
]);
test('DELETE /variables/:id should delete a single credential for an owner', async () => {
const [var1, var2, var3] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
testDb.createVariable('test3', 'value3'),
]);
const delResponse = await authOwnerAgent.delete(`/variables/${var1.id}`);
expect(delResponse.statusCode).toBe(200);
const delResponse = await authAgent(ownerUser).delete(`/variables/${var1.id}`);
expect(delResponse.statusCode).toBe(200);
const byId = await testDb.getVariableById(var1.id);
expect(byId).toBeNull();
const byId = await testDb.getVariableById(var1.id);
expect(byId).toBeNull();
const getResponse = await authOwnerAgent.get('/variables');
expect(getResponse.body.data.length).toBe(2);
});
const getResponse = await authAgent(ownerUser).get('/variables');
expect(getResponse.body.data.length).toBe(2);
});
test('DELETE /variables/:id should not delete a single credential for a member', async () => {
const [var1, var2, var3] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
testDb.createVariable('test3', 'value3'),
]);
const delResponse = await authAgent(memberUser).delete(`/variables/${var1.id}`);
expect(delResponse.statusCode).toBe(401);
const byId = await testDb.getVariableById(var1.id);
expect(byId).not.toBeNull();
const getResponse = await authAgent(memberUser).get('/variables');
expect(getResponse.body.data.length).toBe(3);
test('should not delete a single credential for a member', async () => {
const [var1, var2, var3] = await Promise.all([
testDb.createVariable('test1', 'value1'),
testDb.createVariable('test2', 'value2'),
testDb.createVariable('test3', 'value3'),
]);
const delResponse = await authMemberAgent.delete(`/variables/${var1.id}`);
expect(delResponse.statusCode).toBe(401);
const byId = await testDb.getVariableById(var1.id);
expect(byId).not.toBeNull();
const getResponse = await authMemberAgent.get('/variables');
expect(getResponse.body.data.length).toBe(3);
});
});

View File

@@ -1,20 +1,17 @@
import { Container } from 'typedi';
import type { SuperAgentTest } from 'supertest';
import { v4 as uuid } from 'uuid';
import type { INode } from 'n8n-workflow';
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
import type { User } from '@db/entities/User';
import { getSharedWorkflowIds } from '@/WorkflowHelpers';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
import * as testDb from './shared/testDb';
import { createWorkflow, getGlobalMemberRole, getGlobalOwnerRole } from './shared/testDb';
import type { SaveCredentialFunction } from './shared/types';
import { makeWorkflow } from './shared/utils';
import { makeWorkflow } from './shared/utils/';
import { randomCredentialPayload } from './shared/random';
import { License } from '@/License';
import { getSharedWorkflowIds } from '../../src/WorkflowHelpers';
import config from '@/config';
let owner: User;
let member: User;
@@ -23,12 +20,14 @@ let authOwnerAgent: SuperAgentTest;
let authMemberAgent: SuperAgentTest;
let authAnotherMemberAgent: SuperAgentTest;
let saveCredential: SaveCredentialFunction;
let sharingSpy: jest.SpyInstance<boolean>;
const sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
const testServer = utils.setupTestServer({
endpointGroups: ['workflows'],
enabledFeatures: ['feat:sharing'],
});
beforeAll(async () => {
Container.get(License).isSharingEnabled = () => true;
const app = await utils.initTestServer({ endpointGroups: ['workflows'] });
const globalOwnerRole = await testDb.getGlobalOwnerRole();
const globalMemberRole = await testDb.getGlobalMemberRole();
const credentialOwnerRole = await testDb.getCredentialOwnerRole();
@@ -37,26 +36,19 @@ beforeAll(async () => {
member = await testDb.createUser({ globalRole: globalMemberRole });
anotherMember = await testDb.createUser({ globalRole: globalMemberRole });
const authAgent = utils.createAuthAgent(app);
authOwnerAgent = authAgent(owner);
authMemberAgent = authAgent(member);
authAnotherMemberAgent = authAgent(anotherMember);
authOwnerAgent = testServer.authAgentFor(owner);
authMemberAgent = testServer.authAgentFor(member);
authAnotherMemberAgent = testServer.authAgentFor(anotherMember);
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
sharingSpy = jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(true);
await utils.initNodeTypes();
config.set('userManagement.isInstanceOwnerSetUp', true);
});
beforeEach(async () => {
await testDb.truncate(['Workflow', 'SharedWorkflow']);
});
afterAll(async () => {
await testDb.terminate();
});
describe('router should switch based on flag', () => {
let savedWorkflowId: string;

View File

@@ -1,57 +1,41 @@
import type { SuperAgentTest } from 'supertest';
import type { IPinData } from 'n8n-workflow';
import type { User } from '@db/entities/User';
import * as UserManagementHelpers from '@/UserManagement/UserManagementHelper';
import * as utils from './shared/utils';
import * as utils from './shared/utils/';
import * as testDb from './shared/testDb';
import { makeWorkflow, MOCK_PINDATA } from './shared/utils';
import * as Db from '@/Db';
import { makeWorkflow, MOCK_PINDATA } from './shared/utils/';
let ownerShell: User;
let authOwnerAgent: SuperAgentTest;
beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['workflows'] });
const globalOwnerRole = await testDb.getGlobalOwnerRole();
ownerShell = await testDb.createUserShell(globalOwnerRole);
authOwnerAgent = utils.createAgent(app, { auth: true, user: ownerShell });
jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(false);
const testServer = utils.setupTestServer({ endpointGroups: ['workflows'] });
// mock whether sharing is enabled or not
jest.spyOn(UserManagementHelpers, 'isSharingEnabled').mockReturnValue(false);
beforeAll(async () => {
const globalOwnerRole = await testDb.getGlobalOwnerRole();
const ownerShell = await testDb.createUserShell(globalOwnerRole);
authOwnerAgent = testServer.authAgentFor(ownerShell);
});
beforeEach(async () => {
await testDb.truncate(['Workflow', 'SharedWorkflow']);
});
afterAll(async () => {
await testDb.terminate();
});
describe('POST /workflows', () => {
test('should store pin data for node in workflow', async () => {
const workflow = makeWorkflow({ withPinData: true });
const testWithPinData = async (withPinData: boolean) => {
const workflow = makeWorkflow({ withPinData });
const response = await authOwnerAgent.post('/workflows').send(workflow);
expect(response.statusCode).toBe(200);
return (response.body.data as { pinData: IPinData }).pinData;
};
const { pinData } = response.body.data as { pinData: IPinData };
test('should store pin data for node in workflow', async () => {
const pinData = await testWithPinData(true);
expect(pinData).toMatchObject(MOCK_PINDATA);
});
test('should set pin data to null if no pin data', async () => {
const workflow = makeWorkflow({ withPinData: false });
const response = await authOwnerAgent.post('/workflows').send(workflow);
expect(response.statusCode).toBe(200);
const { pinData } = response.body.data as { pinData: IPinData };
const pinData = await testWithPinData(false);
expect(pinData).toBeNull();
});
});
@@ -59,19 +43,13 @@ describe('POST /workflows', () => {
describe('GET /workflows/:id', () => {
test('should return pin data', async () => {
const workflow = makeWorkflow({ withPinData: true });
const workflowCreationResponse = await authOwnerAgent.post('/workflows').send(workflow);
const { id } = workflowCreationResponse.body.data as { id: string };
const sw = await Db.collections.SharedWorkflow.find();
const workflowRetrievalResponse = await authOwnerAgent.get(`/workflows/${id}`);
expect(workflowRetrievalResponse.statusCode).toBe(200);
const { pinData } = workflowRetrievalResponse.body.data as { pinData: IPinData };
expect(pinData).toMatchObject(MOCK_PINDATA);
});
});

View File

@@ -1,11 +1,11 @@
import { ActiveExecutions } from '@/ActiveExecutions';
import PCancelable from 'p-cancelable';
import { v4 as uuid } from 'uuid';
import { Container } from 'typedi';
import type { IExecuteResponsePromiseData, IRun } from 'n8n-workflow';
import { createDeferredPromise } from 'n8n-workflow';
import type { IWorkflowExecutionDataProcess } from '@/Interfaces';
import { ExecutionRepository } from '@/databases/repositories';
import Container from 'typedi';
import { ExecutionRepository } from '@db/repositories';
const FAKE_EXECUTION_ID = '15';
const FAKE_SECOND_EXECUTION_ID = '20';

View File

@@ -20,7 +20,7 @@ import { mock } from 'jest-mock-extended';
import type { ExternalHooks } from '@/ExternalHooks';
import { Container } from 'typedi';
import { LoadNodesAndCredentials } from '@/LoadNodesAndCredentials';
import { mockInstance } from '../integration/shared/utils';
import { mockInstance } from '../integration/shared/utils/';
import { Push } from '@/push';
import { ActiveExecutions } from '@/ActiveExecutions';
import { NodeTypes } from '@/NodeTypes';

View File

@@ -22,7 +22,7 @@ import { InstalledPackages } from '@db/entities/InstalledPackages';
import { InstalledNodes } from '@db/entities/InstalledNodes';
import { randomName } from '../integration/shared/random';
import config from '@/config';
import { installedPackagePayload, installedNodePayload } from '../integration/shared/utils';
import { installedPackagePayload, installedNodePayload } from '../integration/shared/utils/';
import type { CommunityPackages } from '@/Interfaces';

View File

@@ -13,9 +13,9 @@ import * as UserManagementHelper from '@/UserManagement/UserManagementHelper';
import { getLogger } from '@/Logger';
import { InternalHooks } from '@/InternalHooks';
import { mockInstance } from '../integration/shared/utils';
import { mockInstance } from '../integration/shared/utils/';
import { UserService } from '@/user/user.service';
import { WorkflowEntity } from '@/databases/entities/WorkflowEntity';
import { WorkflowEntity } from '@db/entities/WorkflowEntity';
jest.mock('@/Db', () => {
return {

View File

@@ -22,7 +22,7 @@ import {
import * as testDb from '../integration/shared/testDb';
import { mockNodeTypesData } from './Helpers';
import type { SaveCredentialFunction } from '../integration/shared/types';
import { mockInstance } from '../integration/shared/utils';
import { mockInstance } from '../integration/shared/utils/';
let mockNodeTypes: INodeTypes;
let credentialOwnerRole: Role;

View File

@@ -4,7 +4,7 @@ import { mock } from 'jest-mock-extended';
import type { RoleNames, RoleScopes } from '@db/entities/Role';
import { Role } from '@db/entities/Role';
import { RoleRepository } from '@db/repositories/role.repository';
import { mockInstance } from '../../integration/shared/utils';
import { mockInstance } from '../../integration/shared/utils/';
import { randomInteger } from '../../integration/shared/random';
describe('RoleRepository', () => {

View File

@@ -2,7 +2,7 @@ import { SharedWorkflowRepository } from '@db/repositories/sharedWorkflow.reposi
import { Role } from '@db/entities/Role';
import { SharedWorkflow } from '@db/entities/SharedWorkflow';
import { RoleService } from '@/role/role.service';
import { mockInstance } from '../../integration/shared/utils';
import { mockInstance } from '../../integration/shared/utils/';
describe('RoleService', () => {
const sharedWorkflowRepository = mockInstance(SharedWorkflowRepository);