mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
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:
committed by
GitHub
parent
3e07ffa73e
commit
b895ba438a
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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'> = {};
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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: [
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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<{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {}
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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([]);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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}`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 });
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
29
packages/cli/test/integration/shared/utils/communityNodes.ts
Normal file
29
packages/cli/test/integration/shared/utils/communityNodes.ts
Normal 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;
|
||||
};
|
||||
@@ -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
|
||||
12
packages/cli/test/integration/shared/utils/mocking.ts
Normal file
12
packages/cli/test/integration/shared/utils/mocking.ts
Normal 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;
|
||||
};
|
||||
275
packages/cli/test/integration/shared/utils/testServer.ts
Normal file
275
packages/cli/test/integration/shared/utils/testServer.ts
Normal 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;
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user