refactor(core): Post-release refactorings of Public API (#3495)

*  Post-release refactorings

* 🧪 Add `--forceExit`

* 🛠 typing refactor (#3486)

* 🐛 Fix middleware arguments

* 👕 Fix lint

*  Restore commented out block

Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com>
This commit is contained in:
Iván Ovejero 2022-06-14 18:32:19 +02:00 committed by GitHub
parent 2ebcf4bb91
commit b8e3bcc052
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 307 additions and 343 deletions

View file

@ -30,7 +30,7 @@
"start:default": "cd bin && ./n8n", "start:default": "cd bin && ./n8n",
"start:windows": "cd bin && n8n", "start:windows": "cd bin && n8n",
"test": "npm run test:sqlite", "test": "npm run test:sqlite",
"test:sqlite": "export N8N_LOG_LEVEL=silent; export DB_TYPE=sqlite; jest", "test:sqlite": "export N8N_LOG_LEVEL=silent; export DB_TYPE=sqlite; jest --forceExit",
"test:postgres": "export N8N_LOG_LEVEL=silent; export DB_TYPE=postgresdb; jest", "test:postgres": "export N8N_LOG_LEVEL=silent; export DB_TYPE=postgresdb; jest",
"test:postgres:alt-schema": "export DB_POSTGRESDB_SCHEMA=alt_schema; npm run test:postgres", "test:postgres:alt-schema": "export DB_POSTGRESDB_SCHEMA=alt_schema; npm run test:postgres",
"test:mysql": "export N8N_LOG_LEVEL=silent; export DB_TYPE=mysqldb; jest", "test:mysql": "export N8N_LOG_LEVEL=silent; export DB_TYPE=mysqldb; jest",

View file

@ -178,8 +178,6 @@ export interface ICredentialsDecryptedResponse extends ICredentialsDecryptedDb {
export type DatabaseType = 'mariadb' | 'postgresdb' | 'mysqldb' | 'sqlite'; export type DatabaseType = 'mariadb' | 'postgresdb' | 'mysqldb' | 'sqlite';
export type SaveExecutionDataType = 'all' | 'none'; export type SaveExecutionDataType = 'all' | 'none';
export type ExecutionDataFieldFormat = 'empty' | 'flattened' | 'json';
export interface IExecutionBase { export interface IExecutionBase {
id?: number | string; id?: number | string;
mode: WorkflowExecuteMode; mode: WorkflowExecuteMode;
@ -240,7 +238,7 @@ export interface IExecutionResponseApi {
finished: boolean; finished: boolean;
retryOf?: number | string; retryOf?: number | string;
retrySuccessId?: number | string; retrySuccessId?: number | string;
data?: string; // Just that we can remove it data?: object;
waitTill?: Date | null; waitTill?: Date | null;
workflowData: IWorkflowBase; workflowData: IWorkflowBase;
} }

View file

@ -1,38 +1,38 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable import/no-cycle */ /* eslint-disable import/no-cycle */
import express, { Router } from 'express'; import express, { Router } from 'express';
import fs from 'fs/promises';
import path from 'path';
import * as OpenApiValidator from 'express-openapi-validator'; import * as OpenApiValidator from 'express-openapi-validator';
import { HttpError } from 'express-openapi-validator/dist/framework/types'; import { HttpError } from 'express-openapi-validator/dist/framework/types';
import fs from 'fs/promises';
import { OpenAPIV3 } from 'openapi-types'; import { OpenAPIV3 } from 'openapi-types';
import path from 'path'; import swaggerUi from 'swagger-ui-express';
import * as swaggerUi from 'swagger-ui-express';
import validator from 'validator'; import validator from 'validator';
import * as YAML from 'yamljs'; import YAML from 'yamljs';
import { Db, InternalHooksManager } from '..';
import config from '../../config'; import config from '../../config';
import { Db, InternalHooksManager } from '..';
import { getInstanceBaseUrl } from '../UserManagement/UserManagementHelper'; import { getInstanceBaseUrl } from '../UserManagement/UserManagementHelper';
function createApiRouter( function createApiRouter(
version: string, version: string,
openApiSpecPath: string, openApiSpecPath: string,
hanldersDirectory: string, handlersDirectory: string,
swaggerThemeCss: string, swaggerThemeCss: string,
publicApiEndpoint: string, publicApiEndpoint: string,
): Router { ): Router {
const n8nPath = config.getEnv('path'); const n8nPath = config.getEnv('path');
const swaggerDocument = YAML.load(openApiSpecPath) as swaggerUi.JsonObject; const swaggerDocument = YAML.load(openApiSpecPath) as swaggerUi.JsonObject;
// add the server depeding on the config so the user can interact with the API // add the server depeding on the config so the user can interact with the API
// from the swagger UI // from the Swagger UI
swaggerDocument.server = [ swaggerDocument.server = [
{ {
url: `${getInstanceBaseUrl()}/${publicApiEndpoint}/${version}}`, url: `${getInstanceBaseUrl()}/${publicApiEndpoint}/${version}}`,
}, },
]; ];
const apiController = express.Router(); const apiController = express.Router();
apiController.use( apiController.use(
`/${publicApiEndpoint}/${version}/docs`, `/${publicApiEndpoint}/${version}/docs`,
swaggerUi.serveFiles(swaggerDocument), swaggerUi.serveFiles(swaggerDocument),
@ -42,12 +42,14 @@ function createApiRouter(
customfavIcon: `${n8nPath}favicon.ico`, customfavIcon: `${n8nPath}favicon.ico`,
}), }),
); );
apiController.use(`/${publicApiEndpoint}/${version}`, express.json()); apiController.use(`/${publicApiEndpoint}/${version}`, express.json());
apiController.use( apiController.use(
`/${publicApiEndpoint}/${version}`, `/${publicApiEndpoint}/${version}`,
OpenApiValidator.middleware({ OpenApiValidator.middleware({
apiSpec: openApiSpecPath, apiSpec: openApiSpecPath,
operationHandlers: hanldersDirectory, operationHandlers: handlersDirectory,
validateRequests: true, validateRequests: true,
validateApiSpec: true, validateApiSpec: true,
formats: [ formats: [
@ -71,16 +73,12 @@ function createApiRouter(
schema: OpenAPIV3.ApiKeySecurityScheme, schema: OpenAPIV3.ApiKeySecurityScheme,
): Promise<boolean> => { ): Promise<boolean> => {
const apiKey = req.headers[schema.name.toLowerCase()]; const apiKey = req.headers[schema.name.toLowerCase()];
const user = await Db.collections.User?.findOne({ const user = await Db.collections.User.findOne({
where: { where: { apiKey },
apiKey,
},
relations: ['globalRole'], relations: ['globalRole'],
}); });
if (!user) { if (!user) return false;
return false;
}
void InternalHooksManager.getInstance().onUserInvokedApi({ void InternalHooksManager.getInstance().onUserInvokedApi({
user_id: user.id, user_id: user.id,
@ -97,13 +95,20 @@ function createApiRouter(
}, },
}), }),
); );
apiController.use( apiController.use(
(error: HttpError, req: express.Request, res: express.Response, next: express.NextFunction) => { (
error: HttpError,
_req: express.Request,
res: express.Response,
_next: express.NextFunction,
) => {
return res.status(error.status || 400).json({ return res.status(error.status || 400).json({
message: error.message, message: error.message,
}); });
}, },
); );
return apiController; return apiController;
} }
@ -114,11 +119,12 @@ export const loadPublicApiVersions = async (
const folders = await fs.readdir(__dirname); const folders = await fs.readdir(__dirname);
const css = (await fs.readFile(swaggerThemePath)).toString(); const css = (await fs.readFile(swaggerThemePath)).toString();
const versions = folders.filter((folderName) => folderName.startsWith('v')); const versions = folders.filter((folderName) => folderName.startsWith('v'));
const apiRouters: express.Router[] = [];
for (const version of versions) { const apiRouters = versions.map((version) => {
const openApiPath = path.join(__dirname, version, 'openapi.yml'); const openApiPath = path.join(__dirname, version, 'openapi.yml');
apiRouters.push(createApiRouter(version, openApiPath, __dirname, css, publicApiEndpoint)); return createApiRouter(version, openApiPath, __dirname, css, publicApiEndpoint);
} });
return { return {
apiRouters, apiRouters,
apiLatestVersion: Number(versions.pop()?.charAt(1)) ?? 1, apiLatestVersion: Number(versions.pop()?.charAt(1)) ?? 1,

View file

@ -1,7 +1,7 @@
import express = require('express'); import express from 'express';
import { CredentialsHelper } from '../../../../CredentialsHelper'; import { CredentialsHelper } from '../../../../CredentialsHelper';
import { CredentialTypes } from '../../../../CredentialTypes'; import { CredentialTypes } from '../../../../CredentialTypes';
import { CredentialsEntity } from '../../../../databases/entities/CredentialsEntity'; import { CredentialsEntity } from '../../../../databases/entities/CredentialsEntity';
import { CredentialRequest } from '../../../../requests'; import { CredentialRequest } from '../../../../requests';
import { CredentialTypeRequest } from '../../../types'; import { CredentialTypeRequest } from '../../../types';
@ -29,7 +29,7 @@ export = {
res: express.Response, res: express.Response,
): Promise<express.Response<Partial<CredentialsEntity>>> => { ): Promise<express.Response<Partial<CredentialsEntity>>> => {
try { try {
const newCredential = await createCredential(req.body as Partial<CredentialsEntity>); const newCredential = await createCredential(req.body);
const encryptedData = await encryptCredential(newCredential); const encryptedData = await encryptCredential(newCredential);
@ -56,7 +56,7 @@ export = {
res: express.Response, res: express.Response,
): Promise<express.Response<Partial<CredentialsEntity>>> => { ): Promise<express.Response<Partial<CredentialsEntity>>> => {
const { id: credentialId } = req.params; const { id: credentialId } = req.params;
let credentials: CredentialsEntity | undefined; let credential: CredentialsEntity | undefined;
if (req.user.globalRole.name !== 'owner') { if (req.user.globalRole.name !== 'owner') {
const shared = await getSharedCredentials(req.user.id, credentialId, [ const shared = await getSharedCredentials(req.user.id, credentialId, [
@ -65,27 +65,20 @@ export = {
]); ]);
if (shared?.role.name === 'owner') { if (shared?.role.name === 'owner') {
credentials = shared.credentials; credential = shared.credentials;
} else {
// LoggerProxy.info('Attempt to delete credential blocked due to lack of permissions', {
// credentialId,
// userId: req.user.id,
// });
} }
} else { } else {
credentials = (await getCredentials(credentialId)) as CredentialsEntity; credential = (await getCredentials(credentialId)) as CredentialsEntity;
} }
if (!credentials) { if (!credential) {
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
await removeCredential(credentials); await removeCredential(credential);
credentials.id = Number(credentialId); credential.id = Number(credentialId);
return res.json(sanitizeCredentials(credentials)); return res.json(sanitizeCredentials(credential));
}, },
], ],
@ -97,14 +90,12 @@ export = {
try { try {
CredentialTypes().getByName(credentialTypeName); CredentialTypes().getByName(credentialTypeName);
} catch (error) { } catch (error) {
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
let schema = new CredentialsHelper('').getCredentialsProperties(credentialTypeName); const schema = new CredentialsHelper('')
.getCredentialsProperties(credentialTypeName)
schema = schema.filter((nodeProperty) => nodeProperty.type !== 'hidden'); .filter((property) => property.type !== 'hidden');
return res.json(toJsonSchema(schema)); return res.json(toJsonSchema(schema));
}, },

View file

@ -1,37 +1,36 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-invalid-void-type */
/* eslint-disable consistent-return */
import { RequestHandler } from 'express'; import express from 'express';
import { validate } from 'jsonschema'; import { validate } from 'jsonschema';
import { CredentialsHelper, CredentialTypes } from '../../../..'; import { CredentialsHelper, CredentialTypes } from '../../../..';
import { CredentialRequest } from '../../../types'; import { CredentialRequest } from '../../../types';
import { toJsonSchema } from './credentials.service'; import { toJsonSchema } from './credentials.service';
export const validCredentialType: RequestHandler = async ( export const validCredentialType = (
req: CredentialRequest.Create, req: CredentialRequest.Create,
res, res: express.Response,
next, next: express.NextFunction,
): Promise<any> => { ): express.Response | void => {
const { type } = req.body;
try { try {
CredentialTypes().getByName(type); CredentialTypes().getByName(req.body.type);
} catch (error) { } catch (_) {
return res.status(400).json({ return res.status(400).json({ message: 'req.body.type is not a known type' });
message: 'req.body.type is not a known type',
});
} }
next();
return next();
}; };
export const validCredentialsProperties: RequestHandler = async ( export const validCredentialsProperties = (
req: CredentialRequest.Create, req: CredentialRequest.Create,
res, res: express.Response,
next, next: express.NextFunction,
): Promise<any> => { ): express.Response | void => {
const { type, data } = req.body; const { type, data } = req.body;
let properties = new CredentialsHelper('').getCredentialsProperties(type); const properties = new CredentialsHelper('')
.getCredentialsProperties(type)
properties = properties.filter((nodeProperty) => nodeProperty.type !== 'hidden'); .filter((property) => property.type !== 'hidden');
const schema = toJsonSchema(properties); const schema = toJsonSchema(properties);
@ -43,5 +42,5 @@ export const validCredentialsProperties: RequestHandler = async (
}); });
} }
next(); return next();
}; };

View file

@ -11,6 +11,7 @@ import { SharedCredentials } from '../../../../databases/entities/SharedCredenti
import { User } from '../../../../databases/entities/User'; import { User } from '../../../../databases/entities/User';
import { externalHooks } from '../../../../Server'; import { externalHooks } from '../../../../Server';
import { IDependency, IJsonSchema } from '../../../types'; import { IDependency, IJsonSchema } from '../../../types';
import { CredentialRequest } from '../../../../requests';
export async function getCredentials( export async function getCredentials(
credentialId: number | string, credentialId: number | string,
@ -38,7 +39,7 @@ export async function getSharedCredentials(
} }
export async function createCredential( export async function createCredential(
properties: Partial<CredentialsEntity>, properties: CredentialRequest.CredentialProperties,
): Promise<CredentialsEntity> { ): Promise<CredentialsEntity> {
const newCredential = new CredentialsEntity(); const newCredential = new CredentialsEntity();

View file

@ -1,16 +1,15 @@
import express = require('express'); import express from 'express';
import { BinaryDataManager } from 'n8n-core'; import { BinaryDataManager } from 'n8n-core';
import { import {
getExecutions, getExecutions,
getExecutionInWorkflows, getExecutionInWorkflows,
deleteExecution, deleteExecution,
getExecutionsCount, getExecutionsCount,
} from './executions.service'; } from './executions.service';
import { ActiveExecutions } from '../../../..'; import { ActiveExecutions } from '../../../..';
import { authorize, validCursor } from '../../shared/middlewares/global.middleware'; import { authorize, validCursor } from '../../shared/middlewares/global.middleware';
import { ExecutionRequest } from '../../../types'; import { ExecutionRequest } from '../../../types';
import { getSharedWorkflowIds } from '../workflows/workflows.service'; import { getSharedWorkflowIds } from '../workflows/workflows.service';
import { encodeNextCursor } from '../../shared/services/pagination.service'; import { encodeNextCursor } from '../../shared/services/pagination.service';
@ -20,31 +19,24 @@ export = {
deleteExecution: [ deleteExecution: [
authorize(['owner', 'member']), authorize(['owner', 'member']),
async (req: ExecutionRequest.Delete, res: express.Response): Promise<express.Response> => { async (req: ExecutionRequest.Delete, res: express.Response): Promise<express.Response> => {
const { id } = req.params;
const sharedWorkflowsIds = await getSharedWorkflowIds(req.user); const sharedWorkflowsIds = await getSharedWorkflowIds(req.user);
// user does not have workflows hence no executions // user does not have workflows hence no executions
// or the execution he is trying to access belongs to a workflow he does not own // or the execution he is trying to access belongs to a workflow he does not own
if (!sharedWorkflowsIds.length) { if (!sharedWorkflowsIds.length) {
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const { id } = req.params;
// look for the execution on the workflow the user owns // look for the execution on the workflow the user owns
const execution = await getExecutionInWorkflows(id, sharedWorkflowsIds, false); const execution = await getExecutionInWorkflows(id, sharedWorkflowsIds, false);
// execution was not found
if (!execution) { if (!execution) {
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const binaryDataManager = BinaryDataManager.getInstance(); await BinaryDataManager.getInstance().deleteBinaryDataByExecutionId(execution.id.toString());
await binaryDataManager.deleteBinaryDataByExecutionId(execution.id.toString());
await deleteExecution(execution); await deleteExecution(execution);
@ -56,35 +48,28 @@ export = {
getExecution: [ getExecution: [
authorize(['owner', 'member']), authorize(['owner', 'member']),
async (req: ExecutionRequest.Get, res: express.Response): Promise<express.Response> => { async (req: ExecutionRequest.Get, res: express.Response): Promise<express.Response> => {
const { id } = req.params;
const { includeData = false } = req.query;
const sharedWorkflowsIds = await getSharedWorkflowIds(req.user); const sharedWorkflowsIds = await getSharedWorkflowIds(req.user);
// user does not have workflows hence no executions // user does not have workflows hence no executions
// or the execution he is trying to access belongs to a workflow he does not own // or the execution he is trying to access belongs to a workflow he does not own
if (!sharedWorkflowsIds.length) { if (!sharedWorkflowsIds.length) {
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const { id } = req.params;
const { includeData = false } = req.query;
// look for the execution on the workflow the user owns // look for the execution on the workflow the user owns
const execution = await getExecutionInWorkflows(id, sharedWorkflowsIds, includeData); const execution = await getExecutionInWorkflows(id, sharedWorkflowsIds, includeData);
// execution was not found
if (!execution) { if (!execution) {
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const telemetryData = { void InternalHooksManager.getInstance().onUserRetrievedExecution({
user_id: req.user.id, user_id: req.user.id,
public_api: true, public_api: true,
}; });
void InternalHooksManager.getInstance().onUserRetrievedExecution(telemetryData);
return res.json(execution); return res.json(execution);
}, },
@ -106,10 +91,7 @@ export = {
// user does not have workflows hence no executions // user does not have workflows hence no executions
// or the execution he is trying to access belongs to a workflow he does not own // or the execution he is trying to access belongs to a workflow he does not own
if (!sharedWorkflowsIds.length) { if (!sharedWorkflowsIds.length) {
return res.status(200).json({ return res.status(200).json({ data: [], nextCursor: null });
data: [],
nextCursor: null,
});
} }
// get running workflows so we exclude them from the result // get running workflows so we exclude them from the result
@ -134,12 +116,10 @@ export = {
const count = await getExecutionsCount(filters); const count = await getExecutionsCount(filters);
const telemetryData = { void InternalHooksManager.getInstance().onUserRetrievedAllExecutions({
user_id: req.user.id, user_id: req.user.id,
public_api: true, public_api: true,
}; });
void InternalHooksManager.getInstance().onUserRetrievedAllExecutions(telemetryData);
return res.json({ return res.json({
data: executions, data: executions,

View file

@ -1,23 +1,20 @@
import { parse } from 'flatted'; import { parse } from 'flatted';
import { In, Not, ObjectLiteral, LessThan, IsNull } from 'typeorm'; import { In, Not, ObjectLiteral, LessThan, IsNull } from 'typeorm';
import { Db, IExecutionFlattedDb, IExecutionResponseApi } from '../../../..'; import { Db, IExecutionFlattedDb, IExecutionResponseApi } from '../../../..';
import { ExecutionStatus } from '../../../types'; import { ExecutionStatus } from '../../../types';
function prepareExecutionData( function prepareExecutionData(
execution: IExecutionFlattedDb | undefined, execution: IExecutionFlattedDb | undefined,
): IExecutionResponseApi | undefined { ): IExecutionResponseApi | undefined {
if (execution === undefined) { if (!execution) return undefined;
return undefined;
}
if (!execution.data) { // @ts-ignore
return execution; if (!execution.data) return execution;
}
return { return {
...execution, ...execution,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment data: parse(execution.data) as object,
data: parse(execution.data),
}; };
} }
@ -32,11 +29,12 @@ function getStatusCondition(status: ExecutionStatus): ObjectLiteral {
condition.stoppedAt = Not(IsNull()); condition.stoppedAt = Not(IsNull());
condition.finished = false; condition.finished = false;
} }
return condition; return condition;
} }
function getExecutionSelectableProperties(includeData?: boolean): Array<keyof IExecutionFlattedDb> { function getExecutionSelectableProperties(includeData?: boolean): Array<keyof IExecutionFlattedDb> {
const returnData: Array<keyof IExecutionFlattedDb> = [ const selectFields: Array<keyof IExecutionFlattedDb> = [
'id', 'id',
'mode', 'mode',
'retryOf', 'retryOf',
@ -47,10 +45,10 @@ function getExecutionSelectableProperties(includeData?: boolean): Array<keyof IE
'waitTill', 'waitTill',
'finished', 'finished',
]; ];
if (includeData) {
returnData.push('data'); if (includeData) selectFields.push('data');
}
return returnData; return selectFields;
} }
export async function getExecutions(data: { export async function getExecutions(data: {
@ -92,6 +90,7 @@ export async function getExecutionsCount(data: {
}, },
take: data.limit, take: data.limit,
}); });
return executions; return executions;
} }
@ -107,9 +106,13 @@ export async function getExecutionInWorkflows(
workflowId: In(workflows), workflowId: In(workflows),
}, },
}); });
return prepareExecutionData(execution); return prepareExecutionData(execution);
} }
export async function deleteExecution(execution: IExecutionResponseApi | undefined): Promise<void> { export async function deleteExecution(
await Db.collections.Execution.remove(execution as IExecutionFlattedDb); execution: IExecutionResponseApi | undefined,
): Promise<IExecutionFlattedDb> {
// @ts-ignore
return Db.collections.Execution.remove(execution);
} }

View file

@ -1,5 +1,7 @@
import express = require('express'); import express from 'express';
import { FindManyOptions, In } from 'typeorm'; import { FindManyOptions, In } from 'typeorm';
import { ActiveWorkflowRunner, Db } from '../../../..'; import { ActiveWorkflowRunner, Db } from '../../../..';
import config = require('../../../../../config'); import config = require('../../../../../config');
import { WorkflowEntity } from '../../../../databases/entities/WorkflowEntity'; import { WorkflowEntity } from '../../../../databases/entities/WorkflowEntity';
@ -30,25 +32,24 @@ export = {
createWorkflow: [ createWorkflow: [
authorize(['owner', 'member']), authorize(['owner', 'member']),
async (req: WorkflowRequest.Create, res: express.Response): Promise<express.Response> => { async (req: WorkflowRequest.Create, res: express.Response): Promise<express.Response> => {
let workflow = req.body; const workflow = req.body;
workflow.active = false; workflow.active = false;
// if the workflow does not have a start node, add it.
if (!hasStartNode(workflow)) { if (!hasStartNode(workflow)) {
workflow.nodes.push(getStartNode()); workflow.nodes.push(getStartNode());
} }
const role = await getWorkflowOwnerRole();
await replaceInvalidCredentials(workflow); await replaceInvalidCredentials(workflow);
workflow = await createWorkflow(workflow, req.user, role); const role = await getWorkflowOwnerRole();
await externalHooks.run('workflow.afterCreate', [workflow]); const createdWorkflow = await createWorkflow(workflow, req.user, role);
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, workflow, true);
return res.json(workflow); await externalHooks.run('workflow.afterCreate', [createdWorkflow]);
void InternalHooksManager.getInstance().onWorkflowCreated(req.user.id, createdWorkflow, true);
return res.json(createdWorkflow);
}, },
], ],
deleteWorkflow: [ deleteWorkflow: [
@ -61,16 +62,12 @@ export = {
if (!sharedWorkflow) { if (!sharedWorkflow) {
// user trying to access a workflow he does not own // user trying to access a workflow he does not own
// or workflow does not exist // or workflow does not exist
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const workflowRunner = ActiveWorkflowRunner.getInstance();
if (sharedWorkflow.workflow.active) { if (sharedWorkflow.workflow.active) {
// deactivate before deleting // deactivate before deleting
await workflowRunner.remove(id.toString()); await ActiveWorkflowRunner.getInstance().remove(id.toString());
} }
await Db.collections.Workflow.delete(id); await Db.collections.Workflow.delete(id);
@ -91,17 +88,13 @@ export = {
if (!sharedWorkflow) { if (!sharedWorkflow) {
// user trying to access a workflow he does not own // user trying to access a workflow he does not own
// or workflow does not exist // or workflow does not exist
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const telemetryData = { void InternalHooksManager.getInstance().onUserRetrievedWorkflow({
user_id: req.user.id, user_id: req.user.id,
public_api: true, public_api: true,
}; });
void InternalHooksManager.getInstance().onUserRetrievedWorkflow(telemetryData);
return res.json(sharedWorkflow.workflow); return res.json(sharedWorkflow.workflow);
}, },
@ -158,12 +151,10 @@ export = {
count = await getWorkflowsCount(query); count = await getWorkflowsCount(query);
} }
const telemetryData = { void InternalHooksManager.getInstance().onUserRetrievedAllWorkflows({
user_id: req.user.id, user_id: req.user.id,
public_api: true, public_api: true,
}; });
void InternalHooksManager.getInstance().onUserRetrievedAllWorkflows(telemetryData);
return res.json({ return res.json({
data: workflows, data: workflows,
@ -187,18 +178,13 @@ export = {
if (!sharedWorkflow) { if (!sharedWorkflow) {
// user trying to access a workflow he does not own // user trying to access a workflow he does not own
// or workflow does not exist // or workflow does not exist
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
// if the workflow does not have a start node, add it.
// else there is nothing you can do in IU
if (!hasStartNode(updateData)) { if (!hasStartNode(updateData)) {
updateData.nodes.push(getStartNode()); updateData.nodes.push(getStartNode());
} }
// check credentials for old format
await replaceInvalidCredentials(updateData); await replaceInvalidCredentials(updateData);
const workflowRunner = ActiveWorkflowRunner.getInstance(); const workflowRunner = ActiveWorkflowRunner.getInstance();
@ -215,10 +201,9 @@ export = {
try { try {
await workflowRunner.add(sharedWorkflow.workflowId.toString(), 'update'); await workflowRunner.add(sharedWorkflow.workflowId.toString(), 'update');
} catch (error) { } catch (error) {
// todo if (error instanceof Error) {
// remove the type assertion return res.status(400).json({ message: error.message });
const errorObject = error as unknown as { message: string }; }
return res.status(400).json({ error: errorObject.message });
} }
} }
@ -240,21 +225,19 @@ export = {
if (!sharedWorkflow) { if (!sharedWorkflow) {
// user trying to access a workflow he does not own // user trying to access a workflow he does not own
// or workflow does not exist // or workflow does not exist
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const workflowRunner = ActiveWorkflowRunner.getInstance();
if (!sharedWorkflow.workflow.active) { if (!sharedWorkflow.workflow.active) {
try { try {
await workflowRunner.add(sharedWorkflow.workflowId.toString(), 'activate'); await ActiveWorkflowRunner.getInstance().add(
sharedWorkflow.workflowId.toString(),
'activate',
);
} catch (error) { } catch (error) {
// todo if (error instanceof Error) {
// remove the type assertion return res.status(400).json({ message: error.message });
const errorObject = error as unknown as { message: string }; }
return res.status(400).json({ error: errorObject.message });
} }
// change the status to active in the DB // change the status to active in the DB
@ -279,9 +262,7 @@ export = {
if (!sharedWorkflow) { if (!sharedWorkflow) {
// user trying to access a workflow he does not own // user trying to access a workflow he does not own
// or workflow does not exist // or workflow does not exist
return res.status(404).json({ return res.status(404).json({ message: 'Not Found' });
message: 'Not Found',
});
} }
const workflowRunner = ActiveWorkflowRunner.getInstance(); const workflowRunner = ActiveWorkflowRunner.getInstance();

View file

@ -1,19 +1,19 @@
import { FindManyOptions, In, UpdateResult } from 'typeorm';
import { intersection } from 'lodash'; import { intersection } from 'lodash';
import type { INode } from 'n8n-workflow'; import type { INode } from 'n8n-workflow';
import { FindManyOptions, In, UpdateResult } from 'typeorm';
import { Db } from '../../../..';
import { User } from '../../../../databases/entities/User'; import { User } from '../../../../databases/entities/User';
import { WorkflowEntity } from '../../../../databases/entities/WorkflowEntity'; import { WorkflowEntity } from '../../../../databases/entities/WorkflowEntity';
import { Db } from '../../../..';
import { SharedWorkflow } from '../../../../databases/entities/SharedWorkflow'; import { SharedWorkflow } from '../../../../databases/entities/SharedWorkflow';
import { isInstanceOwner } from '../users/users.service'; import { isInstanceOwner } from '../users/users.service';
import { Role } from '../../../../databases/entities/Role'; import { Role } from '../../../../databases/entities/Role';
export async function getSharedWorkflowIds(user: User): Promise<number[]> { export async function getSharedWorkflowIds(user: User): Promise<number[]> {
const sharedWorkflows = await Db.collections.SharedWorkflow.find({ const sharedWorkflows = await Db.collections.SharedWorkflow.find({
where: { where: { user },
user,
},
}); });
return sharedWorkflows.map((workflow) => workflow.workflowId); return sharedWorkflows.map((workflow) => workflow.workflowId);
} }
@ -21,14 +21,13 @@ export async function getSharedWorkflow(
user: User, user: User,
workflowId?: string | undefined, workflowId?: string | undefined,
): Promise<SharedWorkflow | undefined> { ): Promise<SharedWorkflow | undefined> {
const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ return Db.collections.SharedWorkflow.findOne({
where: { where: {
...(!isInstanceOwner(user) && { user }), ...(!isInstanceOwner(user) && { user }),
...(workflowId && { workflow: { id: workflowId } }), ...(workflowId && { workflow: { id: workflowId } }),
}, },
relations: ['workflow'], relations: ['workflow'],
}); });
return sharedWorkflow;
} }
export async function getSharedWorkflows( export async function getSharedWorkflows(
@ -38,23 +37,19 @@ export async function getSharedWorkflows(
workflowIds?: number[]; workflowIds?: number[];
}, },
): Promise<SharedWorkflow[]> { ): Promise<SharedWorkflow[]> {
const sharedWorkflows = await Db.collections.SharedWorkflow.find({ return Db.collections.SharedWorkflow.find({
where: { where: {
...(!isInstanceOwner(user) && { user }), ...(!isInstanceOwner(user) && { user }),
...(options.workflowIds && { workflow: { id: In(options.workflowIds) } }), ...(options.workflowIds && { workflow: { id: In(options.workflowIds) } }),
}, },
...(options.relations && { relations: options.relations }), ...(options.relations && { relations: options.relations }),
}); });
return sharedWorkflows;
} }
export async function getWorkflowById(id: number): Promise<WorkflowEntity | undefined> { export async function getWorkflowById(id: number): Promise<WorkflowEntity | undefined> {
const workflow = await Db.collections.Workflow.findOne({ return Db.collections.Workflow.findOne({
where: { where: { id },
id,
},
}); });
return workflow;
} }
/** /**
@ -63,9 +58,7 @@ export async function getWorkflowById(id: number): Promise<WorkflowEntity | unde
*/ */
export async function getWorkflowIdsViaTags(tags: string[]): Promise<number[]> { export async function getWorkflowIdsViaTags(tags: string[]): Promise<number[]> {
const dbTags = await Db.collections.Tag.find({ const dbTags = await Db.collections.Tag.find({
where: { where: { name: In(tags) },
name: In(tags),
},
relations: ['workflows'], relations: ['workflows'],
}); });
@ -79,11 +72,11 @@ export async function createWorkflow(
user: User, user: User,
role: Role, role: Role,
): Promise<WorkflowEntity> { ): Promise<WorkflowEntity> {
let savedWorkflow: unknown; return Db.transaction(async (transactionManager) => {
const newWorkflow = new WorkflowEntity(); const newWorkflow = new WorkflowEntity();
Object.assign(newWorkflow, workflow); Object.assign(newWorkflow, workflow);
await Db.transaction(async (transactionManager) => { const savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
savedWorkflow = await transactionManager.save<WorkflowEntity>(newWorkflow);
const newSharedWorkflow = new SharedWorkflow(); const newSharedWorkflow = new SharedWorkflow();
Object.assign(newSharedWorkflow, { Object.assign(newSharedWorkflow, {
role, role,
@ -91,8 +84,9 @@ export async function createWorkflow(
workflow: savedWorkflow, workflow: savedWorkflow,
}); });
await transactionManager.save<SharedWorkflow>(newSharedWorkflow); await transactionManager.save<SharedWorkflow>(newSharedWorkflow);
return savedWorkflow;
}); });
return savedWorkflow as WorkflowEntity;
} }
export async function setWorkflowAsActive(workflow: WorkflowEntity): Promise<UpdateResult> { export async function setWorkflowAsActive(workflow: WorkflowEntity): Promise<UpdateResult> {
@ -110,13 +104,11 @@ export async function deleteWorkflow(workflow: WorkflowEntity): Promise<Workflow
export async function getWorkflows( export async function getWorkflows(
options: FindManyOptions<WorkflowEntity>, options: FindManyOptions<WorkflowEntity>,
): Promise<WorkflowEntity[]> { ): Promise<WorkflowEntity[]> {
const workflows = await Db.collections.Workflow.find(options); return Db.collections.Workflow.find(options);
return workflows;
} }
export async function getWorkflowsCount(options: FindManyOptions<WorkflowEntity>): Promise<number> { export async function getWorkflowsCount(options: FindManyOptions<WorkflowEntity>): Promise<number> {
const count = await Db.collections.Workflow.count(options); return Db.collections.Workflow.count(options);
return count;
} }
export async function updateWorkflow( export async function updateWorkflow(
@ -127,9 +119,11 @@ export async function updateWorkflow(
} }
export function hasStartNode(workflow: WorkflowEntity): boolean { export function hasStartNode(workflow: WorkflowEntity): boolean {
return !( if (!workflow.nodes.length) return false;
!workflow.nodes.length || !workflow.nodes.find((node) => node.type === 'n8n-nodes-base.start')
); const found = workflow.nodes.find((node) => node.type === 'n8n-nodes-base.start');
return Boolean(found);
} }
export function getStartNode(): INode { export function getStartNode(): INode {

View file

@ -1,24 +1,31 @@
/* eslint-disable consistent-return */ /* eslint-disable @typescript-eslint/no-invalid-void-type */
import { RequestHandler } from 'express';
import { PaginatatedRequest } from '../../../types'; import express from 'express';
import { AuthenticatedRequest, PaginatatedRequest } from '../../../types';
import { decodeCursor } from '../services/pagination.service'; import { decodeCursor } from '../services/pagination.service';
type Role = 'member' | 'owner'; export const authorize =
(authorizedRoles: readonly string[]) =>
(
req: AuthenticatedRequest,
res: express.Response,
next: express.NextFunction,
): express.Response | void => {
const { name } = req.user.globalRole;
if (!authorizedRoles.includes(name)) {
return res.status(403).json({ message: 'Forbidden' });
}
export const authorize: (role: Role[]) => RequestHandler = (role: Role[]) => (req, res, next) => {
const {
globalRole: { name: userRole },
} = req.user as { globalRole: { name: Role } };
if (role.includes(userRole)) {
return next(); return next();
} };
return res.status(403).json({
message: 'Forbidden',
});
};
// @ts-ignore export const validCursor = (
export const validCursor: RequestHandler = (req: PaginatatedRequest, res, next) => { req: PaginatatedRequest,
res: express.Response,
next: express.NextFunction,
): express.Response | void => {
if (req.query.cursor) { if (req.query.cursor) {
const { cursor } = req.query; const { cursor } = req.query;
try { try {
@ -36,5 +43,6 @@ export const validCursor: RequestHandler = (req: PaginatatedRequest, res, next)
}); });
} }
} }
next();
return next();
}; };

View file

@ -12,7 +12,6 @@ export const decodeCursor = (cursor: string): PaginationOffsetDecoded | Paginati
}; };
const encodeOffSetPagination = (pagination: OffsetPagination): string | null => { const encodeOffSetPagination = (pagination: OffsetPagination): string | null => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (pagination.numberOfTotalRecords > pagination.offset + pagination.limit) { if (pagination.numberOfTotalRecords > pagination.offset + pagination.limit) {
return Buffer.from( return Buffer.from(
JSON.stringify({ JSON.stringify({

View file

@ -82,7 +82,7 @@ export declare namespace WorkflowRequest {
// ---------------------------------- // ----------------------------------
export declare namespace CredentialRequest { export declare namespace CredentialRequest {
type RequestBody = Partial<{ type CredentialProperties = Partial<{
id: string; // delete if sent id: string; // delete if sent
name: string; name: string;
type: string; type: string;
@ -90,7 +90,7 @@ export declare namespace CredentialRequest {
data: ICredentialDataDecryptedObject; data: ICredentialDataDecryptedObject;
}>; }>;
type Create = AuthenticatedRequest<{}, {}, RequestBody>; type Create = AuthenticatedRequest<{}, {}, CredentialProperties>;
type Get = AuthenticatedRequest<{ id: string }, {}, {}, Record<string, string>>; type Get = AuthenticatedRequest<{ id: string }, {}, {}, Record<string, string>>;
@ -98,7 +98,7 @@ export declare namespace CredentialRequest {
type GetAll = AuthenticatedRequest<{}, {}, {}, { filter: string }>; type GetAll = AuthenticatedRequest<{}, {}, {}, { filter: string }>;
type Update = AuthenticatedRequest<{ id: string }, {}, RequestBody>; type Update = AuthenticatedRequest<{ id: string }, {}, CredentialProperties>;
type NewName = WorkflowRequest.NewName; type NewName = WorkflowRequest.NewName;

View file

@ -1,5 +1,7 @@
import express from 'express'; import express from 'express';
import { UserSettings } from 'n8n-core'; import { UserSettings } from 'n8n-core';
import { Db } from '../../../src'; import { Db } from '../../../src';
import { randomApiKey, randomName, randomString } from '../shared/random'; import { randomApiKey, randomName, randomString } from '../shared/random';
import * as utils from '../shared/utils'; import * as utils from '../shared/utils';
@ -9,13 +11,10 @@ import type { User } from '../../../src/databases/entities/User';
import * as testDb from '../shared/testDb'; import * as testDb from '../shared/testDb';
import { RESPONSE_ERROR_MESSAGES } from '../../../src/constants'; import { RESPONSE_ERROR_MESSAGES } from '../../../src/constants';
jest.mock('../../../src/telemetry');
let app: express.Application; let app: express.Application;
let testDbName = ''; let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let workflowOwnerRole: Role;
let credentialOwnerRole: Role; let credentialOwnerRole: Role;
let saveCredential: SaveCredentialFunction; let saveCredential: SaveCredentialFunction;
@ -30,13 +29,12 @@ beforeAll(async () => {
const [ const [
fetchedGlobalOwnerRole, fetchedGlobalOwnerRole,
fetchedGlobalMemberRole, fetchedGlobalMemberRole,
fetchedWorkflowOwnerRole, _,
fetchedCredentialOwnerRole, fetchedCredentialOwnerRole,
] = await testDb.getAllRoles(); ] = await testDb.getAllRoles();
globalOwnerRole = fetchedGlobalOwnerRole; globalOwnerRole = fetchedGlobalOwnerRole;
globalMemberRole = fetchedGlobalMemberRole; globalMemberRole = fetchedGlobalMemberRole;
workflowOwnerRole = fetchedWorkflowOwnerRole;
credentialOwnerRole = fetchedCredentialOwnerRole; credentialOwnerRole = fetchedCredentialOwnerRole;
saveCredential = affixRoleToSaveCredential(credentialOwnerRole); saveCredential = affixRoleToSaveCredential(credentialOwnerRole);
@ -377,6 +375,7 @@ const credentialPayload = (): CredentialPayload => ({
const dbCredential = () => { const dbCredential = () => {
const credential = credentialPayload(); const credential = credentialPayload();
credential.nodesAccess = [{ nodeType: credential.type }]; credential.nodesAccess = [{ nodeType: credential.type }];
return credential; return credential;
}; };

View file

@ -1,7 +1,7 @@
import express = require('express'); import express from 'express';
import { ActiveWorkflowRunner } from '../../../src'; import { ActiveWorkflowRunner } from '../../../src';
import config = require('../../../config'); import config from '../../../config';
import { Role } from '../../../src/databases/entities/Role'; import { Role } from '../../../src/databases/entities/Role';
import { randomApiKey } from '../shared/random'; import { randomApiKey } from '../shared/random';
@ -11,11 +11,8 @@ import * as testDb from '../shared/testDb';
let app: express.Application; let app: express.Application;
let testDbName = ''; let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let workflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner; let workflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner;
jest.mock('../../../src/telemetry');
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false }); app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false });
const initResult = await testDb.init(); const initResult = await testDb.init();
@ -25,21 +22,29 @@ beforeAll(async () => {
utils.initTestTelemetry(); utils.initTestTelemetry();
utils.initTestLogger(); utils.initTestLogger();
// initializing binary manager leave some async operations open
// TODO mockup binary data mannager to avoid error
await utils.initBinaryManager(); await utils.initBinaryManager();
await utils.initNodeTypes(); await utils.initNodeTypes();
workflowRunner = await utils.initActiveWorkflowRunner(); workflowRunner = await utils.initActiveWorkflowRunner();
}); });
beforeEach(async () => { beforeEach(async () => {
// do not combine calls - shared tables must be cleared first and separately await testDb.truncate(
await testDb.truncate(['SharedCredentials', 'SharedWorkflow'], testDbName); [
await testDb.truncate(['User', 'Workflow', 'Credentials', 'Execution', 'Settings'], testDbName); 'SharedCredentials',
'SharedWorkflow',
'User',
'Workflow',
'Credentials',
'Execution',
'Settings',
],
testDbName,
);
config.set('userManagement.disabled', false); config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('userManagement.emails.mode', 'smtp');
}); });
afterEach(async () => { afterEach(async () => {
@ -50,7 +55,7 @@ afterAll(async () => {
await testDb.terminate(testDbName); await testDb.terminate(testDbName);
}); });
test.skip('GET /executions/:executionId should fail due to missing API Key', async () => { test('GET /executions/:id should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -65,7 +70,7 @@ test.skip('GET /executions/:executionId should fail due to missing API Key', asy
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test.skip('GET /executions/:executionId should fail due to invalid API Key', async () => { test('GET /executions/:id should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -81,7 +86,7 @@ test.skip('GET /executions/:executionId should fail due to invalid API Key', asy
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test.skip('GET /executions/:executionId should get an execution', async () => { test('GET /executions/:id should get an execution', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -93,7 +98,7 @@ test.skip('GET /executions/:executionId should get an execution', async () => {
const workflow = await testDb.createWorkflow({}, owner); const workflow = await testDb.createWorkflow({}, owner);
const execution = await testDb.createSuccessfullExecution(workflow); const execution = await testDb.createSuccessfulExecution(workflow);
const response = await authOwnerAgent.get(`/executions/${execution.id}`); const response = await authOwnerAgent.get(`/executions/${execution.id}`);
@ -122,7 +127,7 @@ test.skip('GET /executions/:executionId should get an execution', async () => {
expect(waitTill).toBeNull(); expect(waitTill).toBeNull();
}); });
test.skip('DELETE /executions/:executionId should fail due to missing API Key', async () => { test('DELETE /executions/:id should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -137,7 +142,7 @@ test.skip('DELETE /executions/:executionId should fail due to missing API Key',
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test.skip('DELETE /executions/:executionId should fail due to invalid API Key', async () => { test('DELETE /executions/:id should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -153,7 +158,7 @@ test.skip('DELETE /executions/:executionId should fail due to invalid API Key',
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test.skip('DELETE /executions/:executionId should delete an execution', async () => { test('DELETE /executions/:id should delete an execution', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -165,7 +170,7 @@ test.skip('DELETE /executions/:executionId should delete an execution', async ()
const workflow = await testDb.createWorkflow({}, owner); const workflow = await testDb.createWorkflow({}, owner);
const execution = await testDb.createSuccessfullExecution(workflow); const execution = await testDb.createSuccessfulExecution(workflow);
const response = await authOwnerAgent.delete(`/executions/${execution.id}`); const response = await authOwnerAgent.delete(`/executions/${execution.id}`);
@ -194,7 +199,7 @@ test.skip('DELETE /executions/:executionId should delete an execution', async ()
expect(waitTill).toBeNull(); expect(waitTill).toBeNull();
}); });
test.skip('GET /executions should fail due to missing API Key', async () => { test('GET /executions should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -209,7 +214,7 @@ test.skip('GET /executions should fail due to missing API Key', async () => {
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test.skip('GET /executions should fail due to invalid API Key', async () => { test('GET /executions should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -225,7 +230,7 @@ test.skip('GET /executions should fail due to invalid API Key', async () => {
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test.skip('GET /executions should retrieve all successfull executions', async () => { test('GET /executions should retrieve all successfull executions', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -237,7 +242,7 @@ test.skip('GET /executions should retrieve all successfull executions', async ()
const workflow = await testDb.createWorkflow({}, owner); const workflow = await testDb.createWorkflow({}, owner);
const successfullExecution = await testDb.createSuccessfullExecution(workflow); const successfullExecution = await testDb.createSuccessfulExecution(workflow);
await testDb.createErrorExecution(workflow); await testDb.createErrorExecution(workflow);
@ -272,7 +277,7 @@ test.skip('GET /executions should retrieve all successfull executions', async ()
expect(waitTill).toBeNull(); expect(waitTill).toBeNull();
}); });
test.skip('GET /executions should retrieve all error executions', async () => { test('GET /executions should retrieve all error executions', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -284,7 +289,7 @@ test.skip('GET /executions should retrieve all error executions', async () => {
const workflow = await testDb.createWorkflow({}, owner); const workflow = await testDb.createWorkflow({}, owner);
await testDb.createSuccessfullExecution(workflow); await testDb.createSuccessfulExecution(workflow);
const errorExecution = await testDb.createErrorExecution(workflow); const errorExecution = await testDb.createErrorExecution(workflow);
@ -319,7 +324,7 @@ test.skip('GET /executions should retrieve all error executions', async () => {
expect(waitTill).toBeNull(); expect(waitTill).toBeNull();
}); });
test.skip('GET /executions should return all waiting executions', async () => { test('GET /executions should return all waiting executions', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -331,7 +336,7 @@ test.skip('GET /executions should return all waiting executions', async () => {
const workflow = await testDb.createWorkflow({}, owner); const workflow = await testDb.createWorkflow({}, owner);
await testDb.createSuccessfullExecution(workflow); await testDb.createSuccessfulExecution(workflow);
await testDb.createErrorExecution(workflow); await testDb.createErrorExecution(workflow);
@ -368,7 +373,7 @@ test.skip('GET /executions should return all waiting executions', async () => {
expect(new Date(waitTill).getTime()).toBeGreaterThan(Date.now() - 1000); expect(new Date(waitTill).getTime()).toBeGreaterThan(Date.now() - 1000);
}); });
test.skip('GET /executions should retrieve all executions of specific workflow', async () => { test('GET /executions should retrieve all executions of specific workflow', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -384,10 +389,10 @@ test.skip('GET /executions should retrieve all executions of specific workflow',
2, 2,
workflow, workflow,
// @ts-ignore // @ts-ignore
testDb.createSuccessfullExecution, testDb.createSuccessfulExecution,
); );
// @ts-ignore // @ts-ignore
await testDb.createManyExecutions(2, workflow2, testDb.createSuccessfullExecution); await testDb.createManyExecutions(2, workflow2, testDb.createSuccessfulExecution);
const response = await authOwnerAgent.get(`/executions`).query({ const response = await authOwnerAgent.get(`/executions`).query({
workflowId: workflow.id.toString(), workflowId: workflow.id.toString(),

View file

@ -1,7 +1,7 @@
import express = require('express'); import express from 'express';
import { ActiveWorkflowRunner, Db } from '../../../src'; import { ActiveWorkflowRunner, Db } from '../../../src';
import config = require('../../../config'); import config from '../../../config';
import { Role } from '../../../src/databases/entities/Role'; import { Role } from '../../../src/databases/entities/Role';
import { randomApiKey } from '../shared/random'; import { randomApiKey } from '../shared/random';
@ -14,11 +14,8 @@ let testDbName = '';
let globalOwnerRole: Role; let globalOwnerRole: Role;
let globalMemberRole: Role; let globalMemberRole: Role;
let workflowOwnerRole: Role; let workflowOwnerRole: Role;
let credentialOwnerRole: Role;
let workflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner; let workflowRunner: ActiveWorkflowRunner.ActiveWorkflowRunner;
jest.mock('../../../src/telemetry');
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false }); app = await utils.initTestServer({ endpointGroups: ['publicApi'], applyAuth: false });
const initResult = await testDb.init(); const initResult = await testDb.init();
@ -28,13 +25,11 @@ beforeAll(async () => {
fetchedGlobalOwnerRole, fetchedGlobalOwnerRole,
fetchedGlobalMemberRole, fetchedGlobalMemberRole,
fetchedWorkflowOwnerRole, fetchedWorkflowOwnerRole,
fetchedCredentialOwnerRole,
] = await testDb.getAllRoles(); ] = await testDb.getAllRoles();
globalOwnerRole = fetchedGlobalOwnerRole; globalOwnerRole = fetchedGlobalOwnerRole;
globalMemberRole = fetchedGlobalMemberRole; globalMemberRole = fetchedGlobalMemberRole;
workflowOwnerRole = fetchedWorkflowOwnerRole; workflowOwnerRole = fetchedWorkflowOwnerRole;
credentialOwnerRole = fetchedCredentialOwnerRole;
utils.initTestTelemetry(); utils.initTestTelemetry();
utils.initTestLogger(); utils.initTestLogger();
@ -43,13 +38,13 @@ beforeAll(async () => {
}); });
beforeEach(async () => { beforeEach(async () => {
// do not combine calls - shared tables must be cleared first and separately await testDb.truncate(
await testDb.truncate(['SharedCredentials', 'SharedWorkflow'], testDbName); ['SharedCredentials', 'SharedWorkflow', 'User', 'Workflow', 'Credentials'],
await testDb.truncate(['User', 'Workflow', 'Credentials'], testDbName); testDbName,
);
config.set('userManagement.disabled', false); config.set('userManagement.disabled', false);
config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.isInstanceOwnerSetUp', true);
config.set('userManagement.emails.mode', 'smtp');
}); });
afterEach(async () => { afterEach(async () => {
@ -352,7 +347,7 @@ test('GET /workflows should return all workflows for owner', async () => {
} }
}); });
test('GET /workflows/:workflowId should fail due to missing API Key', async () => { test('GET /workflows/:id should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
owner.apiKey = null; owner.apiKey = null;
@ -369,7 +364,7 @@ test('GET /workflows/:workflowId should fail due to missing API Key', async () =
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('GET /workflows/:workflowId should fail due to invalid API Key', async () => { test('GET /workflows/:id should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -386,7 +381,7 @@ test('GET /workflows/:workflowId should fail due to invalid API Key', async () =
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('GET /workflows/:workflowId should fail due to non existing workflow', async () => { test('GET /workflows/:id should fail due to non existing workflow', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -401,7 +396,7 @@ test('GET /workflows/:workflowId should fail due to non existing workflow', asyn
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
}); });
test('GET /workflows/:workflowId should retrieve workflow', async () => { test('GET /workflows/:id should retrieve workflow', async () => {
const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
const authAgent = utils.createAgent(app, { const authAgent = utils.createAgent(app, {
@ -432,7 +427,7 @@ test('GET /workflows/:workflowId should retrieve workflow', async () => {
expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); expect(updatedAt).toEqual(workflow.updatedAt.toISOString());
}); });
test('GET /workflows/:workflowId should retrieve non-owned workflow for owner', async () => { test('GET /workflows/:id should retrieve non-owned workflow for owner', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const member = await testDb.createUser({ globalRole: globalMemberRole }); const member = await testDb.createUser({ globalRole: globalMemberRole });
@ -464,7 +459,7 @@ test('GET /workflows/:workflowId should retrieve non-owned workflow for owner',
expect(updatedAt).toEqual(workflow.updatedAt.toISOString()); expect(updatedAt).toEqual(workflow.updatedAt.toISOString());
}); });
test('DELETE /workflows/:workflowId should fail due to missing API Key', async () => { test('DELETE /workflows/:id should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -479,7 +474,7 @@ test('DELETE /workflows/:workflowId should fail due to missing API Key', async (
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('DELETE /workflows/:workflowId should fail due to invalid API Key', async () => { test('DELETE /workflows/:id should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -496,7 +491,7 @@ test('DELETE /workflows/:workflowId should fail due to invalid API Key', async (
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('DELETE /workflows/:workflowId should fail due to non existing workflow', async () => { test('DELETE /workflows/:id should fail due to non existing workflow', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -511,7 +506,7 @@ test('DELETE /workflows/:workflowId should fail due to non existing workflow', a
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
}); });
test('DELETE /workflows/:workflowId should delete the workflow', async () => { test('DELETE /workflows/:id should delete the workflow', async () => {
const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
const authAgent = utils.createAgent(app, { const authAgent = utils.createAgent(app, {
@ -549,7 +544,7 @@ test('DELETE /workflows/:workflowId should delete the workflow', async () => {
expect(sharedWorkflow).toBeUndefined(); expect(sharedWorkflow).toBeUndefined();
}); });
test('DELETE /workflows/:workflowId should delete non-owned workflow when owner', async () => { test('DELETE /workflows/:id should delete non-owned workflow when owner', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const member = await testDb.createUser({ globalRole: globalMemberRole }); const member = await testDb.createUser({ globalRole: globalMemberRole });
@ -588,7 +583,7 @@ test('DELETE /workflows/:workflowId should delete non-owned workflow when owner'
expect(sharedWorkflow).toBeUndefined(); expect(sharedWorkflow).toBeUndefined();
}); });
test('POST /workflows/:workflowId/activate should fail due to missing API Key', async () => { test('POST /workflows/:id/activate should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -603,7 +598,7 @@ test('POST /workflows/:workflowId/activate should fail due to missing API Key',
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('POST /workflows/:workflowId/activate should fail due to invalid API Key', async () => { test('POST /workflows/:id/activate should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -620,7 +615,7 @@ test('POST /workflows/:workflowId/activate should fail due to invalid API Key',
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('POST /workflows/:workflowId/activate should fail due to non existing workflow', async () => { test('POST /workflows/:id/activate should fail due to non existing workflow', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -635,7 +630,7 @@ test('POST /workflows/:workflowId/activate should fail due to non existing workf
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
}); });
test('POST /workflows/:workflowId/activate should fail due to trying to activate a workflow without a trigger', async () => { test('POST /workflows/:id/activate should fail due to trying to activate a workflow without a trigger', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -652,7 +647,7 @@ test('POST /workflows/:workflowId/activate should fail due to trying to activate
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
}); });
test.skip('POST /workflows/:workflowId/activate should set workflow as active', async () => { test.skip('POST /workflows/:id/activate should set workflow as active', async () => {
const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
const authAgent = utils.createAgent(app, { const authAgent = utils.createAgent(app, {
@ -696,7 +691,7 @@ test.skip('POST /workflows/:workflowId/activate should set workflow as active',
expect(await workflowRunner.isActive(workflow.id.toString())).toBe(true); expect(await workflowRunner.isActive(workflow.id.toString())).toBe(true);
}); });
test.skip('POST /workflows/:workflowId/activate should set non-owned workflow as active when owner', async () => { test.skip('POST /workflows/:id/activate should set non-owned workflow as active when owner', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const member = await testDb.createUser({ globalRole: globalMemberRole }); const member = await testDb.createUser({ globalRole: globalMemberRole });
@ -750,7 +745,7 @@ test.skip('POST /workflows/:workflowId/activate should set non-owned workflow as
expect(await workflowRunner.isActive(workflow.id.toString())).toBe(true); expect(await workflowRunner.isActive(workflow.id.toString())).toBe(true);
}); });
test('POST /workflows/:workflowId/deactivate should fail due to missing API Key', async () => { test('POST /workflows/:id/deactivate should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -765,7 +760,7 @@ test('POST /workflows/:workflowId/deactivate should fail due to missing API Key'
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('POST /workflows/:workflowId/deactivate should fail due to invalid API Key', async () => { test('POST /workflows/:id/deactivate should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -782,7 +777,7 @@ test('POST /workflows/:workflowId/deactivate should fail due to invalid API Key'
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('POST /workflows/:workflowId/deactivate should fail due to non existing workflow', async () => { test('POST /workflows/:id/deactivate should fail due to non existing workflow', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -797,7 +792,7 @@ test('POST /workflows/:workflowId/deactivate should fail due to non existing wor
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
}); });
test('POST /workflows/:workflowId/deactivate should deactive workflow', async () => { test('POST /workflows/:id/deactivate should deactive workflow', async () => {
const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() }); const member = await testDb.createUser({ globalRole: globalMemberRole, apiKey: randomApiKey() });
const authAgent = utils.createAgent(app, { const authAgent = utils.createAgent(app, {
@ -841,7 +836,7 @@ test('POST /workflows/:workflowId/deactivate should deactive workflow', async ()
expect(await workflowRunner.isActive(workflow.id.toString())).toBe(false); expect(await workflowRunner.isActive(workflow.id.toString())).toBe(false);
}); });
test('POST /workflows/:workflowId/deactivate should deactive non-owned workflow when owner', async () => { test('POST /workflows/:id/deactivate should deactive non-owned workflow when owner', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const member = await testDb.createUser({ globalRole: globalMemberRole }); const member = await testDb.createUser({ globalRole: globalMemberRole });
@ -904,7 +899,7 @@ test('POST /workflows should fail due to missing API Key', async () => {
version: 1, version: 1,
}); });
const response = await authOwnerAgent.post(`/workflows`); const response = await authOwnerAgent.post('/workflows');
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
@ -921,7 +916,7 @@ test('POST /workflows should fail due to invalid API Key', async () => {
version: 1, version: 1,
}); });
const response = await authOwnerAgent.post(`/workflows`); const response = await authOwnerAgent.post('/workflows');
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
@ -936,7 +931,7 @@ test('POST /workflows should fail due to invalid body', async () => {
version: 1, version: 1,
}); });
const response = await authOwnerAgent.post(`/workflows`).send({}); const response = await authOwnerAgent.post('/workflows').send({});
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
}); });
@ -974,7 +969,7 @@ test('POST /workflows should create workflow', async () => {
}, },
}; };
const response = await authAgent.post(`/workflows`).send(payload); const response = await authAgent.post('/workflows').send(payload);
expect(response.statusCode).toBe(200); expect(response.statusCode).toBe(200);
@ -1005,7 +1000,7 @@ test('POST /workflows should create workflow', async () => {
expect(sharedWorkflow?.role).toEqual(workflowOwnerRole); expect(sharedWorkflow?.role).toEqual(workflowOwnerRole);
}); });
test('PUT /workflows/:workflowId should fail due to missing API Key', async () => { test('PUT /workflows/:id should fail due to missing API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const owner = await testDb.createUser({ globalRole: globalOwnerRole });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -1020,7 +1015,7 @@ test('PUT /workflows/:workflowId should fail due to missing API Key', async () =
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('PUT /workflows/:workflowId should fail due to invalid API Key', async () => { test('PUT /workflows/:id should fail due to invalid API Key', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
owner.apiKey = 'abcXYZ'; owner.apiKey = 'abcXYZ';
@ -1037,7 +1032,7 @@ test('PUT /workflows/:workflowId should fail due to invalid API Key', async () =
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}); });
test('PUT /workflows/:workflowId should fail due to non existing workflow', async () => { test('PUT /workflows/:id should fail due to non existing workflow', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -1073,7 +1068,7 @@ test('PUT /workflows/:workflowId should fail due to non existing workflow', asyn
expect(response.statusCode).toBe(404); expect(response.statusCode).toBe(404);
}); });
test('PUT /workflows/:workflowId should fail due to invalid body', async () => { test('PUT /workflows/:id should fail due to invalid body', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const authOwnerAgent = utils.createAgent(app, { const authOwnerAgent = utils.createAgent(app, {
@ -1108,7 +1103,7 @@ test('PUT /workflows/:workflowId should fail due to invalid body', async () => {
expect(response.statusCode).toBe(400); expect(response.statusCode).toBe(400);
}); });
test('PUT /workflows/:workflowId should update workflow', async () => { test('PUT /workflows/:id should update workflow', async () => {
const member = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const member = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const workflow = await testDb.createWorkflow({}, member); const workflow = await testDb.createWorkflow({}, member);
@ -1182,7 +1177,7 @@ test('PUT /workflows/:workflowId should update workflow', async () => {
); );
}); });
test('PUT /workflows/:workflowId should update non-owned workflow if owner', async () => { test('PUT /workflows/:id should update non-owned workflow if owner', async () => {
const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() }); const owner = await testDb.createUser({ globalRole: globalOwnerRole, apiKey: randomApiKey() });
const member = await testDb.createUser({ globalRole: globalMemberRole }); const member = await testDb.createUser({ globalRole: globalMemberRole });

View file

@ -323,8 +323,7 @@ export async function createManyExecutions(
} }
/** /**
* Store a execution in the DB and assigns it to a workflow. * Store a execution in the DB and assign it to a workflow.
* @param user user to assign the workflow to
*/ */
export async function createExecution( export async function createExecution(
attributes: Partial<ExecutionEntity> = {}, attributes: Partial<ExecutionEntity> = {},
@ -346,48 +345,41 @@ export async function createExecution(
} }
/** /**
* Store a execution in the DB and assigns it to a workflow. * Store a successful execution in the DB and assign it to a workflow.
* @param user user to assign the workflow to
*/ */
export async function createSuccessfullExecution(workflow: WorkflowEntity) { export async function createSuccessfulExecution(workflow: WorkflowEntity) {
const execution = await createExecution( return await createExecution(
{ {
finished: true, finished: true,
}, },
workflow, workflow,
); );
return execution;
} }
/** /**
* Store a execution in the DB and assigns it to a workflow. * Store an error execution in the DB and assign it to a workflow.
* @param user user to assign the workflow to
*/ */
export async function createErrorExecution(workflow: WorkflowEntity) { export async function createErrorExecution(workflow: WorkflowEntity) {
const execution = await createExecution( return await createExecution(
{ {
finished: false, finished: false,
stoppedAt: new Date(), stoppedAt: new Date(),
}, },
workflow, workflow,
); );
return execution;
} }
/** /**
* Store a execution in the DB and assigns it to a workflow. * Store a waiting execution in the DB and assign it to a workflow.
* @param user user to assign the workflow to
*/ */
export async function createWaitingExecution(workflow: WorkflowEntity) { export async function createWaitingExecution(workflow: WorkflowEntity) {
const execution = await createExecution( return await createExecution(
{ {
finished: false, finished: false,
waitTill: new Date(), waitTill: new Date(),
}, },
workflow, workflow,
); );
return execution;
} }
// ---------------------------------- // ----------------------------------
@ -417,7 +409,7 @@ export async function createManyWorkflows(
} }
/** /**
* Store a workflow in the DB (without a trigger) and optionally assigns it to a user. * Store a workflow in the DB (without a trigger) and optionally assign it to a user.
* @param user user to assign the workflow to * @param user user to assign the workflow to
*/ */
export async function createWorkflow(attributes: Partial<WorkflowEntity> = {}, user?: User) { export async function createWorkflow(attributes: Partial<WorkflowEntity> = {}, user?: User) {
@ -450,7 +442,7 @@ export async function createWorkflow(attributes: Partial<WorkflowEntity> = {}, u
} }
/** /**
* Store a workflow in the DB (with a trigger) and optionally assigns it to a user. * Store a workflow in the DB (with a trigger) and optionally assign it to a user.
* @param user user to assign the workflow to * @param user user to assign the workflow to
*/ */
export async function createWorkflowWithTrigger( export async function createWorkflowWithTrigger(
@ -487,6 +479,7 @@ export async function createWorkflowWithTrigger(
}, },
user, user,
); );
return workflow; return workflow;
} }

View file

@ -34,3 +34,12 @@ export type SaveCredentialFunction = (
export type PostgresSchemaSection = { export type PostgresSchemaSection = {
[K in 'host' | 'port' | 'schema' | 'user' | 'password']: { env: string }; [K in 'host' | 'port' | 'schema' | 'user' | 'password']: { env: string };
}; };
export interface TriggerTime {
mode: string;
hour: number;
minute: number;
dayOfMonth: number;
weekeday: number;
[key: string]: string | number;
}

View file

@ -1,5 +1,6 @@
import { randomBytes } from 'crypto'; import { randomBytes } from 'crypto';
import { existsSync } from 'fs'; import { existsSync } from 'fs';
import express from 'express'; import express from 'express';
import superagent from 'superagent'; import superagent from 'superagent';
import request from 'supertest'; import request from 'supertest';
@ -7,6 +8,9 @@ import { URL } from 'url';
import bodyParser from 'body-parser'; import bodyParser from 'body-parser';
import util from 'util'; import util from 'util';
import { createTestAccount } from 'nodemailer'; import { createTestAccount } from 'nodemailer';
import { set } from 'lodash';
import { CronJob } from 'cron';
import { BinaryDataManager, UserSettings } from 'n8n-core';
import { import {
ICredentialType, ICredentialType,
IDataObject, IDataObject,
@ -19,10 +23,8 @@ import {
ITriggerResponse, ITriggerResponse,
LoggerProxy, LoggerProxy,
} from 'n8n-workflow'; } from 'n8n-workflow';
import { BinaryDataManager, UserSettings } from 'n8n-core';
import { CronJob } from 'cron';
import config = require('../../../config'); import config from '../../../config';
import { AUTHLESS_ENDPOINTS, PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } from './constants'; import { AUTHLESS_ENDPOINTS, PUBLIC_API_REST_PATH_SEGMENT, REST_PATH_SEGMENT } from './constants';
import { AUTH_COOKIE_NAME } from '../../../src/constants'; import { AUTH_COOKIE_NAME } from '../../../src/constants';
import { addRoutes as authMiddleware } from '../../../src/UserManagement/routes'; import { addRoutes as authMiddleware } from '../../../src/UserManagement/routes';
@ -43,20 +45,17 @@ import { issueJWT } from '../../../src/UserManagement/auth/jwt';
import { getLogger } from '../../../src/Logger'; import { getLogger } from '../../../src/Logger';
import { credentialsController } from '../../../src/api/credentials.api'; import { credentialsController } from '../../../src/api/credentials.api';
import { loadPublicApiVersions } from '../../../src/PublicApi/'; import { loadPublicApiVersions } from '../../../src/PublicApi/';
import type { User } from '../../../src/databases/entities/User';
import type { ApiPath, EndpointGroup, PostgresSchemaSection, SmtpTestAccount } from './types';
import { Telemetry } from '../../../src/telemetry';
import type { N8nApp } from '../../../src/UserManagement/Interfaces';
import { set } from 'lodash';
interface TriggerTime {
mode: string;
hour: number;
minute: number;
dayOfMonth: number;
weekeday: number;
[key: string]: string | number;
}
import * as UserManagementMailer from '../../../src/UserManagement/email/UserManagementMailer'; import * as UserManagementMailer from '../../../src/UserManagement/email/UserManagementMailer';
import type { User } from '../../../src/databases/entities/User';
import type {
ApiPath,
EndpointGroup,
PostgresSchemaSection,
SmtpTestAccount,
TriggerTime,
} from './types';
import type { N8nApp } from '../../../src/UserManagement/Interfaces';
/** /**
* Initialize a test server. * Initialize a test server.
@ -75,7 +74,7 @@ export async function initTestServer({
app: express(), app: express(),
restEndpoint: REST_PATH_SEGMENT, restEndpoint: REST_PATH_SEGMENT,
publicApiEndpoint: PUBLIC_API_REST_PATH_SEGMENT, publicApiEndpoint: PUBLIC_API_REST_PATH_SEGMENT,
...(endpointGroups?.includes('credentials') ? { externalHooks: ExternalHooks() } : {}), externalHooks: {},
}; };
testServer.app.use(bodyParser.json()); testServer.app.use(bodyParser.json());
@ -90,13 +89,17 @@ export async function initTestServer({
if (!endpointGroups) return testServer.app; if (!endpointGroups) return testServer.app;
if (endpointGroups.includes('credentials')) {
testServer.externalHooks = ExternalHooks();
}
const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups); const [routerEndpoints, functionEndpoints] = classifyEndpointGroups(endpointGroups);
if (routerEndpoints.length) { if (routerEndpoints.length) {
const { apiRouters } = await loadPublicApiVersions(testServer.publicApiEndpoint); const { apiRouters } = await loadPublicApiVersions(testServer.publicApiEndpoint);
const map: Record<string, express.Router | express.Router[]> = { const map: Record<string, express.Router | express.Router[]> = {
credentials: credentialsController, credentials: credentialsController,
publicApi: apiRouters publicApi: apiRouters,
}; };
for (const group of routerEndpoints) { for (const group of routerEndpoints) {
@ -743,8 +746,8 @@ export async function initNodeTypes() {
}, },
}, },
}; };
const nodeTypes = NodeTypes();
await nodeTypes.init(types); await NodeTypes().init(types);
} }
/** /**
@ -759,7 +762,7 @@ export function initTestLogger() {
*/ */
export async function initBinaryManager() { export async function initBinaryManager() {
const binaryDataConfig = config.getEnv('binaryDataManager'); const binaryDataConfig = config.getEnv('binaryDataManager');
await BinaryDataManager.init(binaryDataConfig, true); await BinaryDataManager.init(binaryDataConfig);
} }
/** /**
@ -783,7 +786,7 @@ export function initConfigFile() {
*/ */
export function createAgent( export function createAgent(
app: express.Application, app: express.Application,
options?: { apiPath?: ApiPath; version?: string | number; auth: boolean; user: User }, options?: { auth: boolean; user: User; apiPath?: ApiPath; version?: string | number },
) { ) {
const agent = request.agent(app); const agent = request.agent(app);