Files
n8n-enterprise-unlocked/packages/cli/src/__tests__/credentials-helper.test.ts

287 lines
6.7 KiB
TypeScript

import { CredentialsEntity, type CredentialsRepository } from '@n8n/db';
import { EntityNotFoundError } from '@n8n/typeorm';
import { mock } from 'jest-mock-extended';
import type {
IAuthenticateGeneric,
ICredentialDataDecryptedObject,
ICredentialType,
IHttpRequestOptions,
INode,
INodeProperties,
INodeTypes,
} from 'n8n-workflow';
import { deepCopy, Workflow } from 'n8n-workflow';
import { CredentialTypes } from '@/credential-types';
import { CredentialsHelper } from '@/credentials-helper';
import { CredentialNotFoundError } from '@/errors/credential-not-found.error';
import type { LoadNodesAndCredentials } from '@/load-nodes-and-credentials';
describe('CredentialsHelper', () => {
const nodeTypes = mock<INodeTypes>();
const mockNodesAndCredentials = mock<LoadNodesAndCredentials>();
const credentialsRepository = mock<CredentialsRepository>();
const credentialsHelper = new CredentialsHelper(
new CredentialTypes(mockNodesAndCredentials),
mock(),
credentialsRepository,
mock(),
mock(),
);
describe('getCredentials', () => {
test('turns `EntityNotFoundError` into `CredentialNotFoundError`s', async () => {
credentialsRepository.findOneByOrFail.mockRejectedValueOnce(
new EntityNotFoundError(CredentialsEntity, 'foo'),
);
await expect(
credentialsHelper.getCredentials({ id: '1', name: 'foo' }, 'bar'),
).rejects.toThrow(CredentialNotFoundError);
});
test('passes other error through', async () => {
const errorMessage = 'Connection terminated due to connection timeout';
credentialsRepository.findOneByOrFail.mockRejectedValueOnce(new Error(errorMessage));
await expect(
credentialsHelper.getCredentials({ id: '1', name: 'foo' }, 'bar'),
).rejects.toThrow(errorMessage);
});
});
describe('authenticate', () => {
const tests: Array<{
description: string;
input: {
credentials: ICredentialDataDecryptedObject;
credentialType: ICredentialType;
};
output: IHttpRequestOptions;
}> = [
{
description: 'basicAuth, default property names',
input: {
credentials: {
user: 'user1',
password: 'password1',
},
credentialType: new (class TestApi implements ICredentialType {
name = 'testApi';
displayName = 'Test API';
properties: INodeProperties[] = [
{
displayName: 'User',
name: 'user',
type: 'string',
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
default: '',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
auth: {
username: '={{$credentials.user}}',
password: '={{$credentials.password}}',
},
},
};
})(),
},
output: {
url: '',
headers: {},
auth: { username: 'user1', password: 'password1' },
qs: {},
},
},
{
description: 'headerAuth',
input: {
credentials: {
accessToken: 'test',
},
credentialType: new (class TestApi implements ICredentialType {
name = 'testApi';
displayName = 'Test API';
properties: INodeProperties[] = [
{
displayName: 'Access Token',
name: 'accessToken',
type: 'string',
default: '',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '=Bearer {{$credentials.accessToken}}',
},
},
};
})(),
},
output: { url: '', headers: { Authorization: 'Bearer test' }, qs: {} },
},
{
description: 'headerAuth, key and value expressions',
input: {
credentials: {
accessToken: 'test',
},
credentialType: new (class TestApi implements ICredentialType {
name = 'testApi';
displayName = 'Test API';
properties: INodeProperties[] = [
{
displayName: 'Access Token',
name: 'accessToken',
type: 'string',
default: '',
},
];
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
'={{$credentials.accessToken}}': '=Bearer {{$credentials.accessToken}}',
},
},
};
})(),
},
output: { url: '', headers: { test: 'Bearer test' }, qs: {} },
},
{
description: 'queryAuth',
input: {
credentials: {
accessToken: 'test',
},
credentialType: new (class TestApi implements ICredentialType {
name = 'testApi';
displayName = 'Test API';
properties: INodeProperties[] = [
{
displayName: 'Access Token',
name: 'accessToken',
type: 'string',
default: '',
},
];
authenticate = {
type: 'generic',
properties: {
qs: {
accessToken: '={{$credentials.accessToken}}',
},
},
} as IAuthenticateGeneric;
})(),
},
output: { url: '', headers: {}, qs: { accessToken: 'test' } },
},
{
description: 'custom authentication',
input: {
credentials: {
accessToken: 'test',
user: 'testUser',
},
credentialType: new (class TestApi implements ICredentialType {
name = 'testApi';
displayName = 'Test API';
properties: INodeProperties[] = [
{
displayName: 'My Token',
name: 'myToken',
type: 'string',
default: '',
},
];
async authenticate(
credentials: ICredentialDataDecryptedObject,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
requestOptions.headers!.Authorization = `Bearer ${credentials.accessToken}`;
requestOptions.qs!.user = credentials.user;
return requestOptions;
}
})(),
},
output: {
url: '',
headers: { Authorization: 'Bearer test' },
qs: { user: 'testUser' },
},
},
];
const node: INode = {
id: 'uuid-1',
parameters: {},
name: 'test',
type: 'test.set',
typeVersion: 1,
position: [0, 0],
};
const incomingRequestOptions = {
url: '',
headers: {},
qs: {},
};
const workflow = new Workflow({
nodes: [node],
connections: {},
active: false,
nodeTypes,
});
for (const testData of tests) {
test(testData.description, async () => {
const { credentialType } = testData.input;
mockNodesAndCredentials.getCredential.calledWith(credentialType.name).mockReturnValue({
type: credentialType,
sourcePath: '',
});
const result = await credentialsHelper.authenticate(
testData.input.credentials,
credentialType.name,
deepCopy(incomingRequestOptions),
workflow,
node,
);
expect(result).toEqual(testData.output);
});
}
});
});