import { strict as assert } from 'assert'; import { once } from 'events'; import { itWithClient, TEST_REDIS_SERVERS, TestRedisServers } from './test-utils'; import RedisClient from './client'; import { AbortError } from './errors'; import { defineScript } from './lua-script'; import { spy } from 'sinon'; describe('Client', () => { describe('authentication', () => { itWithClient(TestRedisServers.PASSWORD, 'Client should be authenticated', async client => { assert.equal( await client.ping(), 'PONG' ); }); it('should not retry connecting if failed due to wrong auth', async () => { const client = RedisClient.create({ socket: { ...TEST_REDIS_SERVERS[TestRedisServers.PASSWORD], password: 'wrongpassword' } }); await assert.rejects( client.connect(), { message: 'WRONGPASS invalid username-password pair or user is disabled.' } ); assert.equal(client.isOpen, false); }); }); describe('legacyMode', () => { const client = RedisClient.create({ socket: TEST_REDIS_SERVERS[TestRedisServers.OPEN], legacyMode: true }); before(() => client.connect()); afterEach(() => client.modern.flushAll()); after(() => client.disconnect()); it('client.sendCommand should call the callback', done => { (client as any).sendCommand('PING', (err?: Error, reply?: string) => { if (err) { return done(err); } try { assert.equal(reply, 'PONG'); done(); } catch (err) { done(err); } }); }); it('client.sendCommand should work without callback', async () => { (client as any).sendCommand('PING'); await client.modern.ping(); // make sure the first command was replied }); it('client.modern.sendCommand should return a promise', async () => { assert.equal( await client.modern.sendCommand(['PING']), 'PONG' ); }); it('client.{command} should accept vardict arguments', done => { (client as any).set('a', 'b', (err?: Error, reply?: string) => { if (err) { return done(err); } try { assert.equal(reply, 'OK'); done(); } catch (err) { done(err); } }); }); it('client.{command} should accept arguments array', done => { (client as any).set(['a', 'b'], (err?: Error, reply?: string) => { if (err) { return done(err); } try { assert.equal(reply, 'OK'); done(); } catch (err) { done(err); } }); }); it('client.{command} should accept mix of strings and array of strings', done => { (client as any).set(['a'], 'b', ['GET'], (err?: Error, reply?: string) => { if (err) { return done(err); } try { assert.equal(reply, null); done(); } catch (err) { done(err); } }); }); it('client.multi.exec should call the callback', done => { (client as any).multi() .ping() .exec((err?: Error, reply?: string) => { if (err) { return done(err); } try { assert.deepEqual(reply, ['PONG']); done(); } catch (err) { done(err); } }); }); it('client.multi.exec should work without callback', async () => { (client as any).multi() .ping() .exec(); await client.modern.ping(); // make sure the first command was replied }); it('client.modern.exec should return a promise', async () => { assert.deepEqual( await ((client as any).multi().modern .ping() .exec()), ['PONG'] ); }); }); describe('events', () => { it('connect, ready, end', async () => { const client = RedisClient.create({ socket: TEST_REDIS_SERVERS[TestRedisServers.OPEN] }); await Promise.all([ client.connect(), once(client, 'connect'), once(client, 'ready') ]); await Promise.all([ client.disconnect(), once(client, 'end') ]); }); }); describe('sendCommand', () => { itWithClient(TestRedisServers.OPEN, 'PING', async client => { assert.equal(await client.sendCommand(['PING']), 'PONG'); }); describe('AbortController', () => { itWithClient(TestRedisServers.OPEN, 'success', async client => { await client.sendCommand(['PING'], { signal: new AbortController().signal }); }); itWithClient(TestRedisServers.OPEN, 'AbortError', async client => { const controller = new AbortController(); controller.abort(); await assert.rejects( client.sendCommand(['PING'], { signal: controller.signal }), AbortError ); }); }); }); describe('multi', () => { itWithClient(TestRedisServers.OPEN, 'simple', async client => { assert.deepEqual( await client.multi() .ping() .set('key', 'value') .get('key') .exec(), ['PONG', 'OK', 'value'] ); }); itWithClient(TestRedisServers.OPEN, 'should reject the whole chain on error', async client => { client.on('error', () => { // ignore errors }); await assert.rejects( client.multi() .ping() .addCommand(['DEBUG', 'RESTART']) .ping() .exec() ); }); it('with script', async () => { const client = RedisClient.create({ scripts: { add: defineScript({ NUMBER_OF_KEYS: 0, SCRIPT: 'return ARGV[1] + 1;', transformArguments(number: number): Array { return [number.toString()]; }, transformReply(reply: number): number { return reply; } }) } }); await client.connect(); try { assert.deepEqual( await client.multi() .add(1) .exec(), [2] ); } finally { await client.disconnect(); } }); }); it('scripts', async () => { const client = RedisClient.create({ scripts: { add: defineScript({ NUMBER_OF_KEYS: 0, SCRIPT: 'return ARGV[1] + 1;', transformArguments(number: number): Array { return [number.toString()]; }, transformReply(reply: number): number { return reply; } }) } }); await client.connect(); try { assert.equal( await client.add(1), 2 ); } finally { await client.disconnect(); } }); itWithClient(TestRedisServers.OPEN, 'should reconnect after DEBUG RESTART', async client => { client.on('error', () => { // ignore errors }); await client.sendCommand(['CLIENT', 'SETNAME', 'client']); await assert.rejects(client.sendCommand(['DEBUG', 'RESTART'])); assert.ok(await client.sendCommand(['CLIENT', 'GETNAME']) === null); }); itWithClient(TestRedisServers.OPEN, 'should SELECT db after reconnection', async client => { client.on('error', () => { // ignore errors }); await client.select(1); await assert.rejects(client.sendCommand(['DEBUG', 'RESTART'])); assert.equal( (await client.clientInfo()).db, 1 ); }); itWithClient(TestRedisServers.OPEN, 'scanIterator', async client => { const promises = [], keys = []; for (let i = 0; i < 100; i++) { const key = i.toString(); keys.push(key); promises.push(client.set(key, '')); } await Promise.all(promises); const set = new Set(); for await (const key of client.scanIterator()) { set.add(key); } assert.deepEqual( [...set].sort(), keys.sort() ); }); itWithClient(TestRedisServers.OPEN, 'sScanIterator', async client => { const keys = []; for (let i = 0; i < 100; i++) { keys.push(i.toString()); } await client.sAdd('key', keys); const set = new Set(); for await (const key of client.sScanIterator('key')) { set.add(key); } assert.deepEqual( [...set].sort(), keys.sort() ); }); itWithClient(TestRedisServers.OPEN, 'PubSub', async publisher => { const subscriber = publisher.duplicate(); await subscriber.connect(); try { const channelListener1 = spy(), channelListener2 = spy(), patternListener = spy(); await Promise.all([ subscriber.subscribe('channel', channelListener1), subscriber.subscribe('channel', channelListener2), subscriber.pSubscribe('channel*', patternListener) ]); await publisher.publish('channel', 'message'); assert.ok(channelListener1.calledOnceWithExactly('message', 'channel')); assert.ok(channelListener2.calledOnceWithExactly('message', 'channel')); assert.ok(patternListener.calledOnceWithExactly('message', 'channel')); await subscriber.unsubscribe('channel', channelListener1); await publisher.publish('channel', 'message'); assert.ok(channelListener1.calledOnce); assert.ok(channelListener2.calledTwice); assert.ok(channelListener2.secondCall.calledWithExactly('message', 'channel')); assert.ok(patternListener.calledTwice); assert.ok(patternListener.secondCall.calledWithExactly('message', 'channel')); await subscriber.unsubscribe('channel'); await publisher.publish('channel', 'message'); assert.ok(channelListener1.calledOnce); assert.ok(channelListener2.calledTwice); assert.ok(patternListener.calledThrice); assert.ok(patternListener.thirdCall.calledWithExactly('message', 'channel')); await subscriber.pUnsubscribe(); await publisher.publish('channel', 'message'); assert.ok(channelListener1.calledOnce); assert.ok(channelListener2.calledTwice); assert.ok(patternListener.calledThrice); } finally { await subscriber.disconnect(); } }); });