diff --git a/packages/cli/src/CredentialTypes.ts b/packages/cli/src/CredentialTypes.ts index 7185560b00..47c02f9a62 100644 --- a/packages/cli/src/CredentialTypes.ts +++ b/packages/cli/src/CredentialTypes.ts @@ -18,6 +18,10 @@ class CredentialTypesClass implements ICredentialTypes { return this.getCredential(credentialType).type; } + getNodeTypesToTestWith(type: string): string[] { + return this.knownCredentials[type]?.nodesToTestWith ?? []; + } + private getCredential(type: string): LoadedClass { const loadedCredentials = this.loadedCredentials; if (type in loadedCredentials) { diff --git a/packages/cli/src/CredentialsHelper.ts b/packages/cli/src/CredentialsHelper.ts index cde1530b51..5e73a93864 100644 --- a/packages/cli/src/CredentialsHelper.ts +++ b/packages/cli/src/CredentialsHelper.ts @@ -39,6 +39,7 @@ import { IHttpRequestHelper, INodeTypeData, INodeTypes, + ICredentialTypes, } from 'n8n-workflow'; import * as Db from '@/Db'; @@ -47,25 +48,48 @@ import * as WorkflowExecuteAdditionalData from '@/WorkflowExecuteAdditionalData' import { User } from '@db/entities/User'; import { CredentialsEntity } from '@db/entities/CredentialsEntity'; import { NodeTypes } from '@/NodeTypes'; -import { CredentialsOverwrites } from '@/CredentialsOverwrites'; import { CredentialTypes } from '@/CredentialTypes'; +import { CredentialsOverwrites } from '@/CredentialsOverwrites'; import { whereClause } from './UserManagement/UserManagementHelper'; +import { RESPONSE_ERROR_MESSAGES } from './constants'; -const mockNodesData: INodeTypeData = {}; -const mockNodeTypes: INodeTypes = { - getAll(): Array { - return Object.values(mockNodesData).map((data) => data.type); +const mockNode = { + name: '', + typeVersion: 1, + type: 'mock', + position: [0, 0], + parameters: {} as INodeParameters, +} as INode; + +const mockNodesData: INodeTypeData = { + mock: { + sourcePath: '', + type: { + description: { properties: [] as INodeProperties[] }, + } as INodeType, }, - getByNameAndVersion(nodeType: string, version?: number): INodeType | undefined { - if (mockNodesData[nodeType] === undefined) { - return undefined; +}; + +const mockNodeTypes: INodeTypes = { + getByName(nodeType: string): INodeType | IVersionedNodeType { + return mockNodesData[nodeType]?.type; + }, + getByNameAndVersion(nodeType: string, version?: number): INodeType { + if (!mockNodesData[nodeType]) { + throw new Error(`${RESPONSE_ERROR_MESSAGES.NO_NODE}: ${nodeType}`); } return NodeHelpers.getVersionedNodeType(mockNodesData[nodeType].type, version); }, }; export class CredentialsHelper extends ICredentialsHelper { - private credentialTypes = CredentialTypes(); + constructor( + encryptionKey: string, + private credentialTypes: ICredentialTypes = CredentialTypes(), + private nodeTypes: INodeTypes = NodeTypes(), + ) { + super(encryptionKey); + } /** * Add the required authentication information to the request @@ -387,16 +411,8 @@ export class CredentialsHelper extends ICredentialsHelper { throw e; } } else { - const node = { - name: '', - typeVersion: 1, - type: 'mock', - position: [0, 0], - parameters: {} as INodeParameters, - } as INode; - const workflow = new Workflow({ - nodes: [node], + nodes: [mockNode], connections: {}, active: false, nodeTypes: mockNodeTypes, @@ -404,7 +420,7 @@ export class CredentialsHelper extends ICredentialsHelper { // Resolve expressions if any are set decryptedData = workflow.expression.getComplexParameterValue( - node, + mockNode, decryptedData as INodeParameters, mode, defaultTimezone, @@ -457,28 +473,27 @@ export class CredentialsHelper extends ICredentialsHelper { await Db.collections.Credentials.update(findQuery, newCredentialsData); } - getCredentialTestFunction( + private getCredentialTestFunction( credentialType: string, - nodeToTestWith?: string, ): ICredentialTestFunction | ICredentialTestRequestData | undefined { - const nodeTypes = NodeTypes(); - const allNodes = nodeTypes.getAll(); + // Check if test is defined on credentials + const type = this.credentialTypes.getByName(credentialType); + if (type.test) { + return { + testRequest: type.test, + }; + } - // Check all the nodes one by one if they have a test function defined - for (let i = 0; i < allNodes.length; i++) { - const node = allNodes[i]; - - if (nodeToTestWith && node.description.name !== nodeToTestWith) { - // eslint-disable-next-line no-continue - continue; - } + const nodeTypesToTestWith = this.credentialTypes.getNodeTypesToTestWith(credentialType); + for (const nodeName of nodeTypesToTestWith) { + const node = this.nodeTypes.getByName(nodeName); // Always set to an array even if node is not versioned to not having // to duplicate the logic const allNodeTypes: INodeType[] = []; if (node instanceof VersionedNodeType) { // Node is versioned - allNodeTypes.push(...Object.values((node as IVersionedNodeType).nodeVersions)); + allNodeTypes.push(...Object.values(node.nodeVersions)); } else { // Node is not versioned allNodeTypes.push(node as INodeType); @@ -487,49 +502,35 @@ export class CredentialsHelper extends ICredentialsHelper { // Check each of the node versions for credential tests for (const nodeType of allNodeTypes) { // Check each of teh credentials - for (const credential of nodeType.description.credentials ?? []) { - if (credential.name === credentialType && !!credential.testedBy) { - if (typeof credential.testedBy === 'string') { - if (Object.prototype.hasOwnProperty.call(node, 'nodeVersions')) { + for (const { name, testedBy } of nodeType.description.credentials ?? []) { + if (name === credentialType && !!testedBy) { + if (typeof testedBy === 'string') { + if (node instanceof VersionedNodeType) { // The node is versioned. So check all versions for test function // starting with the latest - const versions = Object.keys((node as IVersionedNodeType).nodeVersions) - .sort() - .reverse(); + const versions = Object.keys(node.nodeVersions).sort().reverse(); for (const version of versions) { - const versionedNode = (node as IVersionedNodeType).nodeVersions[ - parseInt(version, 10) - ]; - if ( - versionedNode.methods?.credentialTest && - versionedNode.methods?.credentialTest[credential.testedBy] - ) { - return versionedNode.methods?.credentialTest[credential.testedBy]; + const versionedNode = node.nodeVersions[parseInt(version, 10)]; + const credentialTest = versionedNode.methods?.credentialTest; + if (credentialTest && testedBy in credentialTest) { + return credentialTest[testedBy]; } } } // Test is defined as string which links to a function - return (node as unknown as INodeType).methods?.credentialTest![credential.testedBy]; + return (node as unknown as INodeType).methods?.credentialTest![testedBy]; } // Test is defined as JSON with a definition for the request to make return { nodeType, - testRequest: credential.testedBy, + testRequest: testedBy, }; } } } } - // Check if test is defined on credentials - const type = this.credentialTypes.getByName(credentialType); - if (type.test) { - return { - testRequest: type.test, - }; - } - return undefined; } @@ -537,9 +538,8 @@ export class CredentialsHelper extends ICredentialsHelper { user: User, credentialType: string, credentialsDecrypted: ICredentialsDecrypted, - nodeToTestWith?: string, ): Promise { - const credentialTestFunction = this.getCredentialTestFunction(credentialType, nodeToTestWith); + const credentialTestFunction = this.getCredentialTestFunction(credentialType); if (credentialTestFunction === undefined) { return Promise.resolve({ status: 'Error', @@ -570,8 +570,7 @@ export class CredentialsHelper extends ICredentialsHelper { if (credentialTestFunction.nodeType) { nodeType = credentialTestFunction.nodeType; } else { - const nodeTypes = NodeTypes(); - nodeType = nodeTypes.getByNameAndVersion('n8n-nodes-base.noOp'); + nodeType = this.nodeTypes.getByNameAndVersion('n8n-nodes-base.noOp'); } const node: INode = { diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index c2b623b197..b30b6ca62e 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -15,7 +15,7 @@ import type { ITelemetrySettings, ITelemetryTrackProperties, IWorkflowBase as IWorkflowBaseWorkflow, - LoadingDetails, + CredentialLoadingDetails, Workflow, WorkflowActivateMode, WorkflowExecuteMode, @@ -59,7 +59,7 @@ export interface ICustomRequest extends Request { } export interface ICredentialsTypeData { - [key: string]: LoadingDetails; + [key: string]: CredentialLoadingDetails; } export interface ICredentialsOverwrite { diff --git a/packages/cli/src/LoadNodesAndCredentials.ts b/packages/cli/src/LoadNodesAndCredentials.ts index 618cd0f5e7..4a1c0eff29 100644 --- a/packages/cli/src/LoadNodesAndCredentials.ts +++ b/packages/cli/src/LoadNodesAndCredentials.ts @@ -366,8 +366,12 @@ export class LoadNodesAndCredentialsClass implements INodesAndCredentials { } for (const type in known.credentials) { - const { className, sourcePath } = known.credentials[type]; - this.known.credentials[type] = { className, sourcePath: path.join(dir, sourcePath) }; + const { className, sourcePath, nodesToTestWith } = known.credentials[type]; + this.known.credentials[type] = { + className, + sourcePath: path.join(dir, sourcePath), + nodesToTestWith: nodesToTestWith?.map((nodeName) => `${packageName}.${nodeName}`), + }; } } diff --git a/packages/cli/src/NodeTypes.ts b/packages/cli/src/NodeTypes.ts index cb43074fdb..b81f2c1e5f 100644 --- a/packages/cli/src/NodeTypes.ts +++ b/packages/cli/src/NodeTypes.ts @@ -21,10 +21,6 @@ class NodeTypesClass implements INodeTypes { } } - getAll(): Array { - return Object.values(this.loadedNodes).map(({ type }) => type); - } - /** * Variant of `getByNameAndVersion` that includes the node's source path, used to locate a node's translations. */ @@ -43,6 +39,10 @@ class NodeTypesClass implements INodeTypes { return { description: { ...description }, sourcePath: nodeType.sourcePath }; } + getByName(nodeType: string): INodeType | IVersionedNodeType { + return this.getNode(nodeType).type; + } + getByNameAndVersion(nodeType: string, version?: number): INodeType { return NodeHelpers.getVersionedNodeType(this.getNode(nodeType).type, version); } diff --git a/packages/cli/src/credentials/credentials.controller.ee.ts b/packages/cli/src/credentials/credentials.controller.ee.ts index 0ff7e37f82..17f6344e0e 100644 --- a/packages/cli/src/credentials/credentials.controller.ee.ts +++ b/packages/cli/src/credentials/credentials.controller.ee.ts @@ -106,7 +106,7 @@ EECredentialsController.get( EECredentialsController.post( '/test', ResponseHelper.send(async (req: CredentialRequest.Test): Promise => { - const { credentials, nodeToTestWith } = req.body; + const { credentials } = req.body; const encryptionKey = await EECredentials.getEncryptionKey(); @@ -122,7 +122,7 @@ EECredentialsController.post( Object.assign(credentials, { data: decryptedData }); } - return EECredentials.test(req.user, encryptionKey, credentials, nodeToTestWith); + return EECredentials.test(req.user, encryptionKey, credentials); }), ); diff --git a/packages/cli/src/credentials/credentials.controller.ts b/packages/cli/src/credentials/credentials.controller.ts index 5c66e0c467..91c25732cb 100644 --- a/packages/cli/src/credentials/credentials.controller.ts +++ b/packages/cli/src/credentials/credentials.controller.ts @@ -113,10 +113,10 @@ credentialsController.get( credentialsController.post( '/test', ResponseHelper.send(async (req: CredentialRequest.Test): Promise => { - const { credentials, nodeToTestWith } = req.body; + const { credentials } = req.body; const encryptionKey = await CredentialsService.getEncryptionKey(); - return CredentialsService.test(req.user, encryptionKey, credentials, nodeToTestWith); + return CredentialsService.test(req.user, encryptionKey, credentials); }), ); diff --git a/packages/cli/src/credentials/credentials.service.ts b/packages/cli/src/credentials/credentials.service.ts index 4ca1fef92b..84b8389637 100644 --- a/packages/cli/src/credentials/credentials.service.ts +++ b/packages/cli/src/credentials/credentials.service.ts @@ -282,10 +282,9 @@ export class CredentialsService { user: User, encryptionKey: string, credentials: ICredentialsDecrypted, - nodeToTestWith: string | undefined, ): Promise { const helper = new CredentialsHelper(encryptionKey); - return helper.testCredentials(user, credentials.type, credentials, nodeToTestWith); + return helper.testCredentials(user, credentials.type, credentials); } } diff --git a/packages/cli/test/unit/CredentialsHelper.test.ts b/packages/cli/test/unit/CredentialsHelper.test.ts index 2062329c7e..f3fc8043dd 100644 --- a/packages/cli/test/unit/CredentialsHelper.test.ts +++ b/packages/cli/test/unit/CredentialsHelper.test.ts @@ -2,7 +2,6 @@ import { IAuthenticateGeneric, ICredentialDataDecryptedObject, ICredentialType, - ICredentialTypeData, IHttpRequestOptions, INode, INodeProperties, @@ -234,9 +233,13 @@ describe('CredentialsHelper', () => { }, }; - CredentialTypes(mockNodesAndCredentials); + const credentialTypes = CredentialTypes(mockNodesAndCredentials); - const credentialsHelper = new CredentialsHelper(TEST_ENCRYPTION_KEY); + const credentialsHelper = new CredentialsHelper( + TEST_ENCRYPTION_KEY, + credentialTypes, + nodeTypes, + ); const result = await credentialsHelper.authenticate( testData.input.credentials, diff --git a/packages/cli/test/unit/Helpers.ts b/packages/cli/test/unit/Helpers.ts index 8b4065d491..155439996e 100644 --- a/packages/cli/test/unit/Helpers.ts +++ b/packages/cli/test/unit/Helpers.ts @@ -3,6 +3,7 @@ import { INodeType, INodeTypeData, INodeTypes, + IVersionedNodeType, NodeHelpers, } from 'n8n-workflow'; @@ -48,12 +49,8 @@ class NodeTypesClass implements INodeTypes { } } - getAll(): INodeType[] { - return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedNodeType(data.type)); - } - - getByName(nodeType: string): INodeType { - return this.getByNameAndVersion(nodeType); + getByName(nodeType: string): INodeType | IVersionedNodeType { + return this.nodeTypes[nodeType].type; } getByNameAndVersion(nodeType: string, version?: number): INodeType { diff --git a/packages/core/bin/generate-known b/packages/core/bin/generate-known index 288c2c25fb..55163e45a6 100755 --- a/packages/core/bin/generate-known +++ b/packages/core/bin/generate-known @@ -22,7 +22,9 @@ const loadClass = (sourcePath) => { } }; -const generate = (kind) => { +const nodesToTestWith = {}; + +const generate = async (kind) => { const data = glob .sync(`dist/${kind}/**/*.${kind === 'nodes' ? 'node' : kind}.js`, { cwd: packageDir, @@ -34,12 +36,28 @@ const generate = (kind) => { const name = kind === 'nodes' ? instance.description.name : instance.name; if (name in obj) console.error('already loaded', kind, name, sourcePath); else obj[name] = { className, sourcePath }; + + if (kind === 'nodes') { + const { credentials } = instance.description; + if (credentials && credentials.length) { + for (const credential of credentials) { + nodesToTestWith[credential.name] = nodesToTestWith[credential.name] || []; + nodesToTestWith[credential.name].push(name); + } + } + } else { + if (name in nodesToTestWith) { + obj[name].nodesToTestWith = nodesToTestWith[name]; + } + } return obj; }, {}); LoggerProxy.info(`Detected ${Object.keys(data).length} ${kind}`); - return writeJSON(`known/${kind}.json`, data); + await writeJSON(`known/${kind}.json`, data); + return data; }; (async () => { - await Promise.all([generate('credentials'), generate('nodes')]); + await generate('nodes'); + await generate('credentials'); })(); diff --git a/packages/core/test/Helpers.ts b/packages/core/test/Helpers.ts index 41d92f7d62..8c4cb22a8b 100644 --- a/packages/core/test/Helpers.ts +++ b/packages/core/test/Helpers.ts @@ -17,6 +17,7 @@ import { INodeTypes, IRun, ITaskData, + IVersionedNodeType, IWorkflowBase, IWorkflowExecuteAdditionalData, NodeHelpers, @@ -805,12 +806,8 @@ class NodeTypesClass implements INodeTypes { }, }; - getAll(): INodeType[] { - return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedNodeType(data.type)); - } - - getByName(nodeType: string): INodeType { - return this.getByNameAndVersion(nodeType); + getByName(nodeType: string): INodeType | IVersionedNodeType { + return this.nodeTypes[nodeType].type; } getByNameAndVersion(nodeType: string, version?: number): INodeType { diff --git a/packages/editor-ui/src/mixins/workflowHelpers.ts b/packages/editor-ui/src/mixins/workflowHelpers.ts index b5e0847ced..df0dd38115 100644 --- a/packages/editor-ui/src/mixins/workflowHelpers.ts +++ b/packages/editor-ui/src/mixins/workflowHelpers.ts @@ -335,10 +335,6 @@ export const workflowHelpers = mixins( const nodeTypes: INodeTypes = { nodeTypes: {}, init: async (nodeTypes?: INodeTypeData): Promise => { }, - getAll: (): Array => { - // Does not get used in Workflow so no need to return it - return []; - }, getByNameAndVersion: (nodeType: string, version?: number): INodeType | undefined => { const nodeTypeDescription = this.nodeTypesStore.getNodeType(nodeType, version); diff --git a/packages/nodes-base/nodes/Mocean/Mocean.node.ts b/packages/nodes-base/nodes/Mocean/Mocean.node.ts index aad4d65be5..0a251ec76c 100644 --- a/packages/nodes-base/nodes/Mocean/Mocean.node.ts +++ b/packages/nodes-base/nodes/Mocean/Mocean.node.ts @@ -221,7 +221,7 @@ export class Mocean implements INodeType { let endpoint: string; let operation: string; - let requesetMethod: string; + let requestMethod: string; let resource: string; let text: string; let dlrUrl: string; @@ -238,7 +238,7 @@ export class Mocean implements INodeType { resource = this.getNodeParameter('resource', itemIndex, '') as string; operation = this.getNodeParameter('operation', itemIndex, '') as string; text = this.getNodeParameter('message', itemIndex, '') as string; - requesetMethod = 'POST'; + requestMethod = 'POST'; body['mocean-from'] = this.getNodeParameter('from', itemIndex, '') as string; body['mocean-to'] = this.getNodeParameter('to', itemIndex, '') as string; @@ -271,13 +271,7 @@ export class Mocean implements INodeType { } if (operation === 'send') { - const responseData = await moceanApiRequest.call( - this, - requesetMethod, - endpoint, - body, - qs, - ); + const responseData = await moceanApiRequest.call(this, requestMethod, endpoint, body, qs); for (const item of responseData[dataKey] as IDataObject[]) { item.type = resource; diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 00c32e9ce2..45b9ac2090 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -181,11 +181,7 @@ export interface IHttpRequestHelper { helpers: { httpRequest: IAllExecuteFunctions['helpers']['httpRequest'] }; } export abstract class ICredentialsHelper { - encryptionKey: string; - - constructor(encryptionKey: string) { - this.encryptionKey = encryptionKey; - } + constructor(readonly encryptionKey: string) {} abstract getParentTypes(name: string): string[]; @@ -329,6 +325,7 @@ export interface ICredentialType { export interface ICredentialTypes { recognizes(credentialType: string): boolean; getByName(credentialType: string): ICredentialType; + getNodeTypesToTestWith(type: string): string[]; } // The way the credentials get saved in the database (data encrypted) @@ -1209,7 +1206,6 @@ export interface INodeCredentialTestResult { } export interface INodeCredentialTestRequest { - nodeToTestWith?: string; // node name i.e. slack credentials: ICredentialsDecrypted; } @@ -1474,18 +1470,24 @@ export type WebhookResponseData = 'allEntries' | 'firstEntryJson' | 'firstEntryB export type WebhookResponseMode = 'onReceived' | 'lastNode'; export interface INodeTypes { - getAll(): Array; - getByNameAndVersion(nodeType: string, version?: number): INodeType | undefined; + getByName(nodeType: string): INodeType | IVersionedNodeType; + getByNameAndVersion(nodeType: string, version?: number): INodeType; } -export type LoadingDetails = { +type LoadingDetails = { className: string; sourcePath: string; }; +export type CredentialLoadingDetails = LoadingDetails & { + nodesToTestWith?: string[]; +}; + +export type NodeLoadingDetails = LoadingDetails; + export type KnownNodesAndCredentials = { - nodes: Record; - credentials: Record; + nodes: Record; + credentials: Record; }; export interface LoadedClass { diff --git a/packages/workflow/test/Helpers.ts b/packages/workflow/test/Helpers.ts index 289e6baa93..d6a93d575d 100644 --- a/packages/workflow/test/Helpers.ts +++ b/packages/workflow/test/Helpers.ts @@ -673,12 +673,8 @@ class NodeTypesClass implements INodeTypes { }, }; - getAll(): INodeType[] { - return Object.values(this.nodeTypes).map((data) => NodeHelpers.getVersionedNodeType(data.type)); - } - - getByName(nodeType: string): INodeType | IVersionedNodeType | undefined { - return this.getByNameAndVersion(nodeType); + getByName(nodeType: string): INodeType | IVersionedNodeType { + return this.nodeTypes[nodeType].type; } getByNameAndVersion(nodeType: string, version?: number): INodeType {