mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
feat(Redis Node): Add support for TLS (#9266)
This commit is contained in:
parent
30c8efc4cc
commit
0a2de093c0
|
@ -35,5 +35,11 @@ export class Redis implements ICredentialType {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
displayName: 'SSL',
|
||||||
|
name: 'ssl',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,79 @@
|
||||||
import { mock } from 'jest-mock-extended';
|
import { mock } from 'jest-mock-extended';
|
||||||
import type { RedisClientType } from '@redis/client';
|
import type { RedisClientType } from '@redis/client';
|
||||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||||
import { Redis } from '../Redis.node';
|
|
||||||
|
|
||||||
const mockClient = mock<RedisClientType>();
|
const mockClient = mock<RedisClientType>();
|
||||||
jest.mock('redis', () => ({
|
const createClient = jest.fn().mockReturnValue(mockClient);
|
||||||
createClient: () => mockClient,
|
jest.mock('redis', () => ({ createClient }));
|
||||||
}));
|
|
||||||
|
import { Redis } from '../Redis.node';
|
||||||
|
import { setupRedisClient } from '../utils';
|
||||||
|
|
||||||
describe('Redis Node', () => {
|
describe('Redis Node', () => {
|
||||||
const mockCredential = {
|
|
||||||
host: 'redis',
|
|
||||||
port: 1234,
|
|
||||||
database: 0,
|
|
||||||
password: 'random',
|
|
||||||
};
|
|
||||||
|
|
||||||
const node = new Redis();
|
const node = new Redis();
|
||||||
const thisArg = mock<IExecuteFunctions>({});
|
|
||||||
thisArg.getCredentials.calledWith('redis').mockResolvedValue(mockCredential);
|
|
||||||
|
|
||||||
beforeEach(() => jest.clearAllMocks());
|
beforeEach(() => jest.clearAllMocks());
|
||||||
|
|
||||||
afterEach(() => {
|
describe('setupRedisClient', () => {
|
||||||
expect(mockClient.connect).toHaveBeenCalled();
|
it('should not configure TLS by default', () => {
|
||||||
expect(mockClient.ping).toHaveBeenCalled();
|
setupRedisClient({
|
||||||
expect(mockClient.quit).toHaveBeenCalled();
|
host: 'redis.domain',
|
||||||
|
port: 1234,
|
||||||
|
database: 0,
|
||||||
|
});
|
||||||
|
expect(createClient).toHaveBeenCalledWith({
|
||||||
|
database: 0,
|
||||||
|
password: undefined,
|
||||||
|
socket: {
|
||||||
|
host: 'redis.domain',
|
||||||
|
port: 1234,
|
||||||
|
tls: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should configure TLS', () => {
|
||||||
|
setupRedisClient({
|
||||||
|
host: 'redis.domain',
|
||||||
|
port: 1234,
|
||||||
|
database: 0,
|
||||||
|
ssl: true,
|
||||||
|
});
|
||||||
|
expect(createClient).toHaveBeenCalledWith({
|
||||||
|
database: 0,
|
||||||
|
password: undefined,
|
||||||
|
socket: {
|
||||||
|
host: 'redis.domain',
|
||||||
|
port: 1234,
|
||||||
|
tls: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('info operation', async () => {
|
describe('operations', () => {
|
||||||
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('info');
|
const thisArg = mock<IExecuteFunctions>({});
|
||||||
mockClient.info.mockResolvedValue(`
|
|
||||||
|
const mockCredential = {
|
||||||
|
host: 'redis',
|
||||||
|
port: 1234,
|
||||||
|
database: 0,
|
||||||
|
password: 'random',
|
||||||
|
};
|
||||||
|
|
||||||
|
thisArg.getCredentials.calledWith('redis').mockResolvedValue(mockCredential);
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
expect(createClient).toHaveBeenCalled();
|
||||||
|
expect(mockClient.connect).toHaveBeenCalled();
|
||||||
|
expect(mockClient.ping).toHaveBeenCalled();
|
||||||
|
expect(mockClient.quit).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('info operation', () => {
|
||||||
|
it('should return info', async () => {
|
||||||
|
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('info');
|
||||||
|
mockClient.info.mockResolvedValue(`
|
||||||
# Server
|
# Server
|
||||||
redis_version:6.2.14
|
redis_version:6.2.14
|
||||||
redis_git_sha1:00000000
|
redis_git_sha1:00000000
|
||||||
|
@ -55,104 +98,108 @@ connected_slaves:0
|
||||||
master_failover_state:no-failover
|
master_failover_state:no-failover
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const output = await node.execute.call(thisArg);
|
const output = await node.execute.call(thisArg);
|
||||||
|
|
||||||
expect(mockClient.info).toHaveBeenCalled();
|
expect(mockClient.info).toHaveBeenCalled();
|
||||||
expect(output[0][0].json).toEqual({
|
expect(output[0][0].json).toEqual({
|
||||||
redis_version: 6.2,
|
redis_version: 6.2,
|
||||||
redis_git_sha1: 0,
|
redis_git_sha1: 0,
|
||||||
redis_git_dirty: 0,
|
redis_git_dirty: 0,
|
||||||
redis_mode: 'standalone',
|
redis_mode: 'standalone',
|
||||||
arch_bits: 64,
|
arch_bits: 64,
|
||||||
tcp_port: 6379,
|
tcp_port: 6379,
|
||||||
uptime_in_seconds: 429905,
|
uptime_in_seconds: 429905,
|
||||||
uptime_in_days: 4,
|
uptime_in_days: 4,
|
||||||
connected_clients: 1,
|
connected_clients: 1,
|
||||||
cluster_connections: 0,
|
cluster_connections: 0,
|
||||||
max_clients: 10000,
|
max_clients: 10000,
|
||||||
used_memory: 876648,
|
used_memory: 876648,
|
||||||
role: 'master',
|
role: 'master',
|
||||||
connected_slaves: 0,
|
connected_slaves: 0,
|
||||||
master_failover_state: 'no-failover',
|
master_failover_state: 'no-failover',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('delete operation', async () => {
|
|
||||||
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
|
|
||||||
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('delete');
|
|
||||||
thisArg.getNodeParameter.calledWith('key', 0).mockReturnValue('key1');
|
|
||||||
mockClient.del.calledWith('key1').mockResolvedValue(1);
|
|
||||||
|
|
||||||
const output = await node.execute.call(thisArg);
|
|
||||||
expect(mockClient.del).toHaveBeenCalledWith('key1');
|
|
||||||
expect(output[0][0].json).toEqual({ x: 1 });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('get operation', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
|
|
||||||
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('get');
|
|
||||||
thisArg.getNodeParameter.calledWith('options', 0).mockReturnValue({ dotNotation: true });
|
|
||||||
thisArg.getNodeParameter.calledWith('key', 0).mockReturnValue('key1');
|
|
||||||
thisArg.getNodeParameter.calledWith('propertyName', 0).mockReturnValue('x.y');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keyType = automatic', async () => {
|
describe('delete operation', () => {
|
||||||
thisArg.getNodeParameter.calledWith('keyType', 0).mockReturnValue('automatic');
|
it('should delete', async () => {
|
||||||
mockClient.type.calledWith('key1').mockResolvedValue('string');
|
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
|
||||||
mockClient.get.calledWith('key1').mockResolvedValue('value');
|
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('delete');
|
||||||
|
thisArg.getNodeParameter.calledWith('key', 0).mockReturnValue('key1');
|
||||||
|
mockClient.del.calledWith('key1').mockResolvedValue(1);
|
||||||
|
|
||||||
const output = await node.execute.call(thisArg);
|
const output = await node.execute.call(thisArg);
|
||||||
expect(mockClient.type).toHaveBeenCalledWith('key1');
|
expect(mockClient.del).toHaveBeenCalledWith('key1');
|
||||||
expect(mockClient.get).toHaveBeenCalledWith('key1');
|
expect(output[0][0].json).toEqual({ x: 1 });
|
||||||
expect(output[0][0].json).toEqual({ x: { y: 'value' } });
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('keyType = hash', async () => {
|
describe('get operation', () => {
|
||||||
thisArg.getNodeParameter.calledWith('keyType', 0).mockReturnValue('hash');
|
beforeEach(() => {
|
||||||
mockClient.hGetAll.calledWith('key1').mockResolvedValue({
|
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
|
||||||
field1: '1',
|
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('get');
|
||||||
field2: '2',
|
thisArg.getNodeParameter.calledWith('options', 0).mockReturnValue({ dotNotation: true });
|
||||||
|
thisArg.getNodeParameter.calledWith('key', 0).mockReturnValue('key1');
|
||||||
|
thisArg.getNodeParameter.calledWith('propertyName', 0).mockReturnValue('x.y');
|
||||||
});
|
});
|
||||||
|
|
||||||
const output = await node.execute.call(thisArg);
|
it('keyType = automatic', async () => {
|
||||||
expect(mockClient.hGetAll).toHaveBeenCalledWith('key1');
|
thisArg.getNodeParameter.calledWith('keyType', 0).mockReturnValue('automatic');
|
||||||
expect(output[0][0].json).toEqual({
|
mockClient.type.calledWith('key1').mockResolvedValue('string');
|
||||||
x: {
|
mockClient.get.calledWith('key1').mockResolvedValue('value');
|
||||||
y: {
|
|
||||||
field1: '1',
|
const output = await node.execute.call(thisArg);
|
||||||
field2: '2',
|
expect(mockClient.type).toHaveBeenCalledWith('key1');
|
||||||
|
expect(mockClient.get).toHaveBeenCalledWith('key1');
|
||||||
|
expect(output[0][0].json).toEqual({ x: { y: 'value' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keyType = hash', async () => {
|
||||||
|
thisArg.getNodeParameter.calledWith('keyType', 0).mockReturnValue('hash');
|
||||||
|
mockClient.hGetAll.calledWith('key1').mockResolvedValue({
|
||||||
|
field1: '1',
|
||||||
|
field2: '2',
|
||||||
|
});
|
||||||
|
|
||||||
|
const output = await node.execute.call(thisArg);
|
||||||
|
expect(mockClient.hGetAll).toHaveBeenCalledWith('key1');
|
||||||
|
expect(output[0][0].json).toEqual({
|
||||||
|
x: {
|
||||||
|
y: {
|
||||||
|
field1: '1',
|
||||||
|
field2: '2',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe('keys operation', () => {
|
describe('keys operation', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
|
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
|
||||||
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('keys');
|
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('keys');
|
||||||
thisArg.getNodeParameter.calledWith('keyPattern', 0).mockReturnValue('key*');
|
thisArg.getNodeParameter.calledWith('keyPattern', 0).mockReturnValue('key*');
|
||||||
mockClient.keys.calledWith('key*').mockResolvedValue(['key1', 'key2']);
|
mockClient.keys.calledWith('key*').mockResolvedValue(['key1', 'key2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getValues = false', async () => {
|
it('getValues = false', async () => {
|
||||||
thisArg.getNodeParameter.calledWith('getValues', 0).mockReturnValue(false);
|
thisArg.getNodeParameter.calledWith('getValues', 0).mockReturnValue(false);
|
||||||
|
|
||||||
const output = await node.execute.call(thisArg);
|
const output = await node.execute.call(thisArg);
|
||||||
expect(mockClient.keys).toHaveBeenCalledWith('key*');
|
expect(mockClient.keys).toHaveBeenCalledWith('key*');
|
||||||
expect(output[0][0].json).toEqual({ keys: ['key1', 'key2'] });
|
expect(output[0][0].json).toEqual({ keys: ['key1', 'key2'] });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('getValues = true', async () => {
|
it('getValues = true', async () => {
|
||||||
thisArg.getNodeParameter.calledWith('getValues', 0).mockReturnValue(true);
|
thisArg.getNodeParameter.calledWith('getValues', 0).mockReturnValue(true);
|
||||||
mockClient.type.mockResolvedValue('string');
|
mockClient.type.mockResolvedValue('string');
|
||||||
mockClient.get.calledWith('key1').mockResolvedValue('value1');
|
mockClient.get.calledWith('key1').mockResolvedValue('value1');
|
||||||
mockClient.get.calledWith('key2').mockResolvedValue('value2');
|
mockClient.get.calledWith('key2').mockResolvedValue('value2');
|
||||||
|
|
||||||
const output = await node.execute.call(thisArg);
|
const output = await node.execute.call(thisArg);
|
||||||
expect(mockClient.keys).toHaveBeenCalledWith('key*');
|
expect(mockClient.keys).toHaveBeenCalledWith('key*');
|
||||||
expect(output[0][0].json).toEqual({ key1: 'value1', key2: 'value2' });
|
expect(output[0][0].json).toEqual({ key1: 'value1', key2: 'value2' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,14 +8,15 @@ import type {
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeOperationError } from 'n8n-workflow';
|
import { NodeOperationError } from 'n8n-workflow';
|
||||||
|
|
||||||
import { createClient } from 'redis';
|
import { type RedisClientOptions, createClient } from 'redis';
|
||||||
export type RedisClientType = ReturnType<typeof createClient>;
|
export type RedisClientType = ReturnType<typeof createClient>;
|
||||||
|
|
||||||
export function setupRedisClient(credentials: ICredentialDataDecryptedObject): RedisClientType {
|
export function setupRedisClient(credentials: ICredentialDataDecryptedObject): RedisClientType {
|
||||||
const redisOptions = {
|
const redisOptions: RedisClientOptions = {
|
||||||
socket: {
|
socket: {
|
||||||
host: credentials.host as string,
|
host: credentials.host as string,
|
||||||
port: credentials.port as number,
|
port: credentials.port as number,
|
||||||
|
tls: credentials.ssl === true,
|
||||||
},
|
},
|
||||||
database: credentials.database as number,
|
database: credentials.database as number,
|
||||||
password: (credentials.password as string) || undefined,
|
password: (credentials.password as string) || undefined,
|
||||||
|
|
Loading…
Reference in a new issue