feat(core): Remove all floating promises. Enforce @typescript-eslint/no-floating-promises (#6281)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-05-24 00:01:45 +00:00 committed by GitHub
parent 5d2f4746ea
commit e046f656fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 94 additions and 120 deletions

View file

@ -459,7 +459,6 @@ const config = (module.exports = {
'@typescript-eslint/naming-convention': 'off', '@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-duplicate-imports': 'off', '@typescript-eslint/no-duplicate-imports': 'off',
'@typescript-eslint/no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-loop-func': 'off', '@typescript-eslint/no-loop-func': 'off',
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-shadow': 'off', '@typescript-eslint/no-shadow': 'off',

View file

@ -3,7 +3,6 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
/* eslint-disable no-restricted-syntax */ /* eslint-disable no-restricted-syntax */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
@ -306,8 +305,7 @@ export class ActiveWorkflowRunner {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const executionMode = 'webhook'; const executionMode = 'webhook';
// @ts-ignore void WebhookHelpers.executeWebhook(
WebhookHelpers.executeWebhook(
workflow, workflow,
webhookData, webhookData,
workflowData, workflowData,
@ -627,7 +625,7 @@ export class ActiveWorkflowRunner {
): void => { ): void => {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
Logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`); Logger.debug(`Received event to trigger execution for workflow "${workflow.name}"`);
WorkflowHelpers.saveStaticData(workflow); void WorkflowHelpers.saveStaticData(workflow);
const executePromise = this.runWorkflow( const executePromise = this.runWorkflow(
workflowData, workflowData,
node, node,
@ -638,14 +636,14 @@ export class ActiveWorkflowRunner {
); );
if (donePromise) { if (donePromise) {
executePromise.then((executionId) => { void executePromise.then((executionId) => {
this.activeExecutions this.activeExecutions
.getPostExecutePromise(executionId) .getPostExecutePromise(executionId)
.then(donePromise.resolve) .then(donePromise.resolve)
.catch(donePromise.reject); .catch(donePromise.reject);
}); });
} else { } else {
executePromise.catch(Logger.error); void executePromise.catch(Logger.error);
} }
}; };
@ -684,7 +682,7 @@ export class ActiveWorkflowRunner {
): void => { ): void => {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions // eslint-disable-next-line @typescript-eslint/restrict-template-expressions
Logger.debug(`Received trigger for workflow "${workflow.name}"`); Logger.debug(`Received trigger for workflow "${workflow.name}"`);
WorkflowHelpers.saveStaticData(workflow); void WorkflowHelpers.saveStaticData(workflow);
// eslint-disable-next-line id-denylist // eslint-disable-next-line id-denylist
const executePromise = this.runWorkflow( const executePromise = this.runWorkflow(
workflowData, workflowData,
@ -696,7 +694,7 @@ export class ActiveWorkflowRunner {
); );
if (donePromise) { if (donePromise) {
executePromise.then((executionId) => { void executePromise.then((executionId) => {
this.activeExecutions this.activeExecutions
.getPostExecutePromise(executionId) .getPostExecutePromise(executionId)
.then(donePromise.resolve) .then(donePromise.resolve)

View file

@ -552,12 +552,12 @@ export class Server extends AbstractServer {
// Check for basic auth credentials if activated // Check for basic auth credentials if activated
if (config.getEnv('security.basicAuth.active')) { if (config.getEnv('security.basicAuth.active')) {
await setupBasicAuth(this.app, config, authIgnoreRegex); setupBasicAuth(this.app, config, authIgnoreRegex);
} }
// Check for and validate JWT if configured // Check for and validate JWT if configured
if (config.getEnv('security.jwtAuth.active')) { if (config.getEnv('security.jwtAuth.active')) {
await setupExternalJWTAuth(this.app, config, authIgnoreRegex); setupExternalJWTAuth(this.app, config, authIgnoreRegex);
} }
// ---------------------------------------- // ----------------------------------------

View file

@ -272,8 +272,7 @@ export class TestWebhooks {
if (!foundWebhook) { if (!foundWebhook) {
// As it removes all webhooks of the workflow execute only once // As it removes all webhooks of the workflow execute only once
// eslint-disable-next-line @typescript-eslint/no-floating-promises void activeWebhooks.removeWorkflow(workflow);
activeWebhooks.removeWorkflow(workflow);
} }
foundWebhook = true; foundWebhook = true;

View file

@ -3,8 +3,6 @@
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-floating-promises */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { import {
ErrorReporterProxy as ErrorReporter, ErrorReporterProxy as ErrorReporter,
LoggerProxy as Logger, LoggerProxy as Logger,
@ -40,10 +38,10 @@ export class WaitTracker {
constructor() { constructor() {
// Poll every 60 seconds a list of upcoming executions // Poll every 60 seconds a list of upcoming executions
this.mainTimer = setInterval(() => { this.mainTimer = setInterval(() => {
this.getWaitingExecutions(); void this.getWaitingExecutions();
}, 60000); }, 60000);
this.getWaitingExecutions(); void this.getWaitingExecutions();
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types

View file

@ -130,8 +130,7 @@ export class WaitingWebhooks {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const executionMode = 'webhook'; const executionMode = 'webhook';
// eslint-disable-next-line @typescript-eslint/no-floating-promises void WebhookHelpers.executeWebhook(
WebhookHelpers.executeWebhook(
workflow, workflow,
webhookData, webhookData,
workflowData as IWorkflowDb, workflowData as IWorkflowDb,

View file

@ -5,7 +5,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-shadow */ /* eslint-disable @typescript-eslint/no-shadow */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/prefer-optional-chain */ /* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
@ -75,8 +74,7 @@ export class WorkflowRunner {
* The process did send a hook message so execute the appropriate hook * The process did send a hook message so execute the appropriate hook
*/ */
processHookMessage(workflowHooks: WorkflowHooks, hookData: IProcessMessageDataHook) { processHookMessage(workflowHooks: WorkflowHooks, hookData: IProcessMessageDataHook) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises void workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters);
workflowHooks.executeHookFunctions(hookData.hook, hookData.parameters);
} }
/** /**
@ -305,11 +303,8 @@ export class WorkflowRunner {
error, error,
error.node, error.node,
); );
additionalData.hooks await additionalData.hooks.executeHookFunctions('workflowExecuteAfter', [failedExecution]);
.executeHookFunctions('workflowExecuteAfter', [failedExecution])
.then(() => {
this.activeExecutions.remove(executionId, failedExecution); this.activeExecutions.remove(executionId, failedExecution);
});
return executionId; return executionId;
} }
@ -382,7 +377,7 @@ export class WorkflowRunner {
if (workflowTimeout > 0) { if (workflowTimeout > 0) {
const timeout = Math.min(workflowTimeout, config.getEnv('executions.maxTimeout')) * 1000; // as seconds const timeout = Math.min(workflowTimeout, config.getEnv('executions.maxTimeout')) * 1000; // as seconds
executionTimeout = setTimeout(() => { executionTimeout = setTimeout(() => {
this.activeExecutions.stopExecution(executionId, 'timeout'); void this.activeExecutions.stopExecution(executionId, 'timeout');
}, timeout); }, timeout);
} }
@ -395,15 +390,15 @@ export class WorkflowRunner {
fullRunData.status = this.activeExecutions.getStatus(executionId); fullRunData.status = this.activeExecutions.getStatus(executionId);
this.activeExecutions.remove(executionId, fullRunData); this.activeExecutions.remove(executionId, fullRunData);
}) })
.catch((error) => { .catch(async (error) =>
this.processError( this.processError(
error, error,
new Date(), new Date(),
data.executionMode, data.executionMode,
executionId, executionId,
additionalData.hooks, additionalData.hooks,
),
); );
});
} catch (error) { } catch (error) {
await this.processError( await this.processError(
error, error,
@ -467,7 +462,7 @@ export class WorkflowRunner {
// Normally also workflow should be supplied here but as it only used for sending // Normally also workflow should be supplied here but as it only used for sending
// data to editor-UI is not needed. // data to editor-UI is not needed.
hooks.executeHookFunctions('workflowExecuteBefore', []); await hooks.executeHookFunctions('workflowExecuteBefore', []);
} catch (error) { } catch (error) {
// We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the // We use "getWorkflowHooksWorkerExecuter" as "getWorkflowHooksWorkerMain" does not contain the
// "workflowExecuteAfter" which we require. // "workflowExecuteAfter" which we require.
@ -585,7 +580,7 @@ export class WorkflowRunner {
this.activeExecutions.remove(executionId, runData); this.activeExecutions.remove(executionId, runData);
// Normally also static data should be supplied here but as it only used for sending // Normally also static data should be supplied here but as it only used for sending
// data to editor-UI is not needed. // data to editor-UI is not needed.
hooks.executeHookFunctions('workflowExecuteAfter', [runData]); await hooks.executeHookFunctions('workflowExecuteAfter', [runData]);
try { try {
// Check if this execution data has to be removed from database // Check if this execution data has to be removed from database
// based on workflow settings. // based on workflow settings.
@ -668,7 +663,7 @@ export class WorkflowRunner {
let workflowTimeout = workflowSettings.executionTimeout ?? config.getEnv('executions.timeout'); // initialize with default let workflowTimeout = workflowSettings.executionTimeout ?? config.getEnv('executions.timeout'); // initialize with default
const processTimeoutFunction = (timeout: number) => { const processTimeoutFunction = (timeout: number) => {
this.activeExecutions.stopExecution(executionId, 'timeout'); void this.activeExecutions.stopExecution(executionId, 'timeout');
executionTimeout = setTimeout(() => subprocess.kill(), Math.max(timeout * 0.2, 5000)); // minimum 5 seconds executionTimeout = setTimeout(() => subprocess.kill(), Math.max(timeout * 0.2, 5000)); // minimum 5 seconds
}; };
@ -732,7 +727,7 @@ export class WorkflowRunner {
const timeoutError = new WorkflowOperationError('Workflow execution timed out!'); const timeoutError = new WorkflowOperationError('Workflow execution timed out!');
// No need to add hook here as the subprocess takes care of calling the hooks // No need to add hook here as the subprocess takes care of calling the hooks
this.processError(timeoutError, startedAt, data.executionMode, executionId); await this.processError(timeoutError, startedAt, data.executionMode, executionId);
} else if (message.type === 'startExecution') { } else if (message.type === 'startExecution') {
const executionId = await this.activeExecutions.add(message.data.runData); const executionId = await this.activeExecutions.add(message.data.runData);
childExecutionIds.push(executionId); childExecutionIds.push(executionId);

View file

@ -497,8 +497,7 @@ process.on('message', async (message: IProcessMessage) => {
status: 'canceled', status: 'canceled',
}; };
// eslint-disable-next-line @typescript-eslint/no-floating-promises await workflowRunner.sendHookToParentProcess('workflowExecuteAfter', [runData]);
workflowRunner.sendHookToParentProcess('workflowExecuteAfter', [runData]);
} }
await sendToParentProcess(message.type === 'timeout' ? message.type : 'end', { await sendToParentProcess(message.type === 'timeout' ? message.type : 'end', {

View file

@ -282,7 +282,6 @@ export class Start extends BaseCommand {
if (dbType === 'sqlite') { if (dbType === 'sqlite') {
const shouldRunVacuum = config.getEnv('database.sqlite.executeVacuumOnStartup'); const shouldRunVacuum = config.getEnv('database.sqlite.executeVacuumOnStartup');
if (shouldRunVacuum) { if (shouldRunVacuum) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
await Db.collections.Execution.query('VACUUM;'); await Db.collections.Execution.query('VACUUM;');
} }
} }
@ -360,8 +359,7 @@ export class Start extends BaseCommand {
this.openBrowser(); this.openBrowser();
} else if (key.charCodeAt(0) === 3) { } else if (key.charCodeAt(0) === 3) {
// Ctrl + c got pressed // Ctrl + c got pressed
// eslint-disable-next-line @typescript-eslint/no-floating-promises void this.stopProcess();
this.stopProcess();
} else { } else {
// When anything else got pressed, record it and send it on enter into the child process // When anything else got pressed, record it and send it on enter into the child process
// eslint-disable-next-line no-lonely-if // eslint-disable-next-line no-lonely-if

View file

@ -51,8 +51,7 @@ export class Worker extends BaseCommand {
LoggerProxy.info('Stopping n8n...'); LoggerProxy.info('Stopping n8n...');
// Stop accepting new jobs // Stop accepting new jobs
// eslint-disable-next-line @typescript-eslint/no-floating-promises await Worker.jobQueue.pause(true);
Worker.jobQueue.pause(true);
try { try {
await this.externalHooks.run('n8n.stop', []); await this.externalHooks.run('n8n.stop', []);
@ -239,8 +238,9 @@ export class Worker extends BaseCommand {
const queue = Container.get(Queue); const queue = Container.get(Queue);
await queue.init(); await queue.init();
Worker.jobQueue = queue.getBullObjectInstance(); Worker.jobQueue = queue.getBullObjectInstance();
// eslint-disable-next-line @typescript-eslint/no-floating-promises void Worker.jobQueue.process(flags.concurrency, async (job) =>
Worker.jobQueue.process(flags.concurrency, async (job) => this.runJob(job, this.nodeTypes)); this.runJob(job, this.nodeTypes),
);
this.logger.info('\nn8n worker is now ready'); this.logger.info('\nn8n worker is now ready');
this.logger.info(` * Version: ${N8N_VERSION}`); this.logger.info(` * Version: ${N8N_VERSION}`);

View file

@ -6,7 +6,7 @@ import { compare } from 'bcryptjs';
import type { Config } from '@/config'; import type { Config } from '@/config';
import { basicAuthAuthorizationError } from '@/ResponseHelper'; import { basicAuthAuthorizationError } from '@/ResponseHelper';
export const setupBasicAuth = async (app: Application, config: Config, authIgnoreRegex: RegExp) => { export const setupBasicAuth = (app: Application, config: Config, authIgnoreRegex: RegExp) => {
const basicAuthUser = config.getEnv('security.basicAuth.user'); const basicAuthUser = config.getEnv('security.basicAuth.user');
if (basicAuthUser === '') { if (basicAuthUser === '') {
throw new Error('Basic auth is activated but no user got defined. Please set one!'); throw new Error('Basic auth is activated but no user got defined. Please set one!');

View file

@ -4,11 +4,7 @@ import jwks from 'jwks-rsa';
import type { Config } from '@/config'; import type { Config } from '@/config';
import { jwtAuthAuthorizationError } from '@/ResponseHelper'; import { jwtAuthAuthorizationError } from '@/ResponseHelper';
export const setupExternalJWTAuth = async ( export const setupExternalJWTAuth = (app: Application, config: Config, authIgnoreRegex: RegExp) => {
app: Application,
config: Config,
authIgnoreRegex: RegExp,
) => {
const jwtAuthHeader = config.getEnv('security.jwtAuth.jwtHeader'); const jwtAuthHeader = config.getEnv('security.jwtAuth.jwtHeader');
if (jwtAuthHeader === '') { if (jwtAuthHeader === '') {
throw new Error('JWT auth is activated but no request header was defined. Please set one!'); throw new Error('JWT auth is activated but no request header was defined. Please set one!');

View file

@ -25,7 +25,7 @@ let sharingSpy: jest.SpyInstance<boolean>;
beforeAll(async () => { beforeAll(async () => {
const app = await utils.initTestServer({ endpointGroups: ['credentials'] }); const app = await utils.initTestServer({ endpointGroups: ['credentials'] });
utils.initConfigFile(); await utils.initConfigFile();
const globalOwnerRole = await testDb.getGlobalOwnerRole(); const globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();

View file

@ -31,7 +31,7 @@ let authAgent: AuthAgent;
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['credentials'] }); app = await utils.initTestServer({ endpointGroups: ['credentials'] });
utils.initConfigFile(); await utils.initConfigFile();
globalOwnerRole = await testDb.getGlobalOwnerRole(); globalOwnerRole = await testDb.getGlobalOwnerRole();
globalMemberRole = await testDb.getGlobalMemberRole(); globalMemberRole = await testDb.getGlobalMemberRole();

View file

@ -103,7 +103,7 @@ beforeAll(async () => {
mockedSyslog.createClient.mockImplementation(() => new syslog.Client()); mockedSyslog.createClient.mockImplementation(() => new syslog.Client());
utils.initConfigFile(); await utils.initConfigFile();
config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter'); config.set('eventBus.logWriter.logBaseName', 'n8n-test-logwriter');
config.set('eventBus.logWriter.keepLogCount', 1); config.set('eventBus.logWriter.keepLogCount', 1);
config.set('userManagement.disabled', false); config.set('userManagement.disabled', false);

View file

@ -58,7 +58,7 @@ beforeAll(async () => {
defaultLdapConfig.bindingAdminPassword, defaultLdapConfig.bindingAdminPassword,
); );
utils.initConfigFile(); await utils.initConfigFile();
await setCurrentAuthenticationMethod('email'); await setCurrentAuthenticationMethod('email');
}); });

View file

@ -54,7 +54,7 @@ beforeAll(async () => {
ownerShell = await testDb.createUserShell(globalOwnerRole); ownerShell = await testDb.createUserShell(globalOwnerRole);
authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell); authOwnerShellAgent = utils.createAuthAgent(app)(ownerShell);
utils.initConfigFile(); await utils.initConfigFile();
}); });
beforeEach(async () => { beforeEach(async () => {

View file

@ -25,7 +25,7 @@ beforeAll(async () => {
enablePublicAPI: true, enablePublicAPI: true,
}); });
utils.initConfigFile(); await utils.initConfigFile();
const [globalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] = const [globalOwnerRole, fetchedGlobalMemberRole, _, fetchedCredentialOwnerRole] =
await testDb.getAllRoles(); await testDb.getAllRoles();
@ -51,7 +51,7 @@ beforeAll(async () => {
saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole); saveCredential = testDb.affixRoleToSaveCredential(credentialOwnerRole);
utils.initCredentialsTypes(); await utils.initCredentialsTypes();
}); });
beforeEach(async () => { beforeEach(async () => {

View file

@ -81,7 +81,7 @@ afterAll(async () => {
const testWithAPIKey = const testWithAPIKey =
(method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => { (method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => {
authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey }); void authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey });
const response = await authOwnerAgent[method](url); const response = await authOwnerAgent[method](url);
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}; };

View file

@ -40,7 +40,7 @@ beforeAll(async () => {
apiKey: randomApiKey(), apiKey: randomApiKey(),
}); });
utils.initConfigFile(); await utils.initConfigFile();
await utils.initNodeTypes(); await utils.initNodeTypes();
workflowRunner = await utils.initActiveWorkflowRunner(); workflowRunner = await utils.initActiveWorkflowRunner();
}); });
@ -76,7 +76,7 @@ afterAll(async () => {
const testWithAPIKey = const testWithAPIKey =
(method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => { (method: 'get' | 'post' | 'put' | 'delete', url: string, apiKey: string | null) => async () => {
authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey }); void authOwnerAgent.set({ 'X-N8N-API-KEY': apiKey });
const response = await authOwnerAgent[method](url); const response = await authOwnerAgent[method](url);
expect(response.statusCode).toBe(401); expect(response.statusCode).toBe(401);
}; };

View file

@ -295,7 +295,7 @@ const classifyEndpointGroups = (endpointGroups: EndpointGroup[]) => {
*/ */
export async function initActiveWorkflowRunner(): Promise<ActiveWorkflowRunner> { export async function initActiveWorkflowRunner(): Promise<ActiveWorkflowRunner> {
const workflowRunner = Container.get(ActiveWorkflowRunner); const workflowRunner = Container.get(ActiveWorkflowRunner);
workflowRunner.init(); await workflowRunner.init();
return workflowRunner; return workflowRunner;
} }
@ -654,12 +654,12 @@ export async function initBinaryManager() {
/** /**
* Initialize a user settings config file if non-existent. * Initialize a user settings config file if non-existent.
*/ */
export function initConfigFile() { export async function initConfigFile() {
const settingsPath = UserSettings.getUserSettingsPath(); const settingsPath = UserSettings.getUserSettingsPath();
if (!existsSync(settingsPath)) { if (!existsSync(settingsPath)) {
const userSettings = { encryptionKey: randomBytes(24).toString('base64') }; const userSettings = { encryptionKey: randomBytes(24).toString('base64') };
UserSettings.writeUserSettings(userSettings, settingsPath); await UserSettings.writeUserSettings(userSettings, settingsPath);
} }
} }
@ -677,7 +677,7 @@ export function createAgent(
const agent = request.agent(app); const agent = request.agent(app);
if (options?.apiPath === undefined || options?.apiPath === 'internal') { if (options?.apiPath === undefined || options?.apiPath === 'internal') {
agent.use(prefix(REST_PATH_SEGMENT)); void agent.use(prefix(REST_PATH_SEGMENT));
if (options?.auth && options?.user) { if (options?.auth && options?.user) {
const { token } = issueJWT(options.user); const { token } = issueJWT(options.user);
agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`); agent.jar.setCookie(`${AUTH_COOKIE_NAME}=${token}`);
@ -685,10 +685,10 @@ export function createAgent(
} }
if (options?.apiPath === 'public') { if (options?.apiPath === 'public') {
agent.use(prefix(`${PUBLIC_API_REST_PATH_SEGMENT}/v${options?.version}`)); void agent.use(prefix(`${PUBLIC_API_REST_PATH_SEGMENT}/v${options?.version}`));
if (options?.auth && options?.user.apiKey) { if (options?.auth && options?.user.apiKey) {
agent.set({ 'X-N8N-API-KEY': options.user.apiKey }); void agent.set({ 'X-N8N-API-KEY': options.user.apiKey });
} }
} }

View file

@ -514,7 +514,7 @@ describe('UserManagementMailer expect NodeMailer.verifyConnection', () => {
test('not be called when SMTP not set up', async () => { test('not be called when SMTP not set up', async () => {
const userManagementMailer = new UserManagementMailer(); const userManagementMailer = new UserManagementMailer();
// NodeMailer.verifyConnection gets called only explicitly // NodeMailer.verifyConnection gets called only explicitly
expect(async () => userManagementMailer.verifyConnection()).rejects.toThrow(); await expect(async () => userManagementMailer.verifyConnection()).rejects.toThrow();
expect(NodeMailer.prototype.verifyConnection).toHaveBeenCalledTimes(0); expect(NodeMailer.prototype.verifyConnection).toHaveBeenCalledTimes(0);
}); });

View file

@ -21,7 +21,7 @@ const licenseLike = {
beforeAll(async () => { beforeAll(async () => {
app = await utils.initTestServer({ endpointGroups: ['variables'] }); app = await utils.initTestServer({ endpointGroups: ['variables'] });
utils.initConfigFile(); await utils.initConfigFile();
utils.mockInstance(License, licenseLike); utils.mockInstance(License, licenseLike);
ownerUser = await testDb.createOwner(); ownerUser = await testDb.createOwner();

View file

@ -3,7 +3,7 @@ import { ActiveExecutions } from '@/ActiveExecutions';
import { mocked } from 'jest-mock'; import { mocked } from 'jest-mock';
import PCancelable from 'p-cancelable'; import PCancelable from 'p-cancelable';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import type { IDeferredPromise, IExecuteResponsePromiseData, IRun } from 'n8n-workflow'; import type { IExecuteResponsePromiseData, IRun } from 'n8n-workflow';
import { createDeferredPromise } from 'n8n-workflow'; import { createDeferredPromise } from 'n8n-workflow';
import type { IWorkflowExecutionDataProcess } from '@/Interfaces'; import type { IWorkflowExecutionDataProcess } from '@/Interfaces';
@ -86,7 +86,7 @@ describe('ActiveExecutions', () => {
const fakeResponse = { data: { resultData: { runData: {} } } }; const fakeResponse = { data: { resultData: { runData: {} } } };
activeExecutions.resolveResponsePromise(FAKE_EXECUTION_ID, fakeResponse); activeExecutions.resolveResponsePromise(FAKE_EXECUTION_ID, fakeResponse);
expect(deferredPromise.promise()).resolves.toEqual(fakeResponse); await expect(deferredPromise.promise()).resolves.toEqual(fakeResponse);
}); });
test('Should remove an existing execution', async () => { test('Should remove an existing execution', async () => {
@ -108,11 +108,11 @@ describe('ActiveExecutions', () => {
const fakeOutput = mockFullRunData(); const fakeOutput = mockFullRunData();
activeExecutions.remove(executionId, fakeOutput); activeExecutions.remove(executionId, fakeOutput);
expect(postExecutePromise).resolves.toEqual(fakeOutput); await expect(postExecutePromise).resolves.toEqual(fakeOutput);
}); });
test('Should throw error when trying to create a promise with invalid execution', async () => { test('Should throw error when trying to create a promise with invalid execution', async () => {
expect(activeExecutions.getPostExecutePromise(FAKE_EXECUTION_ID)).rejects.toThrow(); await expect(activeExecutions.getPostExecutePromise(FAKE_EXECUTION_ID)).rejects.toThrow();
}); });
test('Should call function to cancel execution when asked to stop', async () => { test('Should call function to cancel execution when asked to stop', async () => {
@ -122,7 +122,7 @@ describe('ActiveExecutions', () => {
const cancellablePromise = mockCancelablePromise(); const cancellablePromise = mockCancelablePromise();
cancellablePromise.cancel = cancelExecution; cancellablePromise.cancel = cancelExecution;
activeExecutions.attachWorkflowExecution(executionId, cancellablePromise); activeExecutions.attachWorkflowExecution(executionId, cancellablePromise);
activeExecutions.stopExecution(executionId); void activeExecutions.stopExecution(executionId);
expect(cancelExecution).toHaveBeenCalledTimes(1); expect(cancelExecution).toHaveBeenCalledTimes(1);
}); });
@ -156,12 +156,7 @@ function mockFullRunData(): IRun {
}; };
} }
async function mockCancelablePromise(): PCancelable<IRun> { // eslint-disable-next-line @typescript-eslint/promise-function-async
return new PCancelable(async (resolve) => { const mockCancelablePromise = () => new PCancelable<IRun>((resolve) => resolve());
resolve(); // eslint-disable-next-line @typescript-eslint/promise-function-async
}); const mockDeferredPromise = () => createDeferredPromise<IExecuteResponsePromiseData>();
}
async function mockDeferredPromise(): Promise<IDeferredPromise<IExecuteResponsePromiseData>> {
return createDeferredPromise<IExecuteResponsePromiseData>();
}

View file

@ -213,7 +213,7 @@ describe('PermissionChecker.check()', () => {
const workflow = new Workflow(workflowDetails); const workflow = new Workflow(workflowDetails);
expect(PermissionChecker.check(workflow, member.id)).rejects.toThrow(); await expect(PermissionChecker.check(workflow, member.id)).rejects.toThrow();
}); });
}); });
@ -274,11 +274,7 @@ describe('PermissionChecker.checkSubworkflowExecutePolicy', () => {
// Check description // Check description
try { try {
await PermissionChecker.checkSubworkflowExecutePolicy( await PermissionChecker.checkSubworkflowExecutePolicy(subworkflow, '', 'abcde');
subworkflow,
subworkflow.settings.userId as string,
'abcde',
);
} catch (error) { } catch (error) {
if (error instanceof SubworkflowOperationError) { if (error instanceof SubworkflowOperationError) {
expect(error.description).toBe( expect(error.description).toBe(

View file

@ -73,7 +73,7 @@ describe('PostHog', () => {
const ph = new PostHogClient(); const ph = new PostHogClient();
await ph.init(instanceId); await ph.init(instanceId);
ph.getFeatureFlags({ await ph.getFeatureFlags({
id: userId, id: userId,
createdAt, createdAt,
}); });

View file

@ -1,3 +1,4 @@
import type RudderStack from '@rudderstack/rudder-sdk-node';
import { Telemetry } from '@/telemetry'; import { Telemetry } from '@/telemetry';
import config from '@/config'; import config from '@/config';
import { flushPromises } from './Helpers'; import { flushPromises } from './Helpers';
@ -18,8 +19,13 @@ describe('Telemetry', () => {
let startPulseSpy: jest.SpyInstance; let startPulseSpy: jest.SpyInstance;
const spyTrack = jest.spyOn(Telemetry.prototype, 'track').mockName('track'); const spyTrack = jest.spyOn(Telemetry.prototype, 'track').mockName('track');
const mockRudderStack: Pick<RudderStack, 'flush' | 'identify' | 'track'> = {
flush: (resolve) => resolve?.(),
identify: (data, resolve) => resolve?.(),
track: (data, resolve) => resolve?.(),
};
let telemetry: Telemetry; let telemetry: Telemetry;
const n8nVersion = '0.0.0';
const instanceId = 'Telemetry unit test'; const instanceId = 'Telemetry unit test';
const testDateTime = new Date('2022-01-01 00:00:00'); const testDateTime = new Date('2022-01-01 00:00:00');
@ -33,35 +39,31 @@ describe('Telemetry', () => {
config.set('deployment.type', 'n8n-testing'); config.set('deployment.type', 'n8n-testing');
}); });
afterAll(() => { afterAll(async () => {
jest.clearAllTimers(); jest.clearAllTimers();
jest.useRealTimers(); jest.useRealTimers();
startPulseSpy.mockRestore(); startPulseSpy.mockRestore();
telemetry.trackN8nStop(); await telemetry.trackN8nStop();
}); });
beforeEach(() => { beforeEach(async () => {
spyTrack.mockClear(); spyTrack.mockClear();
const postHog = new PostHogClient(); const postHog = new PostHogClient();
postHog.init(instanceId); await postHog.init(instanceId);
telemetry = new Telemetry(postHog, mock()); telemetry = new Telemetry(postHog, mock());
telemetry.setInstanceId(instanceId); telemetry.setInstanceId(instanceId);
(telemetry as any).rudderStack = { (telemetry as any).rudderStack = mockRudderStack;
flush: () => {},
identify: () => {},
track: () => {},
};
}); });
afterEach(() => { afterEach(async () => {
telemetry.trackN8nStop(); await telemetry.trackN8nStop();
}); });
describe('trackN8nStop', () => { describe('trackN8nStop', () => {
test('should call track method', () => { test('should call track method', async () => {
telemetry.trackN8nStop(); await telemetry.trackN8nStop();
expect(spyTrack).toHaveBeenCalledTimes(1); expect(spyTrack).toHaveBeenCalledTimes(1);
}); });
}); });

View file

@ -48,21 +48,21 @@ describe('WorkflowCredentials', () => {
jest.clearAllMocks(); jest.clearAllMocks();
}); });
test('Should return an error if any node has no credential ID', () => { test('Should return an error if any node has no credential ID', async () => {
const credentials = noIdNode.credentials!.test; const credentials = noIdNode.credentials!.test;
const expectedError = new Error( const expectedError = new Error(
`Credentials with name "${credentials.name}" for type "test" miss an ID.`, `Credentials with name "${credentials.name}" for type "test" miss an ID.`,
); );
expect(WorkflowCredentials([noIdNode])).rejects.toEqual(expectedError); await expect(WorkflowCredentials([noIdNode])).rejects.toEqual(expectedError);
expect(mocked(Db.collections.Credentials.findOneBy)).toHaveBeenCalledTimes(0); expect(mocked(Db.collections.Credentials.findOneBy)).toHaveBeenCalledTimes(0);
}); });
test('Should return an error if credentials cannot be found in the DB', () => { test('Should return an error if credentials cannot be found in the DB', async () => {
const credentials = notFoundNode.credentials!.test; const credentials = notFoundNode.credentials!.test;
const expectedError = new Error( const expectedError = new Error(
`Could not find credentials for type "test" with ID "${credentials.id}".`, `Could not find credentials for type "test" with ID "${credentials.id}".`,
); );
expect(WorkflowCredentials([notFoundNode])).rejects.toEqual(expectedError); await expect(WorkflowCredentials([notFoundNode])).rejects.toEqual(expectedError);
expect(mocked(Db.collections.Credentials.findOneBy)).toHaveBeenCalledTimes(1); expect(mocked(Db.collections.Credentials.findOneBy)).toHaveBeenCalledTimes(1);
}); });

View file

@ -26,14 +26,14 @@ describe('MeController', () => {
describe('updateCurrentUser', () => { describe('updateCurrentUser', () => {
it('should throw BadRequestError if email is missing in the payload', async () => { it('should throw BadRequestError if email is missing in the payload', async () => {
const req = mock<MeRequest.UserUpdate>({}); const req = mock<MeRequest.UserUpdate>({});
expect(controller.updateCurrentUser(req, mock())).rejects.toThrowError( await expect(controller.updateCurrentUser(req, mock())).rejects.toThrowError(
new BadRequestError('Email is mandatory'), new BadRequestError('Email is mandatory'),
); );
}); });
it('should throw BadRequestError if email is invalid', async () => { it('should throw BadRequestError if email is invalid', async () => {
const req = mock<MeRequest.UserUpdate>({ body: { email: 'invalid-email' } }); const req = mock<MeRequest.UserUpdate>({ body: { email: 'invalid-email' } });
expect(controller.updateCurrentUser(req, mock())).rejects.toThrowError( await expect(controller.updateCurrentUser(req, mock())).rejects.toThrowError(
new BadRequestError('Invalid email address'), new BadRequestError('Invalid email address'),
); );
}); });
@ -103,7 +103,7 @@ describe('MeController', () => {
user: mock({ password: undefined }), user: mock({ password: undefined }),
body: { currentPassword: '', newPassword: '' }, body: { currentPassword: '', newPassword: '' },
}); });
expect(controller.updatePassword(req, mock())).rejects.toThrowError( await expect(controller.updatePassword(req, mock())).rejects.toThrowError(
new BadRequestError('Requesting user not set up.'), new BadRequestError('Requesting user not set up.'),
); );
}); });
@ -113,7 +113,7 @@ describe('MeController', () => {
user: mock({ password: passwordHash }), user: mock({ password: passwordHash }),
body: { currentPassword: 'not_old_password', newPassword: '' }, body: { currentPassword: 'not_old_password', newPassword: '' },
}); });
expect(controller.updatePassword(req, mock())).rejects.toThrowError( await expect(controller.updatePassword(req, mock())).rejects.toThrowError(
new BadRequestError('Provided current password is incorrect.'), new BadRequestError('Provided current password is incorrect.'),
); );
}); });
@ -125,7 +125,7 @@ describe('MeController', () => {
user: mock({ password: passwordHash }), user: mock({ password: passwordHash }),
body: { currentPassword: 'old_password', newPassword }, body: { currentPassword: 'old_password', newPassword },
}); });
expect(controller.updatePassword(req, mock())).rejects.toThrowError( await expect(controller.updatePassword(req, mock())).rejects.toThrowError(
new BadRequestError(errorMessage), new BadRequestError(errorMessage),
); );
}); });

View file

@ -40,7 +40,7 @@ describe('OwnerController', () => {
describe('preSetup', () => { describe('preSetup', () => {
it('should throw a BadRequestError if the instance owner is already setup', async () => { it('should throw a BadRequestError if the instance owner is already setup', async () => {
config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(true); config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(true);
expect(controller.preSetup()).rejects.toThrowError( await expect(controller.preSetup()).rejects.toThrowError(
new BadRequestError('Instance owner already setup'), new BadRequestError('Instance owner already setup'),
); );
}); });
@ -58,7 +58,7 @@ describe('OwnerController', () => {
describe('setupOwner', () => { describe('setupOwner', () => {
it('should throw a BadRequestError if the instance owner is already setup', async () => { it('should throw a BadRequestError if the instance owner is already setup', async () => {
config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(true); config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(true);
expect(controller.setupOwner(mock(), mock())).rejects.toThrowError( await expect(controller.setupOwner(mock(), mock())).rejects.toThrowError(
new BadRequestError('Instance owner already setup'), new BadRequestError('Instance owner already setup'),
); );
}); });
@ -66,7 +66,7 @@ describe('OwnerController', () => {
it('should throw a BadRequestError if the email is invalid', async () => { it('should throw a BadRequestError if the email is invalid', async () => {
config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false); config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false);
const req = mock<OwnerRequest.Post>({ body: { email: 'invalid email' } }); const req = mock<OwnerRequest.Post>({ body: { email: 'invalid email' } });
expect(controller.setupOwner(req, mock())).rejects.toThrowError( await expect(controller.setupOwner(req, mock())).rejects.toThrowError(
new BadRequestError('Invalid email address'), new BadRequestError('Invalid email address'),
); );
}); });
@ -76,7 +76,7 @@ describe('OwnerController', () => {
it(password, async () => { it(password, async () => {
config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false); config.getEnv.calledWith('userManagement.isInstanceOwnerSetUp').mockReturnValue(false);
const req = mock<OwnerRequest.Post>({ body: { email: 'valid@email.com', password } }); const req = mock<OwnerRequest.Post>({ body: { email: 'valid@email.com', password } });
expect(controller.setupOwner(req, mock())).rejects.toThrowError( await expect(controller.setupOwner(req, mock())).rejects.toThrowError(
new BadRequestError(errorMessage), new BadRequestError(errorMessage),
); );
}); });
@ -88,7 +88,7 @@ describe('OwnerController', () => {
const req = mock<OwnerRequest.Post>({ const req = mock<OwnerRequest.Post>({
body: { email: 'valid@email.com', password: 'NewPassword123', firstName: '', lastName: '' }, body: { email: 'valid@email.com', password: 'NewPassword123', firstName: '', lastName: '' },
}); });
expect(controller.setupOwner(req, mock())).rejects.toThrowError( await expect(controller.setupOwner(req, mock())).rejects.toThrowError(
new BadRequestError('First and last names are mandatory'), new BadRequestError('First and last names are mandatory'),
); );
}); });

View file

@ -19,7 +19,7 @@ describe('TranslationController', () => {
const req = mock<TranslationRequest.Credential>({ query: { credentialType } }); const req = mock<TranslationRequest.Credential>({ query: { credentialType } });
credentialTypes.recognizes.calledWith(credentialType).mockReturnValue(false); credentialTypes.recognizes.calledWith(credentialType).mockReturnValue(false);
expect(controller.getCredentialTranslation(req)).rejects.toThrowError( await expect(controller.getCredentialTranslation(req)).rejects.toThrowError(
new BadRequestError(`Invalid Credential type: "${credentialType}"`), new BadRequestError(`Invalid Credential type: "${credentialType}"`),
); );
}); });

View file

@ -8,7 +8,7 @@ describe('Basic Auth Middleware', () => {
beforeAll(() => { beforeAll(() => {
app = express(); app = express();
config.set('security.basicAuth', { user: 'jim', password: 'n8n', hash: false }); config.set('security.basicAuth', { user: 'jim', password: 'n8n', hash: false, active: true });
setupBasicAuth(app, config, new RegExp('^/skip-auth')); setupBasicAuth(app, config, new RegExp('^/skip-auth'));
app.get('/test', (req, res) => res.send({ auth: true })); app.get('/test', (req, res) => res.send({ auth: true }));
app.get('/skip-auth', (req, res) => res.send({ auth: false })); app.get('/skip-auth', (req, res) => res.send({ auth: false }));

View file

@ -39,7 +39,7 @@ describe('RoleRepository', () => {
test('should throw otherwise', async () => { test('should throw otherwise', async () => {
entityManager.findOneOrFail.mockRejectedValueOnce(new Error()); entityManager.findOneOrFail.mockRejectedValueOnce(new Error());
expect(async () => roleRepository.findRoleOrFail('global', 'owner')).rejects.toThrow(); await expect(async () => roleRepository.findRoleOrFail('global', 'owner')).rejects.toThrow();
}); });
}); });