You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-01 16:46:54 +03:00
Standard is not as up to date and still uses a old eslint version. Instead, use the airbnb default with a couple of modifications. All required changes are included.
670 lines
26 KiB
JavaScript
670 lines
26 KiB
JavaScript
'use strict'
|
|
|
|
const { Buffer } = require('buffer')
|
|
const assert = require('assert')
|
|
const fs = require('fs')
|
|
const path = require('path')
|
|
const config = require('./lib/config')
|
|
const helper = require('./helper')
|
|
const { fork } = require('child_process')
|
|
const Errors = require('redis-errors')
|
|
|
|
const { redis } = config
|
|
let client
|
|
|
|
describe('The nodeRedis client', () => {
|
|
it('individual commands sanity check', (done) => {
|
|
// All commands should work the same in multi context or without
|
|
// Therefor individual commands always have to be handled in both cases
|
|
fs.readFile(path.resolve(__dirname, '../lib/individualCommands.js'), 'utf8', (err, data) => {
|
|
assert.strictEqual(err, null)
|
|
const clientPrototype = data.match(/(\n| = )RedisClient\.prototype.[a-z][a-zA-Z_]+/g)
|
|
const multiPrototype = data.match(/(\n| = )Multi\.prototype\.[a-z][a-zA-Z_]+/g)
|
|
// Check that every entry RedisClient entry has a correspondent Multi entry
|
|
assert.strictEqual(clientPrototype.filter((entry) => {
|
|
return multiPrototype.indexOf(entry.replace('RedisClient', 'Multi')) === -1
|
|
}).length, 0)
|
|
assert.strictEqual(clientPrototype.length, multiPrototype.length)
|
|
// Check that all entries exist only in lowercase variants
|
|
assert.strictEqual(data.match(/(\n| = )RedisClient\.prototype.[a-z][a-zA-Z_]+/g).length, clientPrototype.length)
|
|
done()
|
|
})
|
|
})
|
|
|
|
it('convert minus to underscore in Redis function names', () => {
|
|
const names = Object.keys(redis.RedisClient.prototype)
|
|
client = redis.createClient()
|
|
for (let i = 0; i < names.length; i++) {
|
|
assert(/^([a-zA-Z_][a-zA-Z_0-9]*)?$/.test(client[names[i]].name))
|
|
}
|
|
return client.quit()
|
|
})
|
|
|
|
it('reset the parser while reconnecting (See #1190)', (done) => {
|
|
const client = redis.createClient({
|
|
retryStrategy() {
|
|
return 5
|
|
}
|
|
})
|
|
client.once('reconnecting', () => {
|
|
process.nextTick(() => {
|
|
assert.strictEqual(client._replyParser.buffer, null)
|
|
done()
|
|
})
|
|
})
|
|
const partialInput = Buffer.from('$100\r\nabcdef')
|
|
client._replyParser.execute(partialInput)
|
|
assert.strictEqual(client._replyParser.buffer.inspect(), partialInput.inspect())
|
|
client._stream.destroy()
|
|
})
|
|
|
|
describe('arguments validation', () => {
|
|
it('no boolean for enableOfflineQueue', () => {
|
|
try {
|
|
redis.createClient({ enableOfflineQueue: 'test' })
|
|
throw new Error('failed')
|
|
} catch (err) {
|
|
assert.strictEqual(err.name, 'TypeError')
|
|
}
|
|
})
|
|
|
|
it('no boolean for enableOfflineQueue', () => {
|
|
try {
|
|
redis.createClient({ password: 'test', authPass: 'foobar' })
|
|
throw new Error('failed')
|
|
} catch (err) {
|
|
assert.strictEqual(err.name, 'TypeError')
|
|
}
|
|
})
|
|
})
|
|
|
|
helper.allTests((ip, args) => {
|
|
describe(`using ${ip}`, () => {
|
|
afterEach(() => {
|
|
client.end(true)
|
|
})
|
|
|
|
describe('when connected', () => {
|
|
beforeEach(() => {
|
|
client = redis.createClient.apply(null, args)
|
|
return client.flushdb()
|
|
})
|
|
|
|
describe('argument errors', () => {
|
|
it('should return an error in case of "null" argument', () => {
|
|
return client.set('foo', null).then(helper.fail, (err) => {
|
|
assert.deepStrictEqual(err.issues, [null])
|
|
})
|
|
})
|
|
|
|
it('should return an error in case of unknown types', () => {
|
|
class Foo {}
|
|
const tmp = new Foo()
|
|
return client.set(tmp, undefined).then(helper.fail, (err) => {
|
|
assert.deepStrictEqual(err.issues, [tmp, undefined])
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('duplicate', () => {
|
|
it('check if all options got copied properly', (done) => {
|
|
client.selectedDb = 2
|
|
const client2 = client.duplicate()
|
|
assert.strictEqual(client.connectionId + 1, client2.connectionId)
|
|
assert.strictEqual(client2.selectedDb, 2)
|
|
assert(client.connected)
|
|
assert(!client2.connected)
|
|
const keys = Object.keys(client._options)
|
|
for (let i = 0; i < keys.length; i++) {
|
|
assert.strictEqual(client2._options[keys[i]], client._options[keys[i]])
|
|
}
|
|
client2.on('error', (err) => {
|
|
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted.')
|
|
assert.strictEqual(err.command, 'SELECT')
|
|
assert(err instanceof Errors.AbortError)
|
|
assert(err instanceof Errors.InterruptError)
|
|
assert.strictEqual(err.name, 'InterruptError')
|
|
})
|
|
client2.on('ready', () => {
|
|
client2.end(true)
|
|
done()
|
|
})
|
|
})
|
|
|
|
it('check if all new options replaced the old ones', (done) => {
|
|
const client2 = client.duplicate({
|
|
noReadyCheck: true
|
|
})
|
|
assert(client.connected)
|
|
assert(!client2.connected)
|
|
assert.strictEqual(client._options.noReadyCheck, undefined)
|
|
assert.strictEqual(client2._options.noReadyCheck, true)
|
|
assert.notDeepEqual(client._options, client2._options)
|
|
const keys = Object.keys(client._options)
|
|
for (let i = 0; i < keys.length; i++) {
|
|
if (keys[i] !== 'noReadyCheck') {
|
|
assert.strictEqual(client2._options[keys[i]], client._options[keys[i]])
|
|
}
|
|
}
|
|
client2.on('ready', () => {
|
|
client2.end(true)
|
|
done()
|
|
})
|
|
})
|
|
|
|
it('works with a callback', (done) => {
|
|
client.duplicate((err, client) => {
|
|
assert(!err)
|
|
assert.strictEqual(client.ready, true)
|
|
client.quit().then(() => done())
|
|
})
|
|
})
|
|
|
|
it('works with a callback and errors out', (done) => {
|
|
client.duplicate({
|
|
port: '9999'
|
|
}, (err, client) => {
|
|
assert.strictEqual(err.code, 'ECONNREFUSED')
|
|
done(client)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('sendCommand', () => {
|
|
it('omitting args should be fine', () => {
|
|
client.serverInfo = {}
|
|
client.sendCommand('info')
|
|
return client.sendCommand('ping').then((res) => {
|
|
assert.strictEqual(res, 'PONG')
|
|
// Check if the previous info command used the internal individual info command
|
|
assert.notDeepEqual(client.serverInfo, {})
|
|
client.serverInfo = {}
|
|
client.sendCommand('ping', null).then(helper.isString('PONG'))
|
|
return client.sendCommand('info').then((res) => {
|
|
assert(/redis_version/.test(res))
|
|
// The individual info command should also be called by using sendCommand
|
|
assert.notDeepEqual(client.serverInfo, {})
|
|
})
|
|
})
|
|
})
|
|
|
|
it('using multi with sendCommand should work as individual command instead of using the internal multi', () => {
|
|
// This is necessary to keep backwards compatibility and it is the
|
|
// only way to handle multi as you want in nodeRedis
|
|
client.sendCommand('multi')
|
|
client.sendCommand('set', ['foo', 'bar']).then(helper.isString('QUEUED'))
|
|
client.get('foo')
|
|
// exec is not manipulated if not fired by the individual multi
|
|
// command As the multi command is handled individually by the user
|
|
// he also has to handle the return value
|
|
return client.exec().then(helper.isDeepEqual(['OK', 'bar']))
|
|
})
|
|
|
|
it('multi should be handled special', () => {
|
|
client.sendCommand('multi', undefined).then(helper.isString('OK'))
|
|
const args = ['test', 'bla']
|
|
client.sendCommand('set', args).then(helper.isString('QUEUED'))
|
|
assert.deepStrictEqual(args, ['test', 'bla']) // Check args manipulation
|
|
client.get('test').then(helper.isString('QUEUED'))
|
|
// As the multi command is handled individually by the user he also
|
|
// has to handle the return value
|
|
return client.exec().then(helper.isDeepEqual(['OK', 'bla']))
|
|
})
|
|
|
|
it('command argument has to be of type string', () => {
|
|
try {
|
|
client.sendCommand(true, ['test', 'bla'])
|
|
throw new Error('failed')
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "Boolean" for command name')
|
|
}
|
|
try {
|
|
client.sendCommand(undefined, ['test', 'bla'])
|
|
throw new Error('failed')
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "undefined" for command name')
|
|
}
|
|
try {
|
|
client.sendCommand(null, ['test', 'bla'])
|
|
throw new Error('failed')
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "null" for command name')
|
|
}
|
|
})
|
|
|
|
it('args may only be of type Array or undefined', () => {
|
|
try {
|
|
client.sendCommand('info', 123)
|
|
throw new Error('failed')
|
|
} catch (err) {
|
|
assert.strictEqual(err.message, 'Wrong input type "Number" for args')
|
|
}
|
|
})
|
|
|
|
it('multi should be handled special', () => {
|
|
client.sendCommand('multi', undefined).then(helper.isString('OK'))
|
|
const args = ['test', 'bla']
|
|
client.sendCommand('set', args).then(helper.isString('QUEUED'))
|
|
assert.deepStrictEqual(args, ['test', 'bla']) // Check args manipulation
|
|
client.get('test').then(helper.isString('QUEUED'))
|
|
// As the multi command is handled individually by the user he also
|
|
// has to handle the return value
|
|
return client.exec().then(helper.isDeepEqual(['OK', 'bla']))
|
|
})
|
|
|
|
it('the args array may contain a arbitrary number of arguments', () => {
|
|
client.sendCommand('mset', ['foo', 1, 'bar', 2, 'baz', 3]).then(helper.isString('OK'))
|
|
// As the multi command is handled individually by the user he also
|
|
// has to handle the return value
|
|
return client.mget(['foo', 'bar', 'baz']).then(helper.isDeepEqual(['1', '2', '3']))
|
|
})
|
|
|
|
it('sendCommand with callback as args', () => {
|
|
return client.sendCommand('abcdef').then(assert, helper.isError(/ERR unknown command 'abcdef'/))
|
|
})
|
|
})
|
|
|
|
describe('retryUnfulfilledCommands', () => {
|
|
it('should retry all commands instead of returning an error if a command did not yet return after a connection loss', () => {
|
|
const bclient = redis.createClient({
|
|
retryUnfulfilledCommands: true
|
|
})
|
|
const promise = bclient.blpop('blocking list 2', 5).then((value) => {
|
|
assert.strictEqual(value[0], 'blocking list 2')
|
|
assert.strictEqual(value[1], 'initial value')
|
|
bclient.end(true)
|
|
})
|
|
bclient.once('ready', () => {
|
|
setTimeout(() => {
|
|
bclient._stream.destroy()
|
|
client.rpush('blocking list 2', 'initial value').then(helper.isNumber(1))
|
|
}, 100)
|
|
})
|
|
return promise
|
|
})
|
|
|
|
it('should retry all commands even if the offline queue is disabled', (done) => {
|
|
const bclient = redis.createClient({
|
|
enableOfflineQueue: false,
|
|
retryUnfulfilledCommands: true
|
|
})
|
|
bclient.once('ready', () => {
|
|
bclient.blpop('blocking list 2', 5).then((value) => {
|
|
assert.strictEqual(value[0], 'blocking list 2')
|
|
assert.strictEqual(value[1], 'initial value')
|
|
bclient.end(true)
|
|
done()
|
|
})
|
|
setTimeout(() => {
|
|
bclient._stream.destroy()
|
|
client.rpush('blocking list 2', 'initial value').then(helper.isNumber(1))
|
|
}, 100)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('.end', () => {
|
|
it('used without flush / flush set to false', () => {
|
|
try {
|
|
client.end()
|
|
throw new Error('failed')
|
|
} catch (e) {
|
|
assert(e instanceof TypeError)
|
|
}
|
|
})
|
|
|
|
it('used with flush set to true', (done) => {
|
|
const end = helper.callFuncAfter(done, 20)
|
|
const cb = function (err) {
|
|
assert(/Connection forcefully ended|The connection is already closed./.test(err.message))
|
|
end()
|
|
}
|
|
for (let i = 0; i < 20; i++) {
|
|
if (i === 10) {
|
|
client.end(true)
|
|
client._stream.write('foo') // Trigger an error on the closed stream that we ignore
|
|
}
|
|
client.set('foo', 'bar').then(assert, cb)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('commands after using .quit should fail', () => {
|
|
it('return an error in the callback version two', function () {
|
|
if (helper.redisProcess().spawnFailed()) this.skip()
|
|
|
|
client.quit()
|
|
return client.get('foo').then(assert, (err) => {
|
|
assert.strictEqual(err.message, 'GET can\'t be processed. The connection is already closed.')
|
|
assert.strictEqual(err.command, 'GET')
|
|
assert.strictEqual(client.offlineQueue.length, 0)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('when redis closes unexpectedly', () => {
|
|
it('reconnects and can retrieve the pre-existing data', (done) => {
|
|
client.on('reconnecting', function onRecon(params) {
|
|
client.on('connect', function onConnect() {
|
|
const end = helper.callFuncAfter(() => {
|
|
client.removeListener('connect', onConnect)
|
|
client.removeListener('reconnecting', onRecon)
|
|
assert.strictEqual(client.serverInfo.keyspace.db0.keys, 2)
|
|
assert.strictEqual(Object.keys(client.serverInfo.keyspace.db0).length, 3)
|
|
done()
|
|
}, 4)
|
|
client.get('recon 1').then(helper.isString('one')).then(end)
|
|
client.get('recon 1').then(helper.isString('one')).then(end)
|
|
client.get('recon 2').then(helper.isString('two')).then(end)
|
|
client.get('recon 2').then(helper.isString('two')).then(end)
|
|
})
|
|
})
|
|
|
|
client.set('recon 1', 'one')
|
|
client.set('recon 2', 'two').then((res) => {
|
|
// Do not do this in normal programs. This is to simulate the server closing on us.
|
|
// For orderly shutdown in normal programs, do client.quit()
|
|
client._stream.destroy()
|
|
})
|
|
})
|
|
|
|
it('reconnects properly when monitoring', (done) => {
|
|
client.on('reconnecting', function onRecon(params) {
|
|
client.on('ready', function onReady() {
|
|
assert.strictEqual(client._monitoring, true, 'monitoring after reconnect')
|
|
client.removeListener('ready', onReady)
|
|
client.removeListener('reconnecting', onRecon)
|
|
done()
|
|
})
|
|
})
|
|
|
|
assert.strictEqual(client._monitoring, false, 'monitoring off at start')
|
|
client.set('recon 1', 'one')
|
|
client.monitor().then((res) => {
|
|
assert.strictEqual(client._monitoring, true, 'monitoring on after monitor()')
|
|
client.set('recon 2', 'two').then((res) => {
|
|
// Do not do this in normal programs. This is to simulate the server closing on us.
|
|
// For orderly shutdown in normal programs, do client.quit()
|
|
client._stream.destroy()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('and it\'s subscribed to a channel', () => {
|
|
// "Connection in subscriber mode, only subscriber commands may be used"
|
|
it('reconnects, unsubscribes, and can retrieve the pre-existing data', (done) => {
|
|
client.on('ready', () => {
|
|
client.unsubscribe()
|
|
|
|
client.on('unsubscribe', (channel, count) => {
|
|
// we should now be out of subscriber mode.
|
|
assert.strictEqual(channel, 'recon channel')
|
|
assert.strictEqual(count, 0)
|
|
client.set('foo', 'bar').then(helper.isString('OK')).then(done)
|
|
})
|
|
})
|
|
|
|
client.set('recon 1', 'one')
|
|
client.subscribe('recon channel').then((res) => {
|
|
// Do not do this in normal programs. This is to simulate the server closing on us.
|
|
// For orderly shutdown in normal programs, do client.quit()
|
|
client._stream.destroy()
|
|
})
|
|
})
|
|
|
|
it('reconnects, unsubscribes, and can retrieve the pre-existing data of a explicit channel', (done) => {
|
|
client.on('ready', () => {
|
|
client.unsubscribe('recon channel').then(helper.isDeepEqual([0, ['recon channel']]))
|
|
|
|
client.on('unsubscribe', (channel, count) => {
|
|
// we should now be out of subscriber mode.
|
|
assert.strictEqual(channel, 'recon channel')
|
|
assert.strictEqual(count, 0)
|
|
client.set('foo', 'bar').then(helper.isString('OK')).then(done)
|
|
})
|
|
})
|
|
|
|
client.set('recon 1', 'one')
|
|
client.subscribe('recon channel').then((res) => {
|
|
// Do not do this in normal programs. This is to simulate the server closing on us.
|
|
// For orderly shutdown in normal programs, do client.quit()
|
|
client._stream.destroy()
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('utf8', () => {
|
|
it('handles utf-8 keys', () => {
|
|
const utf8Sample = 'ಠ_ಠ'
|
|
client.set(['utf8test', utf8Sample]).then(helper.isString('OK'))
|
|
return client.get(['utf8test']).then(helper.isString(utf8Sample))
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('unref', () => {
|
|
it('exits subprocess as soon as final command is processed', function (done) {
|
|
this.timeout(12000)
|
|
const args = config.HOST[ip] ? [config.HOST[ip], config.PORT] : [ip]
|
|
const external = fork('./test/lib/unref.js', args)
|
|
|
|
const id = setTimeout(() => {
|
|
external.kill()
|
|
done(new Error('unref subprocess timed out'))
|
|
}, 8000)
|
|
|
|
external.on('close', (code) => {
|
|
clearTimeout(id)
|
|
assert.strictEqual(code, 0)
|
|
done()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('execution order / fire query while loading', () => {
|
|
it('keep execution order for commands that may fire while redis is still loading', (done) => {
|
|
client = redis.createClient.apply(null, args)
|
|
let fired = false
|
|
client.set('foo', 'bar').then((res) => {
|
|
assert.strictEqual(fired, false)
|
|
done()
|
|
})
|
|
client.info().then(() => {
|
|
fired = true
|
|
})
|
|
})
|
|
|
|
// TODO: consider allowing loading commands in v.3
|
|
// it('should fire early', function (done) {
|
|
// client = redis.createClient.apply(null, args);
|
|
// var fired = false;
|
|
// client.info(function (err, res) {
|
|
// fired = true;
|
|
// });
|
|
// client.set('foo', 'bar', function (err, res) {
|
|
// assert(fired);
|
|
// done();
|
|
// });
|
|
// assert.strictEqual(client.offlineQueue.length, 1);
|
|
// assert.strictEqual(client.commandQueue.length, 1);
|
|
// client.on('connect', function () {
|
|
// assert.strictEqual(client.offlineQueue.length, 1);
|
|
// assert.strictEqual(client.commandQueue.length, 1);
|
|
// });
|
|
// client.on('ready', function () {
|
|
// assert.strictEqual(client.offlineQueue.length, 0);
|
|
// });
|
|
// });
|
|
})
|
|
|
|
describe('protocol error', () => {
|
|
it('should gracefully recover and only fail on the already send commands', (done) => {
|
|
client = redis.createClient.apply(null, args)
|
|
let error
|
|
client.on('error', (err) => {
|
|
assert.strictEqual(err.message, 'Protocol error, got "a" as reply type byte. Please report this.')
|
|
assert.strictEqual(err, error)
|
|
assert(err instanceof redis.ParserError)
|
|
// After the hard failure work properly again. The set should have
|
|
// been processed properly too
|
|
client.get('foo').then(helper.isString('bar')).then(done)
|
|
})
|
|
client.once('ready', () => {
|
|
client.set('foo', 'bar').then(helper.fail, (err) => {
|
|
assert.strictEqual(err.message, 'Fatal error encountered. Command aborted.')
|
|
assert.strictEqual(err.code, 'NR_FATAL')
|
|
assert(err instanceof redis.InterruptError)
|
|
error = err.origin
|
|
})
|
|
// Make sure we call execute out of the reply. Ready is called in a
|
|
// reply. Fail the set answer. Has no corresponding command obj and
|
|
// will therefore land in the error handler and set
|
|
process.nextTick(() => client._replyParser.execute(Buffer.from('a*1\r*1\r$1`zasd\r\na')))
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('enableOfflineQueue', () => {
|
|
describe('true', () => {
|
|
it('does not return an error and enqueues operation', (done) => {
|
|
client = redis.createClient(9999)
|
|
let finished = false
|
|
client.on('error', (e) => {
|
|
// ignore, b/c expecting a "can't connect" error
|
|
})
|
|
|
|
setTimeout(() => {
|
|
client.set('foo', 'bar').then(helper.fail, (err) => {
|
|
if (!finished) done(err)
|
|
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted.')
|
|
})
|
|
|
|
setTimeout(() => {
|
|
assert.strictEqual(client.offlineQueue.length, 1)
|
|
finished = true
|
|
done()
|
|
}, 25)
|
|
}, 50)
|
|
})
|
|
|
|
it('enqueues operation and keep the queue while trying to reconnect', (done) => {
|
|
client = redis.createClient(9999, null, {
|
|
retryStrategy(options) {
|
|
if (options.attempt < 4) {
|
|
return 50
|
|
}
|
|
}
|
|
})
|
|
let i = 0
|
|
|
|
client.on('error', (err) => {
|
|
if (err.code === 'NR_CLOSED') {
|
|
assert(i, 3)
|
|
assert.strictEqual(client.offlineQueue.length, 0)
|
|
assert.strictEqual(err.origin.code, 'ECONNREFUSED')
|
|
if (!(err instanceof redis.AbortError)) {
|
|
done()
|
|
} else {
|
|
assert.strictEqual(err.command, 'SET')
|
|
}
|
|
} else {
|
|
assert.strictEqual(err.code, 'ECONNREFUSED')
|
|
assert.strictEqual(err.errno, 'ECONNREFUSED')
|
|
assert.strictEqual(err.syscall, 'connect')
|
|
}
|
|
})
|
|
|
|
client.on('reconnecting', (params) => {
|
|
i++
|
|
assert.strictEqual(params.attempt, i)
|
|
assert.strictEqual(params.timesConnected, 0)
|
|
assert(params.error instanceof Error)
|
|
assert(typeof params.totalRetryTime === 'number')
|
|
assert.strictEqual(client.offlineQueue.length, 1)
|
|
})
|
|
|
|
client.set('foo', 'bar').then(assert, (err) => {
|
|
assert(i, 3)
|
|
assert(err)
|
|
assert.strictEqual(client.offlineQueue.length, 0)
|
|
})
|
|
})
|
|
|
|
it('flushes the command queue if connection is lost', (done) => {
|
|
const end = helper.callFuncAfter(done, 2)
|
|
client = redis.createClient()
|
|
|
|
client.once('ready', () => {
|
|
const multi = client.multi()
|
|
multi.config('bar')
|
|
for (let i = 0; i < 12; i += 3) {
|
|
client.set(`foo${i}`, `bar${i}`).then(helper.fail, helper.isError)
|
|
multi.set(`foo${i + 1}`, `bar${i + 1}`)
|
|
multi.set(`foo${i + 2}`, `bar${i + 2}`)
|
|
}
|
|
multi.exec().then(helper.fail, (err) => {
|
|
assert.strictEqual(client.commandQueue.length, 0)
|
|
assert.strictEqual(err.errors.length, 9)
|
|
assert.strictEqual(err.errors[1].command, 'SET')
|
|
assert.deepStrictEqual(err.errors[1].args, ['foo1', 'bar1'])
|
|
end()
|
|
})
|
|
assert.strictEqual(client.commandQueue.length, 15)
|
|
helper.killConnection(client)
|
|
})
|
|
client.on('error', (err) => {
|
|
assert.strictEqual(err.code, 'ECONNREFUSED')
|
|
assert.strictEqual(err.errno, 'ECONNREFUSED')
|
|
assert.strictEqual(err.syscall, 'connect')
|
|
end()
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('false', () => {
|
|
it('stream not writable', (done) => {
|
|
client = redis.createClient({
|
|
enableOfflineQueue: false
|
|
})
|
|
client.on('ready', () => {
|
|
client._stream.destroy()
|
|
client.set('foo', 'bar').then(assert, (err) => {
|
|
assert.strictEqual(err.message, 'SET can\'t be processed. Stream not writeable.')
|
|
done()
|
|
})
|
|
})
|
|
})
|
|
|
|
it('emit an error and does not enqueues operation', (done) => {
|
|
client = redis.createClient(9999, null, {
|
|
enableOfflineQueue: false
|
|
})
|
|
const end = helper.callFuncAfter(done, 3)
|
|
|
|
client.on('error', (err) => {
|
|
assert(/ECONNREFUSED/.test(err.message))
|
|
assert.strictEqual(client.commandQueue.length, 0)
|
|
end()
|
|
})
|
|
|
|
client.set('foo', 'bar').then(helper.fail, (err) => {
|
|
assert(/offline queue is deactivated/.test(err.message))
|
|
assert.strictEqual(client.commandQueue.length, 0)
|
|
end()
|
|
})
|
|
|
|
assert.doesNotThrow(() => {
|
|
client.set('foo', 'bar').then(assert, (err) => {
|
|
// should callback with an error
|
|
assert.ok(err)
|
|
setTimeout(end, 50)
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|
|
})
|