From 8f46fd48d2fcb7e916724603b7c59674ac312e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Tue, 26 Apr 2022 11:40:41 +0200 Subject: [PATCH 01/29] :bookmark: Update release version (#3207) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 40b0dd9561..403a37ccc5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n", - "version": "0.168.2", + "version": "0.174.0", "private": true, "homepage": "https://n8n.io", "scripts": { From 8d9e05e3c3ef61cd5a65ec00d3d1474f1195f653 Mon Sep 17 00:00:00 2001 From: Mutasem Aldmour <4711238+mutdmour@users.noreply.github.com> Date: Thu, 28 Apr 2022 17:36:41 +0200 Subject: [PATCH 02/29] fix(editor): Fix bug with touchscreens (#3206) --- .../editor-ui/src/components/mixins/deviceSupportHelpers.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/editor-ui/src/components/mixins/deviceSupportHelpers.ts b/packages/editor-ui/src/components/mixins/deviceSupportHelpers.ts index f47b9e208d..a8506f6edf 100644 --- a/packages/editor-ui/src/components/mixins/deviceSupportHelpers.ts +++ b/packages/editor-ui/src/components/mixins/deviceSupportHelpers.ts @@ -3,7 +3,8 @@ import Vue from 'vue'; export const deviceSupportHelpers = Vue.extend({ data() { return { - isTouchDevice: 'ontouchstart' in window || navigator.maxTouchPoints, + // @ts-ignore msMaxTouchPoints is deprecated but must fix tablet bugs before fixing this.. otherwise breaks touchscreen computers + isTouchDevice: 'ontouchstart' in window || navigator.msMaxTouchPoints, isMacOs: /(ipad|iphone|ipod|mac)/i.test(navigator.platform), // TODO: `platform` deprecated }; }, From 2b008815cad82619a66d4b30d1f79630c82be978 Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Thu, 28 Apr 2022 11:39:38 -0400 Subject: [PATCH 03/29] fix(Sendgrid Node): Fix issue sending attachments (#3213) --- packages/nodes-base/nodes/SendGrid/SendGrid.node.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts b/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts index ca640e89de..2a45e20137 100644 --- a/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts +++ b/packages/nodes-base/nodes/SendGrid/SendGrid.node.ts @@ -456,8 +456,10 @@ export class SendGrid implements INodeType { const binaryProperty = items[i].binary![property]; + const dataBuffer = await this.helpers.getBinaryDataBuffer(i, property); + attachmentsToSend.push({ - content: binaryProperty.data, + content: dataBuffer.toString('base64'), filename: binaryProperty.fileName || 'unknown', type: binaryProperty.mimeType, }); From 5e2589e626db2b97f0cb3ffabc37206a17151118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Ovejero?= Date: Thu, 28 Apr 2022 18:39:57 +0200 Subject: [PATCH 04/29] refactor: Remove reintroduced non-null assertions in `Db` calls (#3162) * :fire: Remove reintroduced non-null assertions * :fire: Remove duplicate cred references * :fire: Remove unneeded `@ts-ignore` * :fire: Remove another `@ts-ignore` * :fire: Remove outdated suite version * :fire: Remove leftover non-null assertion Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> * :fire: Remove more leftovers * :fire: Remove unneeded optional chaining operators Co-authored-by: Ben Hesseldieck <1849459+BHesseldieck@users.noreply.github.com> --- packages/cli/commands/db/revert.ts | 2 +- packages/cli/commands/import/workflow.ts | 2 +- packages/cli/src/ActiveExecutions.ts | 7 +- packages/cli/src/ActiveWorkflowRunner.ts | 12 +- packages/cli/src/Server.ts | 74 ++++----- packages/cli/src/WaitingWebhooks.ts | 2 +- .../cli/src/WorkflowExecuteAdditionalData.ts | 2 +- packages/cli/src/WorkflowHelpers.ts | 6 +- .../cli/test/integration/auth.api.test.ts | 4 +- .../test/integration/auth.endpoints.test.ts | 157 ------------------ .../test/integration/credentials.api.test.ts | 34 ++-- packages/cli/test/integration/me.api.test.ts | 24 +-- .../cli/test/integration/owner.api.test.ts | 6 +- .../integration/passwordReset.api.test.ts | 20 +-- .../cli/test/integration/shared/testDb.ts | 18 +- packages/cli/test/integration/shared/utils.ts | 2 +- .../cli/test/integration/users.api.test.ts | 56 +++---- packages/nodes-base/package.json | 3 - 18 files changed, 132 insertions(+), 299 deletions(-) delete mode 100644 packages/cli/test/integration/auth.endpoints.test.ts diff --git a/packages/cli/commands/db/revert.ts b/packages/cli/commands/db/revert.ts index 585027cf06..193b349b43 100644 --- a/packages/cli/commands/db/revert.ts +++ b/packages/cli/commands/db/revert.ts @@ -28,7 +28,7 @@ export class DbRevertMigrationCommand extends Command { let connection: Connection | undefined; try { await Db.init(); - connection = Db.collections.Credentials?.manager.connection; + connection = Db.collections.Credentials.manager.connection; if (!connection) { throw new Error(`No database connection available.`); diff --git a/packages/cli/commands/import/workflow.ts b/packages/cli/commands/import/workflow.ts index bb277400c1..bf87e680b7 100644 --- a/packages/cli/commands/import/workflow.ts +++ b/packages/cli/commands/import/workflow.ts @@ -82,7 +82,7 @@ export class ImportWorkflowsCommand extends Command { // Make sure the settings exist await UserSettings.prepareUserSettings(); - const credentials = (await Db.collections.Credentials?.find()) ?? []; + const credentials = (await Db.collections.Credentials.find()) ?? []; let totalImported = 0; diff --git a/packages/cli/src/ActiveExecutions.ts b/packages/cli/src/ActiveExecutions.ts index cc2f99f388..cb9da7d65d 100644 --- a/packages/cli/src/ActiveExecutions.ts +++ b/packages/cli/src/ActiveExecutions.ts @@ -70,9 +70,7 @@ export class ActiveExecutions { const execution = ResponseHelper.flattenExecutionData(fullExecutionData); - const executionResult = await Db.collections.Execution!.save( - execution as IExecutionFlattedDb, - ); + const executionResult = await Db.collections.Execution.save(execution as IExecutionFlattedDb); executionId = typeof executionResult.id === 'object' ? // @ts-ignore @@ -87,8 +85,7 @@ export class ActiveExecutions { waitTill: null, }; - // @ts-ignore - await Db.collections.Execution!.update(executionId, execution); + await Db.collections.Execution.update(executionId, execution); } // @ts-ignore diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index a62ab43e2f..9c7af3609d 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -83,7 +83,7 @@ export class ActiveWorkflowRunner { // This is not officially supported but there is no reason // it should not work. // Clear up active workflow table - await Db.collections.Webhook?.clear(); + await Db.collections.Webhook.clear(); } this.activeWorkflows = new ActiveWorkflows(); @@ -189,7 +189,7 @@ export class ActiveWorkflowRunner { path = path.slice(0, -1); } - let webhook = (await Db.collections.Webhook?.findOne({ + let webhook = (await Db.collections.Webhook.findOne({ webhookPath: path, method: httpMethod, })) as IWebhookDb; @@ -200,7 +200,7 @@ export class ActiveWorkflowRunner { // check if a dynamic webhook path exists const pathElements = path.split('/'); webhookId = pathElements.shift(); - const dynamicWebhooks = await Db.collections.Webhook?.find({ + const dynamicWebhooks = await Db.collections.Webhook.find({ webhookId, method: httpMethod, pathLength: pathElements.length, @@ -332,7 +332,7 @@ export class ActiveWorkflowRunner { * @memberof ActiveWorkflowRunner */ async getWebhookMethods(path: string): Promise { - const webhooks = await Db.collections.Webhook?.find({ webhookPath: path }); + const webhooks = await Db.collections.Webhook.find({ webhookPath: path }); // Gather all request methods in string array const webhookMethods: string[] = webhooks.map((webhook) => webhook.method); @@ -443,7 +443,7 @@ export class ActiveWorkflowRunner { try { // eslint-disable-next-line no-await-in-loop - await Db.collections.Webhook?.insert(webhook); + await Db.collections.Webhook.insert(webhook); const webhookExists = await workflow.runWebhookMethod( 'checkExists', webhookData, @@ -556,7 +556,7 @@ export class ActiveWorkflowRunner { workflowId: workflowData.id, } as IWebhookDb; - await Db.collections.Webhook?.delete(webhook); + await Db.collections.Webhook.delete(webhook); } /** diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 68d97c3180..b248b70408 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -756,7 +756,7 @@ class App { const { tags: tagIds } = req.body; if (tagIds?.length && !config.getEnv('workflowTagsDisabled')) { - newWorkflow.tags = await Db.collections.Tag!.findByIds(tagIds, { + newWorkflow.tags = await Db.collections.Tag.findByIds(tagIds, { select: ['id', 'name'], }); } @@ -768,7 +768,7 @@ class App { await getConnection().transaction(async (transactionManager) => { savedWorkflow = await transactionManager.save(newWorkflow); - const role = await Db.collections.Role!.findOneOrFail({ + const role = await Db.collections.Role.findOneOrFail({ name: 'owner', scope: 'workflow', }); @@ -878,13 +878,13 @@ class App { } if (req.user.globalRole.name === 'owner') { - workflows = await Db.collections.Workflow!.find( + workflows = await Db.collections.Workflow.find( Object.assign(query, { where: filter, }), ); } else { - const shared = await Db.collections.SharedWorkflow!.find({ + const shared = await Db.collections.SharedWorkflow.find({ relations: ['workflow'], where: whereClause({ user: req.user, @@ -894,7 +894,7 @@ class App { if (!shared.length) return []; - workflows = await Db.collections.Workflow!.find( + workflows = await Db.collections.Workflow.find( Object.assign(query, { where: { id: In(shared.map(({ workflow }) => workflow.id)), @@ -937,7 +937,7 @@ class App { relations = relations.filter((relation) => relation !== 'workflow.tags'); } - const shared = await Db.collections.SharedWorkflow!.findOne({ + const shared = await Db.collections.SharedWorkflow.findOne({ relations, where: whereClause({ user: req.user, @@ -979,7 +979,7 @@ class App { const { tags, ...rest } = req.body; Object.assign(updateData, rest); - const shared = await Db.collections.SharedWorkflow!.findOne({ + const shared = await Db.collections.SharedWorkflow.findOne({ relations: ['workflow'], where: whereClause({ user: req.user, @@ -1041,7 +1041,7 @@ class App { await validateEntity(updateData); } - await Db.collections.Workflow!.update(workflowId, updateData); + await Db.collections.Workflow.update(workflowId, updateData); if (tags && !config.getEnv('workflowTagsDisabled')) { const tablePrefix = config.getEnv('database.tablePrefix'); @@ -1062,7 +1062,7 @@ class App { // We sadly get nothing back from "update". Neither if it updated a record // nor the new value. So query now the hopefully updated entry. - const updatedWorkflow = await Db.collections.Workflow!.findOne(workflowId, options); + const updatedWorkflow = await Db.collections.Workflow.findOne(workflowId, options); if (updatedWorkflow === undefined) { throw new ResponseHelper.ResponseError( @@ -1079,7 +1079,6 @@ class App { } await this.externalHooks.run('workflow.afterUpdate', [updatedWorkflow]); - // @ts-ignore void InternalHooksManager.getInstance().onWorkflowSaved(req.user.id, updatedWorkflow); if (updatedWorkflow.active) { @@ -1093,8 +1092,7 @@ class App { } catch (error) { // If workflow could not be activated set it again to inactive updateData.active = false; - // @ts-ignore - await Db.collections.Workflow!.update(workflowId, updateData); + await Db.collections.Workflow.update(workflowId, updateData); // Also set it in the returned data updatedWorkflow.active = false; @@ -1121,7 +1119,7 @@ class App { await this.externalHooks.run('workflow.delete', [workflowId]); - const shared = await Db.collections.SharedWorkflow!.findOne({ + const shared = await Db.collections.SharedWorkflow.findOne({ relations: ['workflow'], where: whereClause({ user: req.user, @@ -1147,7 +1145,7 @@ class App { await this.activeWorkflowRunner.remove(workflowId); } - await Db.collections.Workflow!.delete(workflowId); + await Db.collections.Workflow.delete(workflowId); void InternalHooksManager.getInstance().onWorkflowDeleted(req.user.id, workflowId); await this.externalHooks.run('workflow.afterDelete', [workflowId]); @@ -1246,7 +1244,7 @@ class App { return TagHelpers.getTagsWithCountDb(tablePrefix); } - return Db.collections.Tag!.find({ select: ['id', 'name'] }); + return Db.collections.Tag.find({ select: ['id', 'name'] }); }, ), ); @@ -1265,7 +1263,7 @@ class App { await this.externalHooks.run('tag.beforeCreate', [newTag]); await validateEntity(newTag); - const tag = await Db.collections.Tag!.save(newTag); + const tag = await Db.collections.Tag.save(newTag); await this.externalHooks.run('tag.afterCreate', [tag]); @@ -1294,7 +1292,7 @@ class App { await this.externalHooks.run('tag.beforeUpdate', [newTag]); await validateEntity(newTag); - const tag = await Db.collections.Tag!.save(newTag); + const tag = await Db.collections.Tag.save(newTag); await this.externalHooks.run('tag.afterUpdate', [tag]); @@ -1326,7 +1324,7 @@ class App { await this.externalHooks.run('tag.beforeDelete', [id]); - await Db.collections.Tag!.delete({ id }); + await Db.collections.Tag.delete({ id }); await this.externalHooks.run('tag.afterDelete', [id]); @@ -1591,7 +1589,7 @@ class App { ResponseHelper.send(async (req: WorkflowRequest.GetAllActivationErrors) => { const { id: workflowId } = req.params; - const shared = await Db.collections.SharedWorkflow!.findOne({ + const shared = await Db.collections.SharedWorkflow.findOne({ relations: ['workflow'], where: whereClause({ user: req.user, @@ -1794,7 +1792,7 @@ class App { newCredentialsData.updatedAt = this.getCurrentDate(); // Update the credentials in DB - await Db.collections.Credentials!.update(credentialId, newCredentialsData); + await Db.collections.Credentials.update(credentialId, newCredentialsData); LoggerProxy.verbose('OAuth1 authorization successful for new credential', { userId: req.user.id, @@ -1911,7 +1909,7 @@ class App { // Add special database related data newCredentialsData.updatedAt = this.getCurrentDate(); // Save the credentials in DB - await Db.collections.Credentials!.update(credentialId, newCredentialsData); + await Db.collections.Credentials.update(credentialId, newCredentialsData); LoggerProxy.verbose('OAuth1 callback successful for new credential', { userId: req.user?.id, @@ -2026,7 +2024,7 @@ class App { newCredentialsData.updatedAt = this.getCurrentDate(); // Update the credentials in DB - await Db.collections.Credentials!.update(req.query.id as string, newCredentialsData); + await Db.collections.Credentials.update(req.query.id as string, newCredentialsData); const authQueryParameters = _.get(oauthCredentials, 'authQueryParameters', '') as string; let returnUri = oAuthObj.code.getUri(); @@ -2213,7 +2211,7 @@ class App { // Add special database related data newCredentialsData.updatedAt = this.getCurrentDate(); // Save the credentials in DB - await Db.collections.Credentials!.update(state.cid, newCredentialsData); + await Db.collections.Credentials.update(state.cid, newCredentialsData); LoggerProxy.verbose('OAuth2 callback successful for new credential', { userId: req.user?.id, credentialId: state.cid, @@ -2319,7 +2317,7 @@ class App { }); } - const executions = await Db.collections.Execution!.find(findOptions); + const executions = await Db.collections.Execution.find(findOptions); const { count, estimated } = await getExecutionsCount(countFilter, req.user); @@ -2360,7 +2358,7 @@ class App { if (!sharedWorkflowIds.length) return undefined; - const execution = await Db.collections.Execution!.findOne({ + const execution = await Db.collections.Execution.findOne({ where: { id: executionId, workflowId: In(sharedWorkflowIds), @@ -2403,7 +2401,7 @@ class App { if (!sharedWorkflowIds.length) return false; - const execution = await Db.collections.Execution!.findOne({ + const execution = await Db.collections.Execution.findOne({ where: { id: executionId, workflowId: In(sharedWorkflowIds), @@ -2465,9 +2463,7 @@ class App { // Loads the currently saved workflow to execute instead of the // one saved at the time of the execution. const workflowId = fullExecutionData.workflowData.id; - const workflowData = (await Db.collections.Workflow!.findOne( - workflowId, - )) as IWorkflowBase; + const workflowData = (await Db.collections.Workflow.findOne(workflowId)) as IWorkflowBase; if (workflowData === undefined) { throw new Error( @@ -2549,7 +2545,7 @@ class App { Object.assign(filters, requestFilters); } - const executions = await Db.collections.Execution!.find({ + const executions = await Db.collections.Execution.find({ where: { workflowId: In(sharedWorkflowIds), ...filters, @@ -2564,7 +2560,7 @@ class App { idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)), ); - await Db.collections.Execution!.delete({ id: In(idsToDelete) }); + await Db.collections.Execution.delete({ id: In(idsToDelete) }); return; } @@ -2572,7 +2568,7 @@ class App { // delete executions by IDs, if user may access the underyling worfklows if (ids) { - const executions = await Db.collections.Execution!.find({ + const executions = await Db.collections.Execution.find({ where: { id: In(ids), workflowId: In(sharedWorkflowIds), @@ -2593,7 +2589,7 @@ class App { idsToDelete.map(async (id) => binaryDataManager.deleteBinaryDataByExecutionId(id)), ); - await Db.collections.Execution!.delete(idsToDelete); + await Db.collections.Execution.delete(idsToDelete); } }), ); @@ -2644,7 +2640,7 @@ class App { Object.assign(findOptions.where, { workflowId: In(sharedWorkflowIds) }); } - const executions = await Db.collections.Execution!.find(findOptions); + const executions = await Db.collections.Execution.find(findOptions); if (!executions.length) return []; @@ -2705,7 +2701,7 @@ class App { throw new ResponseHelper.ResponseError('Execution not found', undefined, 404); } - const execution = await Db.collections.Execution!.findOne({ + const execution = await Db.collections.Execution.findOne({ where: { id: executionId, workflowId: In(sharedWorkflowIds), @@ -2748,7 +2744,7 @@ class App { await Queue.getInstance().stopJob(job); } - const executionDb = (await Db.collections.Execution?.findOne( + const executionDb = (await Db.collections.Execution.findOne( req.params.id, )) as IExecutionFlattedDb; const fullExecutionData = ResponseHelper.unflattenExecutionData(executionDb); @@ -3083,7 +3079,7 @@ async function getExecutionsCount( if (dbType !== 'postgresdb' || filteredFields.length > 0 || user.globalRole.name !== 'owner') { const sharedWorkflowIds = await getSharedWorkflowIds(user); - const count = await Db.collections.Execution!.count({ + const count = await Db.collections.Execution.count({ where: { workflowId: In(sharedWorkflowIds), ...countFilter, @@ -3097,7 +3093,7 @@ async function getExecutionsCount( // Get an estimate of rows count. const estimateRowsNumberSql = "SELECT n_live_tup FROM pg_stat_all_tables WHERE relname = 'execution_entity';"; - const rows: Array<{ n_live_tup: string }> = await Db.collections.Execution!.query( + const rows: Array<{ n_live_tup: string }> = await Db.collections.Execution.query( estimateRowsNumberSql, ); @@ -3114,7 +3110,7 @@ async function getExecutionsCount( const sharedWorkflowIds = await getSharedWorkflowIds(user); - const count = await Db.collections.Execution!.count({ + const count = await Db.collections.Execution.count({ where: { workflowId: In(sharedWorkflowIds), }, diff --git a/packages/cli/src/WaitingWebhooks.ts b/packages/cli/src/WaitingWebhooks.ts index 9d313cc5ac..0f603b08a0 100644 --- a/packages/cli/src/WaitingWebhooks.ts +++ b/packages/cli/src/WaitingWebhooks.ts @@ -50,7 +50,7 @@ export class WaitingWebhooks { const executionId = pathParts.shift(); const path = pathParts.join('/'); - const execution = await Db.collections.Execution?.findOne(executionId); + const execution = await Db.collections.Execution.findOne(executionId); if (execution === undefined) { throw new ResponseHelper.ResponseError( diff --git a/packages/cli/src/WorkflowExecuteAdditionalData.ts b/packages/cli/src/WorkflowExecuteAdditionalData.ts index 287c362794..d29deecf94 100644 --- a/packages/cli/src/WorkflowExecuteAdditionalData.ts +++ b/packages/cli/src/WorkflowExecuteAdditionalData.ts @@ -584,7 +584,7 @@ function hookFunctionsSave(parentProcessMode?: string): IWorkflowExecuteHooks { if (fullRunData.finished === true && this.retryOf !== undefined) { // If the retry was successful save the reference it on the original execution - // await Db.collections.Execution!.save(executionData as IExecutionFlattedDb); + // await Db.collections.Execution.save(executionData as IExecutionFlattedDb); await Db.collections.Execution.update(this.retryOf, { retrySuccessId: this.executionId, }); diff --git a/packages/cli/src/WorkflowHelpers.ts b/packages/cli/src/WorkflowHelpers.ts index 8ac6cc2284..dcf154b35c 100644 --- a/packages/cli/src/WorkflowHelpers.ts +++ b/packages/cli/src/WorkflowHelpers.ts @@ -479,7 +479,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi credentialsByName[nodeCredentialType] = {}; } if (credentialsByName[nodeCredentialType][name] === undefined) { - const credentials = await Db.collections.Credentials?.find({ + const credentials = await Db.collections.Credentials.find({ name, type: nodeCredentialType, }); @@ -515,7 +515,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi // check if credentials for ID-type are not yet cached if (credentialsById[nodeCredentialType][nodeCredentials.id] === undefined) { // check first if ID-type combination exists - const credentials = await Db.collections.Credentials?.findOne({ + const credentials = await Db.collections.Credentials.findOne({ id: nodeCredentials.id, type: nodeCredentialType, }); @@ -529,7 +529,7 @@ export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promi continue; } // no credentials found for ID, check if some exist for name - const credsByName = await Db.collections.Credentials?.find({ + const credsByName = await Db.collections.Credentials.find({ name: nodeCredentials.name, type: nodeCredentialType, }); diff --git a/packages/cli/test/integration/auth.api.test.ts b/packages/cli/test/integration/auth.api.test.ts index ed49c63edb..03f9d0c108 100644 --- a/packages/cli/test/integration/auth.api.test.ts +++ b/packages/cli/test/integration/auth.api.test.ts @@ -33,7 +33,7 @@ beforeEach(async () => { config.set('userManagement.isInstanceOwnerSetUp', true); - await Db.collections.Settings!.update( + await Db.collections.Settings.update( { key: 'userManagement.isInstanceOwnerSetUp' }, { value: JSON.stringify(true) }, ); @@ -102,7 +102,7 @@ test('GET /login should return cookie if UM is disabled', async () => { config.set('userManagement.isInstanceOwnerSetUp', false); - await Db.collections.Settings!.update( + await Db.collections.Settings.update( { key: 'userManagement.isInstanceOwnerSetUp' }, { value: JSON.stringify(false) }, ); diff --git a/packages/cli/test/integration/auth.endpoints.test.ts b/packages/cli/test/integration/auth.endpoints.test.ts deleted file mode 100644 index 0bbb5b7e58..0000000000 --- a/packages/cli/test/integration/auth.endpoints.test.ts +++ /dev/null @@ -1,157 +0,0 @@ -import express from 'express'; -import validator from 'validator'; -import { v4 as uuid } from 'uuid'; - -import * as config from '../../config'; -import * as utils from './shared/utils'; -import { LOGGED_OUT_RESPONSE_BODY } from './shared/constants'; -import { Db } from '../../src'; -import { Role } from '../../src/databases/entities/Role'; -import { randomEmail, randomValidPassword, randomName } from './shared/random'; -import { getGlobalOwnerRole } from './shared/testDb'; -import * as testDb from './shared/testDb'; - -jest.mock('../../src/telemetry'); - -let globalOwnerRole: Role; - -let app: express.Application; -let testDbName = ''; - -beforeAll(async () => { - app = utils.initTestServer({ endpointGroups: ['auth'], applyAuth: true }); - const initResult = await testDb.init(); - testDbName = initResult.testDbName; - - await testDb.truncate(['User'], testDbName); - - globalOwnerRole = await getGlobalOwnerRole(); - utils.initTestLogger(); - utils.initTestTelemetry(); -}); - -beforeEach(async () => { - await testDb.createUser({ - id: uuid(), - email: TEST_USER.email, - firstName: TEST_USER.firstName, - lastName: TEST_USER.lastName, - password: TEST_USER.password, - globalRole: globalOwnerRole, - }); - - config.set('userManagement.isInstanceOwnerSetUp', true); - - await Db.collections.Settings!.update( - { key: 'userManagement.isInstanceOwnerSetUp' }, - { value: JSON.stringify(true) }, - ); -}); - -afterEach(async () => { - await testDb.truncate(['User'], testDbName); -}); - -afterAll(async () => { - await testDb.terminate(testDbName); -}); - -test('POST /login should log user in', async () => { - const authlessAgent = utils.createAgent(app); - - await Promise.all( - [ - { - email: TEST_USER.email, - password: TEST_USER.password, - }, - { - email: TEST_USER.email.toUpperCase(), - password: TEST_USER.password, - }, - ].map(async (payload) => { - const response = await authlessAgent.post('/login').send(payload); - - expect(response.statusCode).toBe(200); - - const { - id, - email, - firstName, - lastName, - password, - personalizationAnswers, - globalRole, - resetPasswordToken, - } = response.body.data; - - expect(validator.isUUID(id)).toBe(true); - expect(email).toBe(TEST_USER.email); - expect(firstName).toBe(TEST_USER.firstName); - expect(lastName).toBe(TEST_USER.lastName); - expect(password).toBeUndefined(); - expect(personalizationAnswers).toBeNull(); - expect(resetPasswordToken).toBeUndefined(); - expect(globalRole).toBeDefined(); - expect(globalRole.name).toBe('owner'); - expect(globalRole.scope).toBe('global'); - - const authToken = utils.getAuthToken(response); - expect(authToken).toBeDefined(); - }), - ); -}); - -test('GET /login should receive logged in user', async () => { - const owner = await Db.collections.User!.findOneOrFail(); - const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner }); - - const response = await authOwnerAgent.get('/login'); - - expect(response.statusCode).toBe(200); - - const { - id, - email, - firstName, - lastName, - password, - personalizationAnswers, - globalRole, - resetPasswordToken, - } = response.body.data; - - expect(validator.isUUID(id)).toBe(true); - expect(email).toBe(TEST_USER.email); - expect(firstName).toBe(TEST_USER.firstName); - expect(lastName).toBe(TEST_USER.lastName); - expect(password).toBeUndefined(); - expect(personalizationAnswers).toBeNull(); - expect(password).toBeUndefined(); - expect(resetPasswordToken).toBeUndefined(); - expect(globalRole).toBeDefined(); - expect(globalRole.name).toBe('owner'); - expect(globalRole.scope).toBe('global'); - - expect(response.headers['set-cookie']).toBeUndefined(); -}); - -test('POST /logout should log user out', async () => { - const owner = await Db.collections.User!.findOneOrFail(); - const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner }); - - const response = await authOwnerAgent.post('/logout'); - - expect(response.statusCode).toBe(200); - expect(response.body).toEqual(LOGGED_OUT_RESPONSE_BODY); - - const authToken = utils.getAuthToken(response); - expect(authToken).toBeUndefined(); -}); - -const TEST_USER = { - email: randomEmail(), - password: randomValidPassword(), - firstName: randomName(), - lastName: randomName(), -}; diff --git a/packages/cli/test/integration/credentials.api.test.ts b/packages/cli/test/integration/credentials.api.test.ts index e5a502eccd..e1ef311821 100644 --- a/packages/cli/test/integration/credentials.api.test.ts +++ b/packages/cli/test/integration/credentials.api.test.ts @@ -62,14 +62,14 @@ test('POST /credentials should create cred', async () => { expect(nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType); expect(encryptedData).not.toBe(payload.data); - const credential = await Db.collections.Credentials!.findOneOrFail(id); + const credential = await Db.collections.Credentials.findOneOrFail(id); expect(credential.name).toBe(payload.name); expect(credential.type).toBe(payload.type); expect(credential.nodesAccess[0].nodeType).toBe(payload.nodesAccess[0].nodeType); expect(credential.data).not.toBe(payload.data); - const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({ + const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ relations: ['user', 'credentials'], where: { credentials: credential }, }); @@ -131,11 +131,11 @@ test('DELETE /credentials/:id should delete owned cred for owner', async () => { expect(response.statusCode).toBe(200); expect(response.body).toEqual({ data: true }); - const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id); + const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id); expect(deletedCredential).toBeUndefined(); // deleted - const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); + const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); expect(deletedSharedCredential).toBeUndefined(); // deleted }); @@ -151,11 +151,11 @@ test('DELETE /credentials/:id should delete non-owned cred for owner', async () expect(response.statusCode).toBe(200); expect(response.body).toEqual({ data: true }); - const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id); + const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id); expect(deletedCredential).toBeUndefined(); // deleted - const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); + const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); expect(deletedSharedCredential).toBeUndefined(); // deleted }); @@ -170,11 +170,11 @@ test('DELETE /credentials/:id should delete owned cred for member', async () => expect(response.statusCode).toBe(200); expect(response.body).toEqual({ data: true }); - const deletedCredential = await Db.collections.Credentials!.findOne(savedCredential.id); + const deletedCredential = await Db.collections.Credentials.findOne(savedCredential.id); expect(deletedCredential).toBeUndefined(); // deleted - const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); + const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); expect(deletedSharedCredential).toBeUndefined(); // deleted }); @@ -189,11 +189,11 @@ test('DELETE /credentials/:id should not delete non-owned cred for member', asyn expect(response.statusCode).toBe(404); - const shellCredential = await Db.collections.Credentials!.findOne(savedCredential.id); + const shellCredential = await Db.collections.Credentials.findOne(savedCredential.id); expect(shellCredential).toBeDefined(); // not deleted - const deletedSharedCredential = await Db.collections.SharedCredentials!.findOne(); + const deletedSharedCredential = await Db.collections.SharedCredentials.findOne(); expect(deletedSharedCredential).toBeDefined(); // not deleted }); @@ -226,14 +226,14 @@ test('PATCH /credentials/:id should update owned cred for owner', async () => { expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType); expect(encryptedData).not.toBe(patchPayload.data); - const credential = await Db.collections.Credentials!.findOneOrFail(id); + const credential = await Db.collections.Credentials.findOneOrFail(id); expect(credential.name).toBe(patchPayload.name); expect(credential.type).toBe(patchPayload.type); expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType); expect(credential.data).not.toBe(patchPayload.data); - const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({ + const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ relations: ['credentials'], where: { credentials: credential }, }); @@ -261,14 +261,14 @@ test('PATCH /credentials/:id should update non-owned cred for owner', async () = expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType); expect(encryptedData).not.toBe(patchPayload.data); - const credential = await Db.collections.Credentials!.findOneOrFail(id); + const credential = await Db.collections.Credentials.findOneOrFail(id); expect(credential.name).toBe(patchPayload.name); expect(credential.type).toBe(patchPayload.type); expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType); expect(credential.data).not.toBe(patchPayload.data); - const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({ + const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ relations: ['credentials'], where: { credentials: credential }, }); @@ -295,14 +295,14 @@ test('PATCH /credentials/:id should update owned cred for member', async () => { expect(nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType); expect(encryptedData).not.toBe(patchPayload.data); - const credential = await Db.collections.Credentials!.findOneOrFail(id); + const credential = await Db.collections.Credentials.findOneOrFail(id); expect(credential.name).toBe(patchPayload.name); expect(credential.type).toBe(patchPayload.type); expect(credential.nodesAccess[0].nodeType).toBe(patchPayload.nodesAccess[0].nodeType); expect(credential.data).not.toBe(patchPayload.data); - const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({ + const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ relations: ['credentials'], where: { credentials: credential }, }); @@ -323,7 +323,7 @@ test('PATCH /credentials/:id should not update non-owned cred for member', async expect(response.statusCode).toBe(404); - const shellCredential = await Db.collections.Credentials!.findOneOrFail(savedCredential.id); + const shellCredential = await Db.collections.Credentials.findOneOrFail(savedCredential.id); expect(shellCredential.name).not.toBe(patchPayload.name); // not updated }); diff --git a/packages/cli/test/integration/me.api.test.ts b/packages/cli/test/integration/me.api.test.ts index 186f09ddea..5b4cffb3ec 100644 --- a/packages/cli/test/integration/me.api.test.ts +++ b/packages/cli/test/integration/me.api.test.ts @@ -101,7 +101,7 @@ describe('Owner shell', () => { expect(globalRole.name).toBe('owner'); expect(globalRole.scope).toBe('global'); - const storedOwnerShell = await Db.collections.User!.findOneOrFail(id); + const storedOwnerShell = await Db.collections.User.findOneOrFail(id); expect(storedOwnerShell.email).toBe(validPayload.email.toLowerCase()); expect(storedOwnerShell.firstName).toBe(validPayload.firstName); @@ -117,7 +117,7 @@ describe('Owner shell', () => { const response = await authOwnerShellAgent.patch('/me').send(invalidPayload); expect(response.statusCode).toBe(400); - const storedOwnerShell = await Db.collections.User!.findOneOrFail(); + const storedOwnerShell = await Db.collections.User.findOneOrFail(); expect(storedOwnerShell.email).toBeNull(); expect(storedOwnerShell.firstName).toBeNull(); expect(storedOwnerShell.lastName).toBeNull(); @@ -140,7 +140,7 @@ describe('Owner shell', () => { const response = await authOwnerShellAgent.patch('/me/password').send(payload); expect([400, 500].includes(response.statusCode)).toBe(true); - const storedMember = await Db.collections.User!.findOneOrFail(); + const storedMember = await Db.collections.User.findOneOrFail(); if (payload.newPassword) { expect(storedMember.password).not.toBe(payload.newPassword); @@ -152,7 +152,7 @@ describe('Owner shell', () => { }), ); - const storedOwnerShell = await Db.collections.User!.findOneOrFail(); + const storedOwnerShell = await Db.collections.User.findOneOrFail(); expect(storedOwnerShell.password).toBeNull(); }); @@ -168,7 +168,7 @@ describe('Owner shell', () => { expect(response.statusCode).toBe(200); expect(response.body).toEqual(SUCCESS_RESPONSE_BODY); - const storedShellOwner = await Db.collections.User!.findOneOrFail({ + const storedShellOwner = await Db.collections.User.findOneOrFail({ where: { email: IsNull() }, }); @@ -181,7 +181,7 @@ describe('Member', () => { beforeEach(async () => { config.set('userManagement.isInstanceOwnerSetUp', true); - await Db.collections.Settings!.update( + await Db.collections.Settings.update( { key: 'userManagement.isInstanceOwnerSetUp' }, { value: JSON.stringify(true) }, ); @@ -255,7 +255,7 @@ describe('Member', () => { expect(globalRole.name).toBe('member'); expect(globalRole.scope).toBe('global'); - const storedMember = await Db.collections.User!.findOneOrFail(id); + const storedMember = await Db.collections.User.findOneOrFail(id); expect(storedMember.email).toBe(validPayload.email.toLowerCase()); expect(storedMember.firstName).toBe(validPayload.firstName); @@ -271,7 +271,7 @@ describe('Member', () => { const response = await authMemberAgent.patch('/me').send(invalidPayload); expect(response.statusCode).toBe(400); - const storedMember = await Db.collections.User!.findOneOrFail(); + const storedMember = await Db.collections.User.findOneOrFail(); expect(storedMember.email).toBe(member.email); expect(storedMember.firstName).toBe(member.firstName); expect(storedMember.lastName).toBe(member.lastName); @@ -295,7 +295,7 @@ describe('Member', () => { expect(response.statusCode).toBe(200); expect(response.body).toEqual(SUCCESS_RESPONSE_BODY); - const storedMember = await Db.collections.User!.findOneOrFail(); + const storedMember = await Db.collections.User.findOneOrFail(); expect(storedMember.password).not.toBe(member.password); expect(storedMember.password).not.toBe(validPayload.newPassword); }); @@ -308,7 +308,7 @@ describe('Member', () => { const response = await authMemberAgent.patch('/me/password').send(payload); expect([400, 500].includes(response.statusCode)).toBe(true); - const storedMember = await Db.collections.User!.findOneOrFail(); + const storedMember = await Db.collections.User.findOneOrFail(); if (payload.newPassword) { expect(storedMember.password).not.toBe(payload.newPassword); @@ -330,7 +330,7 @@ describe('Member', () => { expect(response.statusCode).toBe(200); expect(response.body).toEqual(SUCCESS_RESPONSE_BODY); - const { personalizationAnswers: storedAnswers } = await Db.collections.User!.findOneOrFail(); + const { personalizationAnswers: storedAnswers } = await Db.collections.User.findOneOrFail(); expect(storedAnswers).toEqual(validPayload); } @@ -410,7 +410,7 @@ describe('Owner', () => { expect(globalRole.name).toBe('owner'); expect(globalRole.scope).toBe('global'); - const storedOwner = await Db.collections.User!.findOneOrFail(id); + const storedOwner = await Db.collections.User.findOneOrFail(id); expect(storedOwner.email).toBe(validPayload.email.toLowerCase()); expect(storedOwner.firstName).toBe(validPayload.firstName); diff --git a/packages/cli/test/integration/owner.api.test.ts b/packages/cli/test/integration/owner.api.test.ts index e479196999..a7c99b6a70 100644 --- a/packages/cli/test/integration/owner.api.test.ts +++ b/packages/cli/test/integration/owner.api.test.ts @@ -81,7 +81,7 @@ test('POST /owner should create owner and enable isInstanceOwnerSetUp', async () expect(globalRole.name).toBe('owner'); expect(globalRole.scope).toBe('global'); - const storedOwner = await Db.collections.User!.findOneOrFail(id); + const storedOwner = await Db.collections.User.findOneOrFail(id); expect(storedOwner.password).not.toBe(newOwnerData.password); expect(storedOwner.email).toBe(newOwnerData.email); expect(storedOwner.firstName).toBe(newOwnerData.firstName); @@ -113,7 +113,7 @@ test('POST /owner should create owner with lowercased email', async () => { expect(email).toBe(newOwnerData.email.toLowerCase()); - const storedOwner = await Db.collections.User!.findOneOrFail(id); + const storedOwner = await Db.collections.User.findOneOrFail(id); expect(storedOwner.email).toBe(newOwnerData.email.toLowerCase()); }); @@ -140,7 +140,7 @@ test('POST /owner/skip-setup should persist skipping setup to the DB', async () const skipConfig = config.getEnv('userManagement.skipInstanceOwnerSetup'); expect(skipConfig).toBe(true); - const { value } = await Db.collections.Settings!.findOneOrFail({ + const { value } = await Db.collections.Settings.findOneOrFail({ key: 'userManagement.skipInstanceOwnerSetup', }); expect(value).toBe('true'); diff --git a/packages/cli/test/integration/passwordReset.api.test.ts b/packages/cli/test/integration/passwordReset.api.test.ts index 9d083fe308..faa7e96a42 100644 --- a/packages/cli/test/integration/passwordReset.api.test.ts +++ b/packages/cli/test/integration/passwordReset.api.test.ts @@ -67,7 +67,7 @@ test( expect(response.statusCode).toBe(200); expect(response.body).toEqual({}); - const user = await Db.collections.User!.findOneOrFail({ email: payload.email }); + const user = await Db.collections.User.findOneOrFail({ email: payload.email }); expect(user.resetPasswordToken).toBeDefined(); expect(user.resetPasswordTokenExpiration).toBeGreaterThan(Math.ceil(Date.now() / 1000)); }), @@ -85,7 +85,7 @@ test('POST /forgot-password should fail if emailing is not set up', async () => expect(response.statusCode).toBe(500); - const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email }); + const storedOwner = await Db.collections.User.findOneOrFail({ email: owner.email }); expect(storedOwner.resetPasswordToken).toBeNull(); }); @@ -109,7 +109,7 @@ test('POST /forgot-password should fail with invalid inputs', async () => { const response = await authlessAgent.post('/forgot-password').send(invalidPayload); expect(response.statusCode).toBe(400); - const storedOwner = await Db.collections.User!.findOneOrFail({ email: owner.email }); + const storedOwner = await Db.collections.User.findOneOrFail({ email: owner.email }); expect(storedOwner.resetPasswordToken).toBeNull(); }), ); @@ -133,7 +133,7 @@ test('GET /resolve-password-token should succeed with valid inputs', async () => const resetPasswordToken = uuid(); const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100; - await Db.collections.User!.update(owner.id, { + await Db.collections.User.update(owner.id, { resetPasswordToken, resetPasswordTokenExpiration, }); @@ -183,7 +183,7 @@ test('GET /resolve-password-token should fail if token is expired', async () => const resetPasswordToken = uuid(); const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1; - await Db.collections.User!.update(owner.id, { + await Db.collections.User.update(owner.id, { resetPasswordToken, resetPasswordTokenExpiration, }); @@ -205,7 +205,7 @@ test('POST /change-password should succeed with valid inputs', async () => { const resetPasswordToken = uuid(); const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100; - await Db.collections.User!.update(owner.id, { + await Db.collections.User.update(owner.id, { resetPasswordToken, resetPasswordTokenExpiration, }); @@ -223,7 +223,7 @@ test('POST /change-password should succeed with valid inputs', async () => { const authToken = utils.getAuthToken(response); expect(authToken).toBeDefined(); - const { password: storedPassword } = await Db.collections.User!.findOneOrFail(owner.id); + const { password: storedPassword } = await Db.collections.User.findOneOrFail(owner.id); const comparisonResult = await compare(passwordToStore, storedPassword); expect(comparisonResult).toBe(true); @@ -238,7 +238,7 @@ test('POST /change-password should fail with invalid inputs', async () => { const resetPasswordToken = uuid(); const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) + 100; - await Db.collections.User!.update(owner.id, { + await Db.collections.User.update(owner.id, { resetPasswordToken, resetPasswordTokenExpiration, }); @@ -267,7 +267,7 @@ test('POST /change-password should fail with invalid inputs', async () => { const response = await authlessAgent.post('/change-password').query(invalidPayload); expect(response.statusCode).toBe(400); - const { password: storedPassword } = await Db.collections.User!.findOneOrFail(); + const { password: storedPassword } = await Db.collections.User.findOneOrFail(); expect(owner.password).toBe(storedPassword); }), ); @@ -281,7 +281,7 @@ test('POST /change-password should fail when token has expired', async () => { const resetPasswordToken = uuid(); const resetPasswordTokenExpiration = Math.floor(Date.now() / 1000) - 1; - await Db.collections.User!.update(owner.id, { + await Db.collections.User.update(owner.id, { resetPasswordToken, resetPasswordTokenExpiration, }); diff --git a/packages/cli/test/integration/shared/testDb.ts b/packages/cli/test/integration/shared/testDb.ts index c15ba056a0..e1f5113793 100644 --- a/packages/cli/test/integration/shared/testDb.ts +++ b/packages/cli/test/integration/shared/testDb.ts @@ -109,7 +109,7 @@ export async function truncate(collections: CollectionName[], testDbName: string if (dbType === 'sqlite') { await testDb.query('PRAGMA foreign_keys=OFF'); - await Promise.all(collections.map((collection) => Db.collections[collection]!.clear())); + await Promise.all(collections.map((collection) => Db.collections[collection].clear())); return testDb.query('PRAGMA foreign_keys=ON'); } @@ -182,11 +182,11 @@ export async function saveCredential( Object.assign(newCredential, encryptedData); - const savedCredential = await Db.collections.Credentials!.save(newCredential); + const savedCredential = await Db.collections.Credentials.save(newCredential); savedCredential.data = newCredential.data; - await Db.collections.SharedCredentials!.save({ + await Db.collections.SharedCredentials.save({ user, credentials: savedCredential, role, @@ -211,7 +211,7 @@ export async function createUser(attributes: Partial & { globalRole: Role ...rest, }; - return Db.collections.User!.save(user); + return Db.collections.User.save(user); } export function createUserShell(globalRole: Role): Promise { @@ -225,7 +225,7 @@ export function createUserShell(globalRole: Role): Promise { shell.email = randomEmail(); } - return Db.collections.User!.save(shell); + return Db.collections.User.save(shell); } // ---------------------------------- @@ -233,28 +233,28 @@ export function createUserShell(globalRole: Role): Promise { // ---------------------------------- export function getGlobalOwnerRole() { - return Db.collections.Role!.findOneOrFail({ + return Db.collections.Role.findOneOrFail({ name: 'owner', scope: 'global', }); } export function getGlobalMemberRole() { - return Db.collections.Role!.findOneOrFail({ + return Db.collections.Role.findOneOrFail({ name: 'member', scope: 'global', }); } export function getWorkflowOwnerRole() { - return Db.collections.Role!.findOneOrFail({ + return Db.collections.Role.findOneOrFail({ name: 'owner', scope: 'workflow', }); } export function getCredentialOwnerRole() { - return Db.collections.Role!.findOneOrFail({ + return Db.collections.Role.findOneOrFail({ name: 'owner', scope: 'credential', }); diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 4127eb98db..3086b96349 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -198,7 +198,7 @@ export function getAuthToken(response: request.Response, authCookieName = AUTH_C // ---------------------------------- export async function isInstanceOwnerSetUp() { - const { value } = await Db.collections.Settings!.findOneOrFail({ + const { value } = await Db.collections.Settings.findOneOrFail({ key: 'userManagement.isInstanceOwnerSetUp', }); diff --git a/packages/cli/test/integration/users.api.test.ts b/packages/cli/test/integration/users.api.test.ts index b5db3dc338..62e4899b12 100644 --- a/packages/cli/test/integration/users.api.test.ts +++ b/packages/cli/test/integration/users.api.test.ts @@ -119,9 +119,9 @@ test('DELETE /users/:id should delete the user', async () => { nodes: [], }); - const savedWorkflow = await Db.collections.Workflow!.save(newWorkflow); + const savedWorkflow = await Db.collections.Workflow.save(newWorkflow); - await Db.collections.SharedWorkflow!.save({ + await Db.collections.SharedWorkflow.save({ role: workflowOwnerRole, user: userToDelete, workflow: savedWorkflow, @@ -136,9 +136,9 @@ test('DELETE /users/:id should delete the user', async () => { nodesAccess: [], }); - const savedCredential = await Db.collections.Credentials!.save(newCredential); + const savedCredential = await Db.collections.Credentials.save(newCredential); - await Db.collections.SharedCredentials!.save({ + await Db.collections.SharedCredentials.save({ role: credentialOwnerRole, user: userToDelete, credentials: savedCredential, @@ -149,27 +149,27 @@ test('DELETE /users/:id should delete the user', async () => { expect(response.statusCode).toBe(200); expect(response.body).toEqual(SUCCESS_RESPONSE_BODY); - const user = await Db.collections.User!.findOne(userToDelete.id); + const user = await Db.collections.User.findOne(userToDelete.id); expect(user).toBeUndefined(); // deleted - const sharedWorkflow = await Db.collections.SharedWorkflow!.findOne({ + const sharedWorkflow = await Db.collections.SharedWorkflow.findOne({ relations: ['user'], where: { user: userToDelete }, }); expect(sharedWorkflow).toBeUndefined(); // deleted - const sharedCredential = await Db.collections.SharedCredentials!.findOne({ + const sharedCredential = await Db.collections.SharedCredentials.findOne({ relations: ['user'], where: { user: userToDelete }, }); expect(sharedCredential).toBeUndefined(); // deleted - const workflow = await Db.collections.Workflow!.findOne(savedWorkflow.id); + const workflow = await Db.collections.Workflow.findOne(savedWorkflow.id); expect(workflow).toBeUndefined(); // deleted // TODO: Include active workflow and check whether webhook has been removed - const credential = await Db.collections.Credentials!.findOne(savedCredential.id); + const credential = await Db.collections.Credentials.findOne(savedCredential.id); expect(credential).toBeUndefined(); // deleted }); @@ -181,7 +181,7 @@ test('DELETE /users/:id should fail to delete self', async () => { expect(response.statusCode).toBe(400); - const user = await Db.collections.User!.findOne(owner.id); + const user = await Db.collections.User.findOne(owner.id); expect(user).toBeDefined(); }); @@ -197,7 +197,7 @@ test('DELETE /users/:id should fail if user to delete is transferee', async () = expect(response.statusCode).toBe(400); - const user = await Db.collections.User!.findOne(idToDelete); + const user = await Db.collections.User.findOne(idToDelete); expect(user).toBeDefined(); }); @@ -205,7 +205,7 @@ test('DELETE /users/:id with transferId should perform transfer', async () => { const owner = await testDb.createUser({ globalRole: globalOwnerRole }); const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner }); - const userToDelete = await Db.collections.User!.save({ + const userToDelete = await Db.collections.User.save({ id: uuid(), email: randomEmail(), password: randomValidPassword(), @@ -225,9 +225,9 @@ test('DELETE /users/:id with transferId should perform transfer', async () => { nodes: [], }); - const savedWorkflow = await Db.collections.Workflow!.save(newWorkflow); + const savedWorkflow = await Db.collections.Workflow.save(newWorkflow); - await Db.collections.SharedWorkflow!.save({ + await Db.collections.SharedWorkflow.save({ role: workflowOwnerRole, user: userToDelete, workflow: savedWorkflow, @@ -242,9 +242,9 @@ test('DELETE /users/:id with transferId should perform transfer', async () => { nodesAccess: [], }); - const savedCredential = await Db.collections.Credentials!.save(newCredential); + const savedCredential = await Db.collections.Credentials.save(newCredential); - await Db.collections.SharedCredentials!.save({ + await Db.collections.SharedCredentials.save({ role: credentialOwnerRole, user: userToDelete, credentials: savedCredential, @@ -256,17 +256,17 @@ test('DELETE /users/:id with transferId should perform transfer', async () => { expect(response.statusCode).toBe(200); - const sharedWorkflow = await Db.collections.SharedWorkflow!.findOneOrFail({ + const sharedWorkflow = await Db.collections.SharedWorkflow.findOneOrFail({ relations: ['user'], where: { user: owner }, }); - const sharedCredential = await Db.collections.SharedCredentials!.findOneOrFail({ + const sharedCredential = await Db.collections.SharedCredentials.findOneOrFail({ relations: ['user'], where: { user: owner }, }); - const deletedUser = await Db.collections.User!.findOne(userToDelete); + const deletedUser = await Db.collections.User.findOne(userToDelete); expect(sharedWorkflow.user.id).toBe(owner.id); expect(sharedCredential.user.id).toBe(owner.id); @@ -317,7 +317,7 @@ test('GET /resolve-signup-token should fail with invalid inputs', async () => { .query({ inviteeId }); // cause inconsistent DB state - await Db.collections.User!.update(owner.id, { email: '' }); + await Db.collections.User.update(owner.id, { email: '' }); const fifth = await authOwnerAgent .get('/resolve-signup-token') .query({ inviterId: owner.id }) @@ -369,7 +369,7 @@ test('POST /users/:id should fill out a user shell', async () => { const authToken = utils.getAuthToken(response); expect(authToken).toBeDefined(); - const member = await Db.collections.User!.findOneOrFail(memberShell.id); + const member = await Db.collections.User.findOneOrFail(memberShell.id); expect(member.firstName).toBe(memberData.firstName); expect(member.lastName).toBe(memberData.lastName); expect(member.password).not.toBe(memberData.password); @@ -382,7 +382,7 @@ test('POST /users/:id should fail with invalid inputs', async () => { const memberShellEmail = randomEmail(); - const memberShell = await Db.collections.User!.save({ + const memberShell = await Db.collections.User.save({ email: memberShellEmail, globalRole: globalMemberRole, }); @@ -421,7 +421,7 @@ test('POST /users/:id should fail with invalid inputs', async () => { const response = await authlessAgent.post(`/users/${memberShell.id}`).send(invalidPayload); expect(response.statusCode).toBe(400); - const storedUser = await Db.collections.User!.findOneOrFail({ + const storedUser = await Db.collections.User.findOneOrFail({ where: { email: memberShellEmail }, }); expect(storedUser.firstName).toBeNull(); @@ -448,7 +448,7 @@ test('POST /users/:id should fail with already accepted invite', async () => { expect(response.statusCode).toBe(400); - const storedMember = await Db.collections.User!.findOneOrFail({ + const storedMember = await Db.collections.User.findOneOrFail({ where: { email: member.email }, }); expect(storedMember.firstName).not.toBe(newMemberData.firstName); @@ -517,7 +517,7 @@ test( expect(error).toBe('Email could not be sent'); } - const storedUser = await Db.collections.User!.findOneOrFail(id); + const storedUser = await Db.collections.User.findOneOrFail(id); const { firstName, lastName, personalizationAnswers, password, resetPasswordToken } = storedUser; @@ -552,7 +552,7 @@ test( const response = await authOwnerAgent.post('/users').send(invalidPayload); expect(response.statusCode).toBe(400); - const users = await Db.collections.User!.find(); + const users = await Db.collections.User.find(); expect(users.length).toBe(1); // DB unaffected }), ); @@ -576,7 +576,7 @@ test( expect(Array.isArray(data)).toBe(true); expect(data.length).toBe(0); - const users = await Db.collections.User!.find(); + const users = await Db.collections.User.find(); expect(users.length).toBe(1); }, SMTP_TEST_TIMEOUT, @@ -586,7 +586,7 @@ test( // TODO: UserManagementMailer is a singleton - cannot reinstantiate with wrong creds // test('POST /users should error for wrong SMTP config', async () => { -// const owner = await Db.collections.User!.findOneOrFail(); +// const owner = await Db.collections.User.findOneOrFail(); // const authOwnerAgent = utils.createAgent(app, { auth: true, user: owner }); // config.set('userManagement.emails.mode', 'smtp'); diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 8aae163514..a5c1f8f8bd 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -247,11 +247,9 @@ "dist/credentials/SalesforceJwtApi.credentials.js", "dist/credentials/SalesforceOAuth2Api.credentials.js", "dist/credentials/SalesmateApi.credentials.js", - "dist/credentials/SalesmateApi.credentials.js", "dist/credentials/SeaTableApi.credentials.js", "dist/credentials/SecurityScorecardApi.credentials.js", "dist/credentials/SegmentApi.credentials.js", - "dist/credentials/SegmentApi.credentials.js", "dist/credentials/SendGridApi.credentials.js", "dist/credentials/SendyApi.credentials.js", "dist/credentials/SentryIoApi.credentials.js", @@ -262,7 +260,6 @@ "dist/credentials/Sftp.credentials.js", "dist/credentials/ShopifyApi.credentials.js", "dist/credentials/Signl4Api.credentials.js", - "dist/credentials/Signl4Api.credentials.js", "dist/credentials/SlackApi.credentials.js", "dist/credentials/SlackOAuth2Api.credentials.js", "dist/credentials/Sms77Api.credentials.js", From d5b9b0cb9596688b3bcad0010b65888428c297c6 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Thu, 28 Apr 2022 19:04:09 +0200 Subject: [PATCH 05/29] feat(core): Introduce simplified node versioning (#3205) * :sparkles: Introduce simple node versioning * :zap: Add example how to read version in node-code for custom logic * :bug: Fix setting of parameters * :bug: Fix another instance where it sets the wrong parameter * :zap: Remove unnecessary TOODs * :zap: Revert Set Node example changes * ;rotating_light: Add test --- packages/cli/src/CredentialsHelper.ts | 7 +- packages/core/src/NodeExecuteFunctions.ts | 1 + packages/core/test/Helpers.ts | 59 ++++++++ packages/core/test/WorkflowExecute.test.ts | 139 ++++++++++++++++++ .../src/components/CollectionParameter.vue | 6 +- .../CredentialEdit/CredentialEdit.vue | 2 + .../src/components/NodeCredentials.vue | 2 +- .../editor-ui/src/components/NodeSettings.vue | 4 +- .../src/components/ParameterInput.vue | 2 +- .../src/components/ParameterInputList.vue | 11 +- .../src/components/mixins/nodeHelpers.ts | 6 +- .../src/components/mixins/workflowHelpers.ts | 4 +- packages/editor-ui/src/store.ts | 8 +- packages/editor-ui/src/views/NodeView.vue | 13 +- packages/workflow/src/Interfaces.ts | 2 +- packages/workflow/src/NodeHelpers.ts | 28 +++- packages/workflow/src/RoutingNode.ts | 9 +- packages/workflow/src/Workflow.ts | 1 + packages/workflow/test/NodeHelpers.test.ts | 4 + 19 files changed, 277 insertions(+), 31 deletions(-) diff --git a/packages/cli/src/CredentialsHelper.ts b/packages/cli/src/CredentialsHelper.ts index c4278aaa6a..7707cd1a39 100644 --- a/packages/cli/src/CredentialsHelper.ts +++ b/packages/cli/src/CredentialsHelper.ts @@ -342,6 +342,7 @@ export class CredentialsHelper extends ICredentialsHelper { decryptedDataOriginal as INodeParameters, true, false, + null, ) as ICredentialDataDecryptedObject; if (decryptedDataOriginal.oauthTokenData !== undefined) { @@ -436,8 +437,6 @@ export class CredentialsHelper extends ICredentialsHelper { // Add special database related data newCredentialsData.updatedAt = new Date(); - // TODO: also add user automatically depending on who is logged in, if anybody is logged in - // Save the credentials in DB const findQuery = { id: credentials.id, @@ -562,7 +561,9 @@ export class CredentialsHelper extends ICredentialsHelper { parameters: {}, name: 'Temp-Node', type: nodeType.description.name, - typeVersion: nodeType.description.version, + typeVersion: Array.isArray(nodeType.description.version) + ? nodeType.description.version.slice(-1)[0] + : nodeType.description.version, position: [0, 0], }; diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 6dc23ed38e..e8038b7572 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -1298,6 +1298,7 @@ export async function getCredentials( !NodeHelpers.displayParameter( additionalData.currentNodeParameters || node.parameters, nodeCredentialDescription, + node, node.parameters, ) ) { diff --git a/packages/core/test/Helpers.ts b/packages/core/test/Helpers.ts index f40dcb4bf5..d719dd51eb 100644 --- a/packages/core/test/Helpers.ts +++ b/packages/core/test/Helpers.ts @@ -517,6 +517,65 @@ class NodeTypesClass implements INodeTypes { }, }, }, + 'n8n-nodes-base.versionTest': { + sourcePath: '', + type: { + description: { + displayName: 'Version Test', + name: 'versionTest', + group: ['input'], + version: 1, + description: 'Tests if versioning works', + defaults: { + name: 'Version Test', + color: '#0000FF', + }, + inputs: ['main'], + outputs: ['main'], + properties: [ + { + displayName: 'Display V1', + name: 'versionTest', + type: 'number', + displayOptions: { + show: { + '@version': [1], + }, + }, + default: 1, + }, + { + displayName: 'Display V2', + name: 'versionTest', + type: 'number', + displayOptions: { + show: { + '@version': [2], + }, + }, + default: 2, + }, + ], + }, + execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { + const newItem: INodeExecutionData = { + json: { + versionFromParameter: this.getNodeParameter('versionTest', itemIndex), + versionFromNode: this.getNode().typeVersion, + }, + }; + + returnData.push(newItem); + } + + return this.prepareOutputData(returnData); + }, + }, + }, 'n8n-nodes-base.set': { sourcePath: '', type: { diff --git a/packages/core/test/WorkflowExecute.test.ts b/packages/core/test/WorkflowExecute.test.ts index b1ac658dc6..f249ebe52f 100644 --- a/packages/core/test/WorkflowExecute.test.ts +++ b/packages/core/test/WorkflowExecute.test.ts @@ -1169,6 +1169,145 @@ describe('WorkflowExecute', () => { }, }, }, + + { + description: + 'should display the correct parameters and so correct data when simplified node-versioning is used', + input: { + workflowData: { + nodes: [ + { + parameters: {}, + name: 'Start', + type: 'n8n-nodes-base.start', + typeVersion: 1, + position: [240, 300], + }, + { + parameters: {}, + name: 'VersionTest1a', + type: 'n8n-nodes-base.versionTest', + typeVersion: 1, + position: [460, 300], + }, + { + parameters: { + versionTest: 11, + }, + name: 'VersionTest1b', + type: 'n8n-nodes-base.versionTest', + typeVersion: 1, + position: [680, 300], + }, + { + parameters: {}, + name: 'VersionTest2a', + type: 'n8n-nodes-base.versionTest', + typeVersion: 2, + position: [880, 300], + }, + { + parameters: { + versionTest: 22, + }, + name: 'VersionTest2b', + type: 'n8n-nodes-base.versionTest', + typeVersion: 2, + position: [1080, 300], + }, + ], + connections: { + Start: { + main: [ + [ + { + node: 'VersionTest1a', + type: 'main', + index: 0, + }, + ], + ], + }, + VersionTest1a: { + main: [ + [ + { + node: 'VersionTest1b', + type: 'main', + index: 0, + }, + ], + ], + }, + VersionTest1b: { + main: [ + [ + { + node: 'VersionTest2a', + type: 'main', + index: 0, + }, + ], + ], + }, + VersionTest2a: { + main: [ + [ + { + node: 'VersionTest2b', + type: 'main', + index: 0, + }, + ], + ], + }, + }, + }, + }, + output: { + nodeExecutionOrder: [ + 'Start', + 'VersionTest1a', + 'VersionTest1b', + 'VersionTest2a', + 'VersionTest2b', + ], + nodeData: { + VersionTest1a: [ + [ + { + versionFromNode: 1, + versionFromParameter: 1, + }, + ], + ], + VersionTest1b: [ + [ + { + versionFromNode: 1, + versionFromParameter: 11, + }, + ], + ], + VersionTest2a: [ + [ + { + versionFromNode: 2, + versionFromParameter: 2, + }, + ], + ], + VersionTest2b: [ + [ + { + versionFromNode: 2, + versionFromParameter: 22, + }, + ], + ], + }, + }, + }, ]; const fakeLogger = { diff --git a/packages/editor-ui/src/components/CollectionParameter.vue b/packages/editor-ui/src/components/CollectionParameter.vue index ae2dcbd2ba..b93e15987c 100644 --- a/packages/editor-ui/src/components/CollectionParameter.vue +++ b/packages/editor-ui/src/components/CollectionParameter.vue @@ -32,6 +32,7 @@ This content contains a script tag and is sanitized.', +}; + +export const Truncated = PropTemplate.bind({}); +Truncated.args = { + theme: 'warning', + truncate: true, + content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', +}; + +export const HtmlEdgeCase = PropTemplate.bind({}); +HtmlEdgeCase.args = { + theme: 'warning', + truncate: true, + content: 'This content is long and will be truncated at 150 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod read the documentation ut labore et dolore magna aliqua.', +}; diff --git a/packages/design-system/src/components/N8nNotice/Notice.vue b/packages/design-system/src/components/N8nNotice/Notice.vue new file mode 100644 index 0000000000..bdd7bcec24 --- /dev/null +++ b/packages/design-system/src/components/N8nNotice/Notice.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/packages/design-system/src/components/N8nNotice/__tests__/Notice.spec.ts b/packages/design-system/src/components/N8nNotice/__tests__/Notice.spec.ts new file mode 100644 index 0000000000..7029cdd084 --- /dev/null +++ b/packages/design-system/src/components/N8nNotice/__tests__/Notice.spec.ts @@ -0,0 +1,95 @@ +import {fireEvent, render} from '@testing-library/vue'; +import N8nNotice from "../Notice.vue"; + +describe('components', () => { + describe('N8nNotice', () => { + it('should render correctly', () => { + const wrapper = render(N8nNotice, { + props: { + id: 'notice', + }, + slots: { + default: 'This is a notice.', + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + describe('props', () => { + describe('content', () => { + it('should render correctly with content prop', () => { + const wrapper = render(N8nNotice, { + props: { + id: 'notice', + content: 'This is a notice.', + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should render html', () => { + const wrapper = render(N8nNotice, { + props: { + id: 'notice', + content: 'Hello world! This is a notice.', + }, + }); + + expect(wrapper.container.querySelectorAll('strong')).toHaveLength(1); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should sanitize rendered html', () => { + const wrapper = render(N8nNotice, { + props: { + id: 'notice', + content: ' This is a notice.', + }, + }); + + expect(wrapper.container.querySelector('script')).not.toBeTruthy(); + expect(wrapper.html()).toMatchSnapshot(); + }); + }); + }); + + describe('truncation', () => { + it('should truncate content longer than 150 characters', async () => { + const wrapper = render(N8nNotice, { + props: { + id: 'notice', + truncate: true, + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', + }, + }); + + const button = await wrapper.findByRole('button'); + const region = await wrapper.findByRole('region'); + + expect(button).toBeVisible(); + expect(button).toHaveTextContent('Show more'); + + expect(region).toBeVisible(); + expect(region.textContent!.endsWith('...')).toBeTruthy(); + }); + + it('should expand truncated text when clicking show more', async () => { + const wrapper = render(N8nNotice, { + props: { + id: 'notice', + truncate: true, + content: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.', + }, + }); + + const button = await wrapper.findByRole('button'); + const region = await wrapper.findByRole('region'); + + await fireEvent.click(button); + + expect(button).toHaveTextContent('Show less'); + expect(region.textContent!.endsWith('...')).not.toBeTruthy(); + }); + }); + }); +}); diff --git a/packages/design-system/src/components/N8nNotice/__tests__/__snapshots__/Notice.spec.ts.snap b/packages/design-system/src/components/N8nNotice/__tests__/__snapshots__/Notice.spec.ts.snap new file mode 100644 index 0000000000..b93c323f81 --- /dev/null +++ b/packages/design-system/src/components/N8nNotice/__tests__/__snapshots__/Notice.spec.ts.snap @@ -0,0 +1,28 @@ +// Vitest Snapshot v1 + +exports[`components > N8nNotice > props > content > should render correctly with content prop 1`] = ` +"
+
This is a notice. +
+
" +`; + +exports[`components > N8nNotice > props > content > should render html 1`] = ` +"
+
Hello world! This is a notice. +
+
" +`; + +exports[`components > N8nNotice > props > content > should sanitize rendered html 1`] = ` +"
+
This is a notice. +
+
" +`; + +exports[`components > N8nNotice > should render correctly 1`] = ` +"
+
This is a notice.
+
" +`; diff --git a/packages/design-system/src/components/N8nNotice/index.ts b/packages/design-system/src/components/N8nNotice/index.ts new file mode 100644 index 0000000000..10b8f0695c --- /dev/null +++ b/packages/design-system/src/components/N8nNotice/index.ts @@ -0,0 +1,3 @@ +import N8nNotice from './Notice.vue'; + +export default N8nNotice; diff --git a/packages/design-system/src/components/N8nOption/index.js b/packages/design-system/src/components/N8nOption/index.ts similarity index 100% rename from packages/design-system/src/components/N8nOption/index.js rename to packages/design-system/src/components/N8nOption/index.ts diff --git a/packages/design-system/src/components/N8nRadioButtons/RadioButtons.vue b/packages/design-system/src/components/N8nRadioButtons/RadioButtons.vue index 3cd0f41323..e5f87ddb55 100644 --- a/packages/design-system/src/components/N8nRadioButtons/RadioButtons.vue +++ b/packages/design-system/src/components/N8nRadioButtons/RadioButtons.vue @@ -11,7 +11,7 @@