From 3a5412850c137aafa02cf21bdf5b2c6a5ceeae1d Mon Sep 17 00:00:00 2001 From: Ricardo Espinoza Date: Fri, 17 May 2024 10:03:39 -0400 Subject: [PATCH 1/5] refactor(editor): Convert `workflowActivate` mixin into a composable (no-changelog) (#9423) --- .../src/components/NodeDetailsView.vue | 7 +- .../src/components/WorkflowActivator.vue | 8 +- .../src/composables/useWorkflowActivate.ts | 135 ++++++++++++++++++ .../editor-ui/src/mixins/workflowActivate.ts | 134 ----------------- 4 files changed, 141 insertions(+), 143 deletions(-) create mode 100644 packages/editor-ui/src/composables/useWorkflowActivate.ts delete mode 100644 packages/editor-ui/src/mixins/workflowActivate.ts diff --git a/packages/editor-ui/src/components/NodeDetailsView.vue b/packages/editor-ui/src/components/NodeDetailsView.vue index d89000a0fb6f8..21ac42f7ff080 100644 --- a/packages/editor-ui/src/components/NodeDetailsView.vue +++ b/packages/editor-ui/src/components/NodeDetailsView.vue @@ -165,7 +165,7 @@ import { START_NODE_TYPE, STICKY_NODE_TYPE, } from '@/constants'; -import { workflowActivate } from '@/mixins/workflowActivate'; +import { useWorkflowActivate } from '@/composables/useWorkflowActivate'; import { dataPinningEventBus } from '@/event-bus'; import { useWorkflowsStore } from '@/stores/workflows.store'; import { useNDVStore } from '@/stores/ndv.store'; @@ -189,7 +189,6 @@ export default defineComponent({ NDVDraggablePanels, TriggerPanel, }, - mixins: [workflowActivate], props: { readOnly: { type: Boolean, @@ -210,16 +209,16 @@ export default defineComponent({ const pinnedData = usePinnedData(activeNode); const router = useRouter(); const workflowHelpers = useWorkflowHelpers({ router }); + const workflowActivate = useWorkflowActivate(); return { externalHooks, nodeHelpers, pinnedData, workflowHelpers, + workflowActivate, ...useDeviceSupport(), ...useMessage(), - // eslint-disable-next-line @typescript-eslint/no-misused-promises - ...workflowActivate.setup?.(props, ctx), }; }, data() { diff --git a/packages/editor-ui/src/components/WorkflowActivator.vue b/packages/editor-ui/src/components/WorkflowActivator.vue index 0e0c31f3c242e..10599a97c36b6 100644 --- a/packages/editor-ui/src/components/WorkflowActivator.vue +++ b/packages/editor-ui/src/components/WorkflowActivator.vue @@ -52,7 +52,7 @@ diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index a4b23aa966d9c..06cc232faf828 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -404,7 +404,7 @@ export default defineComponent({ type: String, }, nodeType: { - type: Object as PropType, + type: Object as PropType, }, readOnly: { type: Boolean, From 04dd4760e173bfc8a938413a5915d63291da8afe Mon Sep 17 00:00:00 2001 From: Csaba Tuncsik Date: Fri, 17 May 2024 17:18:15 +0200 Subject: [PATCH 3/5] fix(editor): Fix i18n translation addition (#9451) --- .../CredentialEdit/CredentialConfig.vue | 7 ++- packages/editor-ui/src/plugins/i18n/index.ts | 59 +++++-------------- 2 files changed, 19 insertions(+), 47 deletions(-) diff --git a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue index 226e0f7fab875..b1e5dfa07bb02 100644 --- a/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue +++ b/packages/editor-ui/src/components/CredentialEdit/CredentialConfig.vue @@ -186,6 +186,7 @@ export default defineComponent({ }, parentTypes: { type: Array as PropType, + default: () => [], }, credentialData: {}, credentialId: { @@ -274,7 +275,7 @@ export default defineComponent({ return ''; } - const appName = getAppNameFromCredType((this.credentialType as ICredentialType).displayName); + const appName = getAppNameFromCredType(this.credentialType.displayName); return ( appName || @@ -282,13 +283,13 @@ export default defineComponent({ ); }, credentialTypeName(): string { - return (this.credentialType as ICredentialType)?.name; + return this.credentialType?.name; }, credentialOwnerName(): string { return this.credentialsStore.getCredentialOwnerNameById(`${this.credentialId}`); }, documentationUrl(): string { - const type = this.credentialType as ICredentialType; + const type = this.credentialType; const activeNode = this.ndvStore.activeNode; const isCommunityNode = activeNode ? isCommunityPackageName(activeNode.type) : false; diff --git a/packages/editor-ui/src/plugins/i18n/index.ts b/packages/editor-ui/src/plugins/i18n/index.ts index 999393189d197..b120829c0a559 100644 --- a/packages/editor-ui/src/plugins/i18n/index.ts +++ b/packages/editor-ui/src/plugins/i18n/index.ts @@ -1,7 +1,6 @@ import type { Plugin } from 'vue'; import axios from 'axios'; import { createI18n } from 'vue-i18n'; -import type { I18nOptions } from 'vue-i18n'; import { locale } from 'n8n-design-system'; import type { INodeProperties, INodePropertyCollection, INodePropertyOptions } from 'n8n-workflow'; @@ -437,9 +436,7 @@ async function setLanguage(language: string) { return language; } -export async function loadLanguage(language?: string) { - if (!language) return; - +export async function loadLanguage(language: string) { if (i18nInstance.global.locale === language) { return await setLanguage(language); } @@ -466,25 +463,15 @@ export async function loadLanguage(language?: string) { */ export function addNodeTranslation( nodeTranslation: { [nodeType: string]: object }, - language: keyof I18nOptions['messages'], + language: string, ) { - const oldNodesBase: { nodes: {} } = i18nInstance.global.messages[language]['n8n-nodes-base'] ?? { - nodes: {}, - }; - - const updatedNodes = { - ...oldNodesBase.nodes, - ...nodeTranslation, - }; - - const newNodesBase = { - 'n8n-nodes-base': Object.assign(oldNodesBase, { nodes: updatedNodes }), + const newMessages = { + 'n8n-nodes-base': { + nodes: nodeTranslation, + }, }; - i18nInstance.global.setLocaleMessage( - language, - Object.assign(i18nInstance.global.messages[language], newNodesBase), - ); + i18nInstance.global.mergeLocaleMessage(language, newMessages); } /** @@ -492,38 +479,22 @@ export function addNodeTranslation( */ export function addCredentialTranslation( nodeCredentialTranslation: { [credentialType: string]: object }, - language: keyof I18nOptions['messages'], + language: string, ) { - const oldNodesBase: { credentials: {} } = i18nInstance.global.messages[language][ - 'n8n-nodes-base' - ] || { credentials: {} }; - - const updatedCredentials = { - ...oldNodesBase.credentials, - ...nodeCredentialTranslation, - }; - - const newNodesBase = { - 'n8n-nodes-base': Object.assign(oldNodesBase, { credentials: updatedCredentials }), + const newMessages = { + 'n8n-nodes-base': { + credentials: nodeCredentialTranslation, + }, }; - i18nInstance.global.setLocaleMessage( - language, - Object.assign(i18nInstance.global.messages[language], newNodesBase), - ); + i18nInstance.global.mergeLocaleMessage(language, newMessages); } /** * Add a node's header strings to the i18n instance's `messages` object. */ -export function addHeaders( - headers: INodeTranslationHeaders, - language: keyof I18nOptions['messages'], -) { - i18nInstance.global.setLocaleMessage( - language, - Object.assign(i18nInstance.global.messages[language], { headers }), - ); +export function addHeaders(headers: INodeTranslationHeaders, language: string) { + i18nInstance.global.mergeLocaleMessage(language, { headers }); } export const i18n: I18nClass = new I18nClass(); From 6f2d83bffdc43e890c82177e4ceb35a9ffa7e90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 17 May 2024 17:55:29 +0200 Subject: [PATCH 4/5] fix(core): Setup webhook stopping endpoint after the CORS middleware (no-changelog) (#9454) --- packages/cli/src/AbstractServer.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/cli/src/AbstractServer.ts b/packages/cli/src/AbstractServer.ts index 1610600e50e88..b458981c150a8 100644 --- a/packages/cli/src/AbstractServer.ts +++ b/packages/cli/src/AbstractServer.ts @@ -207,13 +207,6 @@ export abstract class AbstractServer { // Register a handler this.app.all(`/${this.endpointFormTest}/:path(*)`, webhookRequestHandler(testWebhooks)); this.app.all(`/${this.endpointWebhookTest}/:path(*)`, webhookRequestHandler(testWebhooks)); - - // Removes a test webhook - // TODO UM: check if this needs validation with user management. - this.app.delete( - `/${this.restEndpoint}/test-webhook/:id`, - send(async (req) => await testWebhooks.cancelWebhook(req.params.id)), - ); } // Block bots from scanning the application @@ -230,6 +223,16 @@ export abstract class AbstractServer { this.setupDevMiddlewares(); } + if (this.testWebhooksEnabled) { + const testWebhooks = Container.get(TestWebhooks); + // Removes a test webhook + // TODO UM: check if this needs validation with user management. + this.app.delete( + `/${this.restEndpoint}/test-webhook/:id`, + send(async (req) => await testWebhooks.cancelWebhook(req.params.id)), + ); + } + // Setup body parsing middleware after the webhook handlers are setup this.app.use(bodyParser); From bf2ee51e36214c44bb7bb57d27d33dd29a8ccf72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=A4=95=E0=A4=BE=E0=A4=B0=E0=A4=A4=E0=A5=8B=E0=A4=AB?= =?UTF-8?q?=E0=A5=8D=E0=A4=AB=E0=A5=87=E0=A4=B2=E0=A4=B8=E0=A5=8D=E0=A4=95?= =?UTF-8?q?=E0=A5=8D=E0=A4=B0=E0=A4=BF=E0=A4=AA=E0=A5=8D=E0=A4=9F=E2=84=A2?= Date: Fri, 17 May 2024 18:43:50 +0200 Subject: [PATCH 5/5] ci: Delete unused code in some backend tests (no-changelog) (#9456) --- packages/core/test/helpers/index.ts | 96 +-- .../nodes-base/test/nodes/ExecuteWorkflow.ts | 8 +- packages/nodes-base/test/nodes/Helpers.ts | 72 +-- packages/workflow/src/Interfaces.ts | 22 +- packages/workflow/test/Helpers.ts | 561 ++---------------- packages/workflow/test/NodeTypes.ts | 60 +- packages/workflow/test/RoutingNode.test.ts | 65 +- 7 files changed, 118 insertions(+), 766 deletions(-) diff --git a/packages/core/test/helpers/index.ts b/packages/core/test/helpers/index.ts index fc0717188b12e..5f935ef85086c 100644 --- a/packages/core/test/helpers/index.ts +++ b/packages/core/test/helpers/index.ts @@ -4,14 +4,8 @@ import { readdirSync, readFileSync } from 'fs'; const BASE_DIR = path.resolve(__dirname, '../../..'); import type { - ICredentialDataDecryptedObject, IDataObject, IDeferredPromise, - IExecuteWorkflowInfo, - IHttpRequestHelper, - IHttpRequestOptions, - INode, - INodeCredentialsDetails, INodeType, INodeTypes, IRun, @@ -24,66 +18,13 @@ import type { INodeTypeData, } from 'n8n-workflow'; -import { ApplicationError, ICredentialsHelper, NodeHelpers, WorkflowHooks } from 'n8n-workflow'; -import { Credentials } from '@/Credentials'; +import { ApplicationError, NodeHelpers, WorkflowHooks } from 'n8n-workflow'; import { predefinedNodesTypes } from './constants'; - -export class CredentialsHelper extends ICredentialsHelper { - async authenticate( - credentials: ICredentialDataDecryptedObject, - typeName: string, - requestParams: IHttpRequestOptions, - ): Promise { - return requestParams; - } - - async preAuthentication( - helpers: IHttpRequestHelper, - credentials: ICredentialDataDecryptedObject, - typeName: string, - node: INode, - credentialsExpired: boolean, - ): Promise { - return undefined; - } - - getParentTypes(name: string): string[] { - return []; - } - - async getDecrypted( - additionalData: IWorkflowExecuteAdditionalData, - nodeCredentials: INodeCredentialsDetails, - type: string, - ): Promise { - return {}; - } - - async getCredentials( - nodeCredentials: INodeCredentialsDetails, - type: string, - ): Promise { - return new Credentials({ id: null, name: '' }, '', [], ''); - } - - async updateCredentials( - nodeCredentials: INodeCredentialsDetails, - type: string, - data: ICredentialDataDecryptedObject, - ): Promise {} -} +import { mock } from 'jest-mock-extended'; class NodeTypesClass implements INodeTypes { - nodeTypes: INodeTypeData; - - constructor(nodeTypes?: INodeTypeData) { - if (nodeTypes) { - this.nodeTypes = nodeTypes; - } else { - this.nodeTypes = predefinedNodesTypes; - } - } + constructor(private nodeTypes: INodeTypeData = predefinedNodesTypes) {} getByName(nodeType: string): INodeType | IVersionedNodeType { return this.nodeTypes[nodeType].type; @@ -92,11 +33,15 @@ class NodeTypesClass implements INodeTypes { getByNameAndVersion(nodeType: string, version?: number): INodeType { return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version); } + + getKnownTypes(): IDataObject { + throw new Error('Method not implemented.'); + } } let nodeTypesInstance: NodeTypesClass | undefined; -export function NodeTypes(nodeTypes?: INodeTypeData): NodeTypesClass { +export function NodeTypes(nodeTypes?: INodeTypeData): INodeTypes { if (nodeTypesInstance === undefined || nodeTypes !== undefined) { nodeTypesInstance = new NodeTypesClass(nodeTypes); } @@ -110,7 +55,7 @@ export function WorkflowExecuteAdditionalData( ): IWorkflowExecuteAdditionalData { const hookFunctions = { nodeExecuteAfter: [ - async (nodeName: string, data: ITaskData): Promise => { + async (nodeName: string, _data: ITaskData): Promise => { nodeExecutionOrder.push(nodeName); }, ], @@ -121,26 +66,9 @@ export function WorkflowExecuteAdditionalData( ], }; - const workflowData: IWorkflowBase = { - name: '', - createdAt: new Date(), - updatedAt: new Date(), - active: true, - nodes: [], - connections: {}, - }; - - return { - credentialsHelper: new CredentialsHelper(), - hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData), - executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo) => {}, - sendDataToUI: (message: string) => {}, - restApiUrl: '', - webhookBaseUrl: 'webhook', - webhookWaitingBaseUrl: 'webhook-waiting', - webhookTestBaseUrl: 'webhook-test', - userId: '123', - }; + return mock({ + hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', mock()), + }); } const preparePinData = (pinData: IDataObject) => { diff --git a/packages/nodes-base/test/nodes/ExecuteWorkflow.ts b/packages/nodes-base/test/nodes/ExecuteWorkflow.ts index 528eca40a901b..5975d14071cba 100644 --- a/packages/nodes-base/test/nodes/ExecuteWorkflow.ts +++ b/packages/nodes-base/test/nodes/ExecuteWorkflow.ts @@ -10,6 +10,7 @@ export async function executeWorkflow(testData: WorkflowTestData, nodeTypes: INo const { baseUrl, mocks } = testData.nock; const agent = nock(baseUrl); mocks.forEach(({ method, path, statusCode, responseBody }) => + // @ts-expect-error agent[method](path).reply(statusCode, responseBody), ); } @@ -24,11 +25,7 @@ export async function executeWorkflow(testData: WorkflowTestData, nodeTypes: INo }); const waitPromise = await createDeferredPromise(); const nodeExecutionOrder: string[] = []; - const additionalData = Helpers.WorkflowExecuteAdditionalData( - waitPromise, - nodeExecutionOrder, - testData, - ); + const additionalData = Helpers.WorkflowExecuteAdditionalData(waitPromise, nodeExecutionOrder); let executionData: IRun; const runExecutionData: IRunExecutionData = { @@ -36,6 +33,7 @@ export async function executeWorkflow(testData: WorkflowTestData, nodeTypes: INo runData: {}, }, executionData: { + metadata: {}, contextData: {}, waitingExecution: {}, waitingExecutionSource: null, diff --git a/packages/nodes-base/test/nodes/Helpers.ts b/packages/nodes-base/test/nodes/Helpers.ts index b5c6714280fbf..063fa6f3d9b28 100644 --- a/packages/nodes-base/test/nodes/Helpers.ts +++ b/packages/nodes-base/test/nodes/Helpers.ts @@ -6,6 +6,7 @@ import { isEmpty } from 'lodash'; import { get } from 'lodash'; import { BinaryDataService, Credentials, constructExecutionMetaData } from 'n8n-core'; import { Container } from 'typedi'; +import { mock } from 'jest-mock-extended'; import type { CredentialLoadingDetails, ICredentialDataDecryptedObject, @@ -15,7 +16,6 @@ import type { IDataObject, IDeferredPromise, IExecuteFunctions, - IExecuteWorkflowInfo, IGetNodeParameterOptions, IHttpRequestHelper, IHttpRequestOptions, @@ -87,22 +87,20 @@ class CredentialType implements ICredentialTypes { return knownCredentials[type]?.supportedNodes ?? []; } - getParentTypes(typeName: string): string[] { + getParentTypes(_typeName: string): string[] { return []; } } -export class CredentialsHelper extends ICredentialsHelper { - constructor(private credentialTypes: ICredentialTypes) { - super(''); - } +const credentialTypes = new CredentialType(); +class CredentialsHelper extends ICredentialsHelper { async authenticate( credentials: ICredentialDataDecryptedObject, typeName: string, requestParams: IHttpRequestOptions, ): Promise { - const credentialType = this.credentialTypes.getByName(typeName); + const credentialType = credentialTypes.getByName(typeName); if (typeof credentialType.authenticate === 'function') { return await credentialType.authenticate(credentials, requestParams); } @@ -110,21 +108,21 @@ export class CredentialsHelper extends ICredentialsHelper { } async preAuthentication( - helpers: IHttpRequestHelper, - credentials: ICredentialDataDecryptedObject, - typeName: string, - node: INode, - credentialsExpired: boolean, + _helpers: IHttpRequestHelper, + _credentials: ICredentialDataDecryptedObject, + _typeName: string, + _node: INode, + _credentialsExpired: boolean, ): Promise { return undefined; } - getParentTypes(name: string): string[] { + getParentTypes(_name: string): string[] { return []; } async getDecrypted( - additionalData: IWorkflowExecuteAdditionalData, + _additionalData: IWorkflowExecuteAdditionalData, nodeCredentials: INodeCredentialsDetails, type: string, ): Promise { @@ -132,27 +130,26 @@ export class CredentialsHelper extends ICredentialsHelper { } async getCredentials( - nodeCredentials: INodeCredentialsDetails, - type: string, + _nodeCredentials: INodeCredentialsDetails, + _type: string, ): Promise { - return new Credentials({ id: null, name: '' }, '', [], ''); + return new Credentials({ id: null, name: '' }, '', ''); } async updateCredentials( - nodeCredentials: INodeCredentialsDetails, - type: string, - data: ICredentialDataDecryptedObject, + _nodeCredentials: INodeCredentialsDetails, + _type: string, + _data: ICredentialDataDecryptedObject, ): Promise {} } export function WorkflowExecuteAdditionalData( waitPromise: IDeferredPromise, nodeExecutionOrder: string[], - workflowTestData?: WorkflowTestData, ): IWorkflowExecuteAdditionalData { const hookFunctions = { nodeExecuteAfter: [ - async (nodeName: string, data: ITaskData): Promise => { + async (nodeName: string, _data: ITaskData): Promise => { nodeExecutionOrder.push(nodeName); }, ], @@ -163,27 +160,10 @@ export function WorkflowExecuteAdditionalData( ], }; - const workflowData: IWorkflowBase = { - name: '', - createdAt: new Date(), - updatedAt: new Date(), - active: true, - nodes: [], - connections: {}, - }; - return { - credentialsHelper: new CredentialsHelper(credentialTypes), - hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', workflowData), - executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise => {}, - sendDataToUI: (message: string) => {}, - restApiUrl: '', - webhookBaseUrl: 'webhook', - webhookWaitingBaseUrl: 'webhook-waiting', - webhookTestBaseUrl: 'webhook-test', - userId: '123', - variables: {}, - instanceBaseUrl: '', - }; + return mock({ + credentialsHelper: new CredentialsHelper(), + hooks: new WorkflowHooks(hookFunctions, 'trigger', '1', mock()), + }); } class NodeTypes implements INodeTypes { @@ -209,6 +189,10 @@ class NodeTypes implements INodeTypes { getByNameAndVersion(nodeType: string, version?: number): INodeType { return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType].type, version); } + + getKnownTypes(): IDataObject { + throw new Error('Method not implemented.'); + } } export function createTemporaryDir(prefix = 'n8n') { @@ -225,8 +209,6 @@ export async function initBinaryDataService(mode: 'default' | 'filesystem' = 'de Container.set(BinaryDataService, binaryDataService); } -const credentialTypes = new CredentialType(); - export function setup(testData: WorkflowTestData[] | WorkflowTestData) { if (!Array.isArray(testData)) { testData = [testData]; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index f3a4264edc37e..dd1c15b92bff9 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -2060,21 +2060,23 @@ export const eventNamesAiNodes = [ export type EventNamesAiNodesType = (typeof eventNamesAiNodes)[number]; +export interface ExecuteWorkflowOptions { + node?: INode; + parentWorkflowId: string; + inputData?: INodeExecutionData[]; + parentExecutionId?: string; + loadedWorkflowData?: IWorkflowBase; + loadedRunData?: any; + parentWorkflowSettings?: IWorkflowSettings; + parentCallbackManager?: CallbackManager; +} + export interface IWorkflowExecuteAdditionalData { credentialsHelper: ICredentialsHelper; executeWorkflow: ( workflowInfo: IExecuteWorkflowInfo, additionalData: IWorkflowExecuteAdditionalData, - options: { - node?: INode; - parentWorkflowId: string; - inputData?: INodeExecutionData[]; - parentExecutionId?: string; - loadedWorkflowData?: IWorkflowBase; - loadedRunData?: any; - parentWorkflowSettings?: IWorkflowSettings; - parentCallbackManager?: CallbackManager; - }, + options: ExecuteWorkflowOptions, ) => Promise; executionId?: string; restartExecutionId?: string; diff --git a/packages/workflow/test/Helpers.ts b/packages/workflow/test/Helpers.ts index 8b07f3d6aa366..c618f5fd0c207 100644 --- a/packages/workflow/test/Helpers.ts +++ b/packages/workflow/test/Helpers.ts @@ -1,531 +1,65 @@ +import { readFileSync } from 'fs'; +import path from 'path'; +import { mock } from 'jest-mock-extended'; import get from 'lodash/get'; + import type { - IAdditionalCredentialOptions, - IAllExecuteFunctions, - IContextObject, - ICredentialDataDecryptedObject, - ICredentialsEncrypted, - IDataObject, - IExecuteData, - IExecuteFunctions, - IExecuteResponsePromiseData, IExecuteSingleFunctions, - IExecuteWorkflowInfo, - IHttpRequestHelper, IHttpRequestOptions, IN8nHttpFullResponse, IN8nHttpResponse, INode, - INodeCredentialsDetails, - INodeExecutionData, - INodeParameters, - INodeType, + INodeTypes, IRunExecutionData, - ITaskDataConnections, - IWorkflowBase, - IWorkflowDataProxyAdditionalKeys, - IWorkflowDataProxyData, - IWorkflowExecuteAdditionalData, - NodeParameterValue, - WorkflowExecuteMode, } from '@/Interfaces'; -import { ICredentials, ICredentialsHelper } from '@/Interfaces'; import type { Workflow } from '@/Workflow'; -import { WorkflowDataProxy } from '@/WorkflowDataProxy'; -import { WorkflowHooks } from '@/WorkflowHooks'; -import * as NodeHelpers from '@/NodeHelpers'; -import { deepCopy } from '@/utils'; -import { getGlobalState } from '@/GlobalState'; -import { ApplicationError } from '@/errors/application.error'; import { NodeTypes as NodeTypesClass } from './NodeTypes'; -import { readFileSync } from 'fs'; -import path from 'path'; - -export interface INodeTypesObject { - [key: string]: INodeType; -} - -export class Credentials extends ICredentials { - setData(data: ICredentialDataDecryptedObject) { - this.data = JSON.stringify(data); - } - - getData(): ICredentialDataDecryptedObject { - if (this.data === undefined) { - throw new ApplicationError('No data is set so nothing can be returned'); - } - return JSON.parse(this.data); - } - - getDataToSave(): ICredentialsEncrypted { - if (this.data === undefined) { - throw new ApplicationError('No credentials were set to save'); - } - - return { - id: this.id, - name: this.name, - type: this.type, - data: this.data, - }; - } -} - -export class CredentialsHelper extends ICredentialsHelper { - async authenticate( - credentials: ICredentialDataDecryptedObject, - typeName: string, - requestParams: IHttpRequestOptions, - ): Promise { - return requestParams; - } - - async preAuthentication( - helpers: IHttpRequestHelper, - credentials: ICredentialDataDecryptedObject, - typeName: string, - node: INode, - credentialsExpired: boolean, - ): Promise<{ updatedCredentials: boolean; data: ICredentialDataDecryptedObject }> { - return { updatedCredentials: false, data: {} }; - } - - getParentTypes(name: string): string[] { - return []; - } - - async getDecrypted( - additionalData: IWorkflowExecuteAdditionalData, - nodeCredentials: INodeCredentialsDetails, - type: string, - ): Promise { - return {}; - } - - async getCredentials( - nodeCredentials: INodeCredentialsDetails, - type: string, - ): Promise { - return new Credentials({ id: null, name: '' }, '', [], ''); - } - - async updateCredentials( - nodeCredentials: INodeCredentialsDetails, - type: string, - data: ICredentialDataDecryptedObject, - ): Promise {} -} - -export function getNodeParameter( - workflow: Workflow, - runExecutionData: IRunExecutionData | null, - runIndex: number, - connectionInputData: INodeExecutionData[], - node: INode, - parameterName: string, - itemIndex: number, - mode: WorkflowExecuteMode, - additionalKeys: IWorkflowDataProxyAdditionalKeys, - executeData: IExecuteData, - fallbackValue?: any, -): NodeParameterValue | INodeParameters | NodeParameterValue[] | INodeParameters[] | object { - const nodeType = workflow.nodeTypes.getByNameAndVersion(node.type, node.typeVersion); - if (nodeType === undefined) { - throw new ApplicationError('Node type is unknown so cannot return parameter value', { - tags: { nodeType: node.type }, - }); - } - - const value = get(node.parameters, parameterName, fallbackValue); - - if (value === undefined) { - throw new ApplicationError('Could not get parameter', { extra: { parameterName } }); - } - - let returnData; - try { - returnData = workflow.expression.getParameterValue( - value, - runExecutionData, - runIndex, - itemIndex, - node.name, - connectionInputData, - mode, - additionalKeys, - ); - } catch (e) { - e.message += ` [Error in parameter: "${parameterName}"]`; - throw e; - } - - return returnData; -} - -export function getExecuteFunctions( - workflow: Workflow, - runExecutionData: IRunExecutionData, - runIndex: number, - connectionInputData: INodeExecutionData[], - inputData: ITaskDataConnections, - node: INode, - itemIndex: number, - additionalData: IWorkflowExecuteAdditionalData, - executeData: IExecuteData, - mode: WorkflowExecuteMode, -): IExecuteFunctions { - return ((workflow, runExecutionData, connectionInputData, inputData, node) => { - return { - continueOnFail: () => { - return false; - }, - evaluateExpression: (expression: string, itemIndex: number) => { - return expression; - }, - async executeWorkflow( - workflowInfo: IExecuteWorkflowInfo, - inputData?: INodeExecutionData[], - ): Promise { - return await additionalData.executeWorkflow(workflowInfo, additionalData, { inputData }); - }, - getContext(type: string): IContextObject { - return NodeHelpers.getContext(runExecutionData, type, node); - }, - async getCredentials( - type: string, - itemIndex?: number, - ): Promise { - return { - apiKey: '12345', - }; - }, - getExecutionId: (): string => { - return additionalData.executionId!; - }, - getInputData: (inputIndex = 0, inputName = 'main') => { - if (!inputData.hasOwnProperty(inputName)) { - // Return empty array because else it would throw error when nothing is connected to input - return []; - } - - if (inputData[inputName].length < inputIndex) { - throw new ApplicationError('Could not get input index', { - extra: { inputIndex, inputName }, - }); - } - - if (inputData[inputName][inputIndex] === null) { - throw new ApplicationError('Value of input did not get set', { - extra: { inputIndex, inputName }, - }); - } - - return inputData[inputName][inputIndex] as INodeExecutionData[]; - }, - getNodeParameter: ( - parameterName: string, - itemIndex: number, - fallbackValue?: any, - ): - | NodeParameterValue - | INodeParameters - | NodeParameterValue[] - | INodeParameters[] - | object => { - return getNodeParameter( - workflow, - runExecutionData, - runIndex, - connectionInputData, - node, - parameterName, - itemIndex, - mode, - {}, - fallbackValue, - ); - }, - getMode: (): WorkflowExecuteMode => { - return mode; - }, - getNode: () => { - return deepCopy(node); - }, - getRestApiUrl: (): string => { - return additionalData.restApiUrl; - }, - getTimezone: (): string => { - return workflow.settings.timezone ?? getGlobalState().defaultTimezone; - }, - getExecuteData: (): IExecuteData => { - return executeData; - }, - getWorkflow: () => { - return { - id: workflow.id, - name: workflow.name, - active: workflow.active, - }; - }, - getWorkflowDataProxy: (itemIndex: number): IWorkflowDataProxyData => { - const dataProxy = new WorkflowDataProxy( - workflow, - runExecutionData, - runIndex, - itemIndex, - node.name, - connectionInputData, - {}, - mode, - {}, - executeData, - ); - return dataProxy.getDataProxy(); - }, - getWorkflowStaticData(type: string): IDataObject { - return workflow.getStaticData(type, node); - }, - prepareOutputData: NodeHelpers.prepareOutputData, - async putExecutionToWait(waitTill: Date): Promise { - runExecutionData.waitTill = waitTill; - }, - sendMessageToUI(...args: any[]): void { - if (mode !== 'manual') { - return; - } - try { - if (additionalData.sendMessageToUI) { - additionalData.sendMessageToUI(node.name, args); - } - } catch (error) { - console.error(`There was a problem sending message to UI: ${error.message}`); - } - }, - async sendResponse(response: IExecuteResponsePromiseData): Promise { - await additionalData.hooks?.executeHookFunctions('sendResponse', [response]); - }, - helpers: { - async httpRequest( - requestOptions: IHttpRequestOptions, - ): Promise { - return { - body: { - headers: {}, - statusCode: 200, - requestOptions, - }, - }; - }, - async requestWithAuthentication( - this: IAllExecuteFunctions, - credentialsType: string, - requestOptions: IHttpRequestOptions, - additionalCredentialOptions?: IAdditionalCredentialOptions, - ): Promise { - return { - body: { - headers: {}, - statusCode: 200, - credentialsType, - requestOptions, - additionalCredentialOptions, - }, - }; - }, - async httpRequestWithAuthentication( - this: IAllExecuteFunctions, - credentialsType: string, - requestOptions: IHttpRequestOptions, - additionalCredentialOptions?: IAdditionalCredentialOptions, - ): Promise { - return { - body: { - headers: {}, - statusCode: 200, - credentialsType, - requestOptions, - additionalCredentialOptions, - }, - }; - }, - }, - }; - })(workflow, runExecutionData, connectionInputData, inputData, node); -} export function getExecuteSingleFunctions( workflow: Workflow, runExecutionData: IRunExecutionData, runIndex: number, - connectionInputData: INodeExecutionData[], - inputData: ITaskDataConnections, node: INode, itemIndex: number, - additionalData: IWorkflowExecuteAdditionalData, - executeData: IExecuteData, - mode: WorkflowExecuteMode, ): IExecuteSingleFunctions { - return ((workflow, runExecutionData, connectionInputData, inputData, node, itemIndex) => { - return { - continueOnFail: () => { - return false; - }, - evaluateExpression: (expression: string, evaluateItemIndex: number | undefined) => { - return expression; - }, - getContext(type: string): IContextObject { - return NodeHelpers.getContext(runExecutionData, type, node); - }, - async getCredentials(type: string): Promise { - return { - apiKey: '12345', - }; - }, - getInputData: (inputIndex = 0, inputName = 'main') => { - if (!inputData.hasOwnProperty(inputName)) { - // Return empty array because else it would throw error when nothing is connected to input - return { json: {} }; - } - - if (inputData[inputName].length < inputIndex) { - throw new ApplicationError('Could not get input index', { - extra: { inputIndex, inputName }, - }); - } - - const allItems = inputData[inputName][inputIndex]; - - if (allItems === null) { - throw new ApplicationError('Value of input did not get set', { - extra: { inputIndex, inputName }, - }); - } - - if (allItems[itemIndex] === null) { - throw new ApplicationError('Value of input with item index did not get set', { - extra: { inputIndex, inputName, itemIndex }, - }); - } - - return allItems[itemIndex]; - }, - getItemIndex: () => { - return itemIndex; - }, - getMode: (): WorkflowExecuteMode => { - return mode; - }, - getNode: () => { - return deepCopy(node); - }, - getRestApiUrl: (): string => { - return additionalData.restApiUrl; - }, - getTimezone: (): string => { - return workflow.settings.timezone ?? getGlobalState().defaultTimezone; - }, - getExecuteData: (): IExecuteData => { - return executeData; - }, - getNodeParameter: ( - parameterName: string, - fallbackValue?: any, - ): - | NodeParameterValue - | INodeParameters - | NodeParameterValue[] - | INodeParameters[] - | object => { - return getNodeParameter( - workflow, - runExecutionData, - runIndex, - connectionInputData, - node, - parameterName, - itemIndex, - mode, - {}, - fallbackValue, - ); - }, - getWorkflow: () => { + return mock({ + getItemIndex: () => itemIndex, + getNodeParameter: (parameterName: string) => { + return workflow.expression.getParameterValue( + get(node.parameters, parameterName), + runExecutionData, + runIndex, + itemIndex, + node.name, + [], + 'internal', + {}, + ); + }, + getWorkflow: () => ({ + id: workflow.id, + name: workflow.name, + active: workflow.active, + }), + helpers: mock({ + async httpRequest( + requestOptions: IHttpRequestOptions, + ): Promise { return { - id: workflow.id, - name: workflow.name, - active: workflow.active, + body: { + headers: {}, + statusCode: 200, + requestOptions, + }, }; }, - getWorkflowDataProxy: (): IWorkflowDataProxyData => { - const dataProxy = new WorkflowDataProxy( - workflow, - runExecutionData, - runIndex, - itemIndex, - node.name, - connectionInputData, - {}, - mode, - {}, - executeData, - ); - return dataProxy.getDataProxy(); - }, - getWorkflowStaticData(type: string): IDataObject { - return workflow.getStaticData(type, node); - }, - helpers: { - async httpRequest( - requestOptions: IHttpRequestOptions, - ): Promise { - return { - body: { - headers: {}, - statusCode: 200, - requestOptions, - }, - }; - }, - async requestWithAuthentication( - this: IAllExecuteFunctions, - credentialsType: string, - requestOptions: IHttpRequestOptions, - additionalCredentialOptions?: IAdditionalCredentialOptions, - ): Promise { - return { - body: { - headers: {}, - statusCode: 200, - credentialsType, - requestOptions, - additionalCredentialOptions, - }, - }; - }, - async httpRequestWithAuthentication( - this: IAllExecuteFunctions, - credentialsType: string, - requestOptions: IHttpRequestOptions, - additionalCredentialOptions?: IAdditionalCredentialOptions, - ): Promise { - return { - body: { - headers: {}, - statusCode: 200, - credentialsType, - requestOptions, - additionalCredentialOptions, - }, - }; - }, - }, - }; - })(workflow, runExecutionData, connectionInputData, inputData, node, itemIndex); + }), + }); } let nodeTypesInstance: NodeTypesClass | undefined; -export function NodeTypes(): NodeTypesClass { +export function NodeTypes(): INodeTypes { if (nodeTypesInstance === undefined) { nodeTypesInstance = new NodeTypesClass(); } @@ -533,29 +67,6 @@ export function NodeTypes(): NodeTypesClass { return nodeTypesInstance; } -export function WorkflowExecuteAdditionalData(): IWorkflowExecuteAdditionalData { - const workflowData: IWorkflowBase = { - name: '', - createdAt: new Date(), - updatedAt: new Date(), - active: true, - nodes: [], - connections: {}, - }; - - return { - credentialsHelper: new CredentialsHelper(), - hooks: new WorkflowHooks({}, 'trigger', '1', workflowData), - executeWorkflow: async (workflowInfo: IExecuteWorkflowInfo): Promise => {}, - sendDataToUI: (message: string) => {}, - restApiUrl: '', - webhookBaseUrl: 'webhook', - webhookWaitingBaseUrl: 'webhook-waiting', - webhookTestBaseUrl: 'webhook-test', - userId: '123', - }; -} - const BASE_DIR = path.resolve(__dirname, '..'); export const readJsonFileSync = (filePath: string) => JSON.parse(readFileSync(path.join(BASE_DIR, filePath), 'utf-8')) as T; diff --git a/packages/workflow/test/NodeTypes.ts b/packages/workflow/test/NodeTypes.ts index 3adbe32109d32..101ec534c26cd 100644 --- a/packages/workflow/test/NodeTypes.ts +++ b/packages/workflow/test/NodeTypes.ts @@ -1,11 +1,13 @@ -import { - NodeHelpers, - type INodeType, - type INodeTypeData, - type INodeTypes, - type IVersionedNodeType, - type LoadedClass, -} from '@/index'; +import { mock } from 'jest-mock-extended'; +import type { + IDataObject, + INodeType, + INodeTypeData, + INodeTypes, + IVersionedNodeType, + LoadedClass, +} from '@/Interfaces'; +import * as NodeHelpers from '@/NodeHelpers'; const stickyNode: LoadedClass = { type: { @@ -626,39 +628,6 @@ export class NodeTypes implements INodeTypes { }, }, }, - 'test.switch': { - sourcePath: '', - type: { - description: { - displayName: 'Set', - name: 'set', - group: ['input'], - version: 1, - description: 'Switches', - defaults: { - name: 'Switch', - color: '#0000FF', - }, - inputs: ['main'], - outputs: ['main', 'main', 'main', 'main'], - outputNames: ['0', '1', '2', '3'], - properties: [ - { - displayName: 'Value1', - name: 'value1', - type: 'string', - default: 'default-value1', - }, - { - displayName: 'Value2', - name: 'value2', - type: 'string', - default: 'default-value2', - }, - ], - }, - }, - }, }; getByName(nodeType: string): INodeType | IVersionedNodeType { @@ -669,5 +638,14 @@ export class NodeTypes implements INodeTypes { if (this.nodeTypes[nodeType]?.type) { return NodeHelpers.getVersionedNodeType(this.nodeTypes[nodeType]?.type, version); } + return mock({ + description: { + properties: [], + }, + }); + } + + getKnownTypes(): IDataObject { + throw new Error('Method not implemented.'); } } diff --git a/packages/workflow/test/RoutingNode.test.ts b/packages/workflow/test/RoutingNode.test.ts index 9496e84d6a4f9..4b0ac4a5e4998 100644 --- a/packages/workflow/test/RoutingNode.test.ts +++ b/packages/workflow/test/RoutingNode.test.ts @@ -7,23 +7,24 @@ import type { INodeProperties, IExecuteSingleFunctions, IHttpRequestOptions, - IN8nHttpFullResponse, ITaskDataConnections, INodeExecuteFunctions, IN8nRequestOperations, INodeCredentialDescription, IExecuteData, INodeTypeDescription, + IWorkflowExecuteAdditionalData, + IExecuteFunctions, } from '@/Interfaces'; import { RoutingNode } from '@/RoutingNode'; import { Workflow } from '@/Workflow'; import * as Helpers from './Helpers'; +import { mock } from 'jest-mock-extended'; const postReceiveFunction1 = async function ( this: IExecuteSingleFunctions, items: INodeExecutionData[], - response: IN8nHttpFullResponse, ): Promise { items.forEach((item) => (item.json1 = { success: true })); return items; @@ -39,6 +40,8 @@ const preSendFunction1 = async function ( }; describe('RoutingNode', () => { + const additionalData = mock(); + describe('getRequestOptionsFromParameters', () => { const tests: Array<{ description: string; @@ -659,7 +662,6 @@ describe('RoutingNode', () => { const itemIndex = 0; const connectionInputData: INodeExecutionData[] = []; const runExecutionData: IRunExecutionData = { resultData: { runData: {} } }; - const additionalData = Helpers.WorkflowExecuteAdditionalData(); const path = ''; const nodeType = nodeTypes.getByNameAndVersion(node.type); @@ -693,17 +695,8 @@ describe('RoutingNode', () => { workflow, runExecutionData, runIndex, - connectionInputData, - {}, node, itemIndex, - additionalData, - { - node, - data: {}, - source: null, - }, - mode, ); const result = routingNode.getRequestOptionsFromParameters( @@ -1704,7 +1697,6 @@ describe('RoutingNode', () => { const itemIndex = 0; const connectionInputData: INodeExecutionData[] = []; const runExecutionData: IRunExecutionData = { resultData: { runData: {} } }; - const additionalData = Helpers.WorkflowExecuteAdditionalData(); const nodeType = nodeTypes.getByNameAndVersion(baseNode.type); const inputData: ITaskDataConnections = { @@ -1751,34 +1743,15 @@ describe('RoutingNode', () => { } as IExecuteData; const nodeExecuteFunctions: Partial = { - getExecuteFunctions: () => { - return Helpers.getExecuteFunctions( + getExecuteFunctions: () => mock(), + getExecuteSingleFunctions: () => + Helpers.getExecuteSingleFunctions( workflow, runExecutionData, runIndex, - connectionInputData, - {}, node, itemIndex, - additionalData, - executeData, - mode, - ); - }, - getExecuteSingleFunctions: () => { - return Helpers.getExecuteSingleFunctions( - workflow, - runExecutionData, - runIndex, - connectionInputData, - {}, - node, - itemIndex, - additionalData, - executeData, - mode, - ); - }, + ), }; const result = await routingNode.runNode( @@ -1870,7 +1843,6 @@ describe('RoutingNode', () => { const itemIndex = 0; const connectionInputData: INodeExecutionData[] = []; const runExecutionData: IRunExecutionData = { resultData: { runData: {} } }; - const additionalData = Helpers.WorkflowExecuteAdditionalData(); const nodeType = nodeTypes.getByNameAndVersion(baseNode.type); const inputData: ITaskDataConnections = { @@ -1925,32 +1897,13 @@ describe('RoutingNode', () => { let currentItemIndex = 0; for (let iteration = 0; iteration < inputData.main[0]!.length; iteration++) { const nodeExecuteFunctions: Partial = { - getExecuteFunctions: () => { - return Helpers.getExecuteFunctions( - workflow, - runExecutionData, - runIndex, - connectionInputData, - {}, - node, - itemIndex + iteration, - additionalData, - executeData, - mode, - ); - }, getExecuteSingleFunctions: () => { return Helpers.getExecuteSingleFunctions( workflow, runExecutionData, runIndex, - connectionInputData, - {}, node, itemIndex + iteration, - additionalData, - executeData, - mode, ); }, };