Skip to content

Commit

Permalink
fix(core): Fix supportedNodes for non-lazy loaded community packages …
Browse files Browse the repository at this point in the history
…(no-changelog) (n8n-io#11329)
  • Loading branch information
netroy authored Dec 10, 2024
1 parent 1c52bf9 commit 2d36b42
Show file tree
Hide file tree
Showing 28 changed files with 1,433 additions and 584 deletions.
5 changes: 2 additions & 3 deletions packages/@n8n/nodes-langchain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
"clean": "rimraf dist .turbo",
"dev": "pnpm run watch",
"typecheck": "tsc --noEmit",
"build": "tsc -p tsconfig.build.json && pnpm n8n-copy-icons && pnpm build:metadata",
"build:metadata": "pnpm n8n-generate-known && pnpm n8n-generate-ui-types",
"build": "tsc -p tsconfig.build.json && pnpm n8n-copy-icons && pnpm n8n-generate-metadata",
"format": "biome format --write .",
"format:check": "biome ci .",
"lint": "eslint nodes credentials --quiet",
"lintfix": "eslint nodes credentials --fix",
"watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\" --onSuccess \"pnpm n8n-generate-ui-types\"",
"watch": "tsc-watch -p tsconfig.build.json --onCompilationComplete \"tsc-alias -p tsconfig.build.json\" --onSuccess \"pnpm n8n-generate-metadata\"",
"test": "jest",
"test:dev": "jest --watch"
},
Expand Down
140 changes: 110 additions & 30 deletions packages/cli/src/__tests__/credential-types.test.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,121 @@
import { Container } from 'typedi';
import { mock } from 'jest-mock-extended';
import { UnrecognizedCredentialTypeError } from 'n8n-core';
import type { ICredentialType, LoadedClass } from 'n8n-workflow';

import { CredentialTypes } from '@/credential-types';
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import { mockInstance } from '@test/mocking';
import type { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';

describe('CredentialTypes', () => {
const mockNodesAndCredentials = mockInstance(LoadNodesAndCredentials, {
loadedCredentials: {
fakeFirstCredential: {
type: {
name: 'fakeFirstCredential',
displayName: 'Fake First Credential',
properties: [],
},
sourcePath: '',
},
fakeSecondCredential: {
type: {
name: 'fakeSecondCredential',
displayName: 'Fake Second Credential',
properties: [],
},
sourcePath: '',
},
},
const loadNodesAndCredentials = mock<LoadNodesAndCredentials>();

const credentialTypes = new CredentialTypes(loadNodesAndCredentials);

const testCredential: LoadedClass<ICredentialType> = {
sourcePath: '',
type: mock(),
};

loadNodesAndCredentials.getCredential.mockImplementation((credentialType) => {
if (credentialType === 'testCredential') return testCredential;
throw new UnrecognizedCredentialTypeError(credentialType);
});

beforeEach(() => {
jest.clearAllMocks();
});

describe('getByName', () => {
test('Should throw error when calling invalid credential name', () => {
expect(() => credentialTypes.getByName('unknownCredential')).toThrowError('c');
});

test('Should return correct credential type for valid name', () => {
expect(credentialTypes.getByName('testCredential')).toStrictEqual(testCredential.type);
});
});

describe('recognizes', () => {
test('Should recognize credential type that exists in knownCredentials', () => {
const credentialTypes = new CredentialTypes(
mock<LoadNodesAndCredentials>({
loadedCredentials: {},
knownCredentials: { testCredential: mock({ supportedNodes: [] }) },
}),
);

expect(credentialTypes.recognizes('testCredential')).toBe(true);
});

test('Should recognize credential type that exists in loadedCredentials', () => {
const credentialTypes = new CredentialTypes(
mock<LoadNodesAndCredentials>({
loadedCredentials: { testCredential },
knownCredentials: {},
}),
);

expect(credentialTypes.recognizes('testCredential')).toBe(true);
});

test('Should not recognize unknown credential type', () => {
expect(credentialTypes.recognizes('unknownCredential')).toBe(false);
});
});

const credentialTypes = Container.get(CredentialTypes);
describe('getSupportedNodes', () => {
test('Should return supported nodes for known credential type', () => {
const supportedNodes = ['node1', 'node2'];
const credentialTypes = new CredentialTypes(
mock<LoadNodesAndCredentials>({
knownCredentials: { testCredential: mock({ supportedNodes }) },
}),
);

expect(credentialTypes.getSupportedNodes('testCredential')).toEqual(supportedNodes);
});

test('Should throw error when calling invalid credential name', () => {
expect(() => credentialTypes.getByName('fakeThirdCredential')).toThrowError();
test('Should return empty array for unknown credential type supported nodes', () => {
expect(credentialTypes.getSupportedNodes('unknownCredential')).toBeEmptyArray();
});
});

test('Should return correct credential type for valid name', () => {
const mockedCredentialTypes = mockNodesAndCredentials.loadedCredentials;
expect(credentialTypes.getByName('fakeFirstCredential')).toStrictEqual(
mockedCredentialTypes.fakeFirstCredential.type,
);
describe('getParentTypes', () => {
test('Should return parent types for credential type with extends', () => {
const credentialTypes = new CredentialTypes(
mock<LoadNodesAndCredentials>({
knownCredentials: {
childType: { extends: ['parentType1', 'parentType2'] },
parentType1: { extends: ['grandparentType'] },
parentType2: { extends: [] },
grandparentType: { extends: [] },
},
}),
);

const parentTypes = credentialTypes.getParentTypes('childType');
expect(parentTypes).toContain('parentType1');
expect(parentTypes).toContain('parentType2');
expect(parentTypes).toContain('grandparentType');
});

test('Should return empty array for credential type without extends', () => {
const credentialTypes = new CredentialTypes(
mock<LoadNodesAndCredentials>({
knownCredentials: { testCredential: { extends: [] } },
}),
);

expect(credentialTypes.getParentTypes('testCredential')).toBeEmptyArray();
});

test('Should return empty array for unknown credential type parent types', () => {
const credentialTypes = new CredentialTypes(
mock<LoadNodesAndCredentials>({
knownCredentials: {},
}),
);

expect(credentialTypes.getParentTypes('unknownCredential')).toBeEmptyArray();
});
});
});
76 changes: 20 additions & 56 deletions packages/cli/src/__tests__/credentials-helper.test.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,30 @@
import { mock } from 'jest-mock-extended';
import type {
IAuthenticateGeneric,
ICredentialDataDecryptedObject,
ICredentialType,
IHttpRequestOptions,
INode,
INodeProperties,
INodeTypes,
} from 'n8n-workflow';
import { NodeConnectionType, deepCopy } from 'n8n-workflow';
import { Workflow } from 'n8n-workflow';
import Container from 'typedi';
import { deepCopy, Workflow } from 'n8n-workflow';

import { CredentialTypes } from '@/credential-types';
import { CredentialsHelper } from '@/credentials-helper';
import { CredentialsRepository } from '@/databases/repositories/credentials.repository';
import { SharedCredentialsRepository } from '@/databases/repositories/shared-credentials.repository';
import { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
import { NodeTypes } from '@/node-types';
import { mockInstance } from '@test/mocking';
import type { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';

describe('CredentialsHelper', () => {
mockInstance(CredentialsRepository);
mockInstance(SharedCredentialsRepository);
const mockNodesAndCredentials = mockInstance(LoadNodesAndCredentials, {
loadedNodes: {
'test.set': {
sourcePath: '',
type: {
description: {
displayName: 'Set',
name: 'set',
group: ['input'],
version: 1,
description: 'Sets a value',
defaults: {
name: 'Set',
color: '#0000FF',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
properties: [
{
displayName: 'Value1',
name: 'value1',
type: 'string',
default: 'default-value1',
},
{
displayName: 'Value2',
name: 'value2',
type: 'string',
default: 'default-value2',
},
],
},
},
},
},
});
const nodeTypes = mock<INodeTypes>();
const mockNodesAndCredentials = mock<LoadNodesAndCredentials>();

const nodeTypes = mockInstance(NodeTypes);
const credentialsHelper = new CredentialsHelper(
new CredentialTypes(mockNodesAndCredentials),
mock(),
mock(),
mock(),
mock(),
);

describe('authenticate', () => {
const tests: Array<{
Expand Down Expand Up @@ -272,19 +239,16 @@ describe('CredentialsHelper', () => {

for (const testData of tests) {
test(testData.description, async () => {
//@ts-expect-error `loadedCredentials` is a getter and we are replacing it here with a property
mockNodesAndCredentials.loadedCredentials = {
[testData.input.credentialType.name]: {
type: testData.input.credentialType,
sourcePath: '',
},
};
const { credentialType } = testData.input;

const credentialsHelper = Container.get(CredentialsHelper);
mockNodesAndCredentials.getCredential.calledWith(credentialType.name).mockReturnValue({
type: credentialType,
sourcePath: '',
});

const result = await credentialsHelper.authenticate(
testData.input.credentials,
testData.input.credentialType.name,
credentialType.name,
deepCopy(incomingRequestOptions),
workflow,
node,
Expand Down
Loading

0 comments on commit 2d36b42

Please sign in to comment.