1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

chore: add print helper again and refactor some code

Expose the RedisClient directly instead of only being a property
This commit is contained in:
Ruben Bridgewater
2017-05-29 18:23:24 +02:00
parent 4e593587cb
commit 0276e15f04
14 changed files with 169 additions and 104 deletions

View File

@@ -19,14 +19,18 @@ and maybe more.
Bugfixes Bugfixes
- Fixed auth in batch not saving the password - Fixed auth in batch not saving the password
- Fixed catch tls handshake failures with newer Node.js versions
- Fixed stream option being copied
- Fixed pub sub activated, listening to a `(p)messageBuffer`, no Buffer returns
and monitoring resulting in a crash.
Features Features
- Native promise support - Native promise support
- Auto pipelining - Auto pipelining
- The client is now exported directly and be instantiated directly
Breaking Changes Breaking Changes
- Dropped support for `UPPER_CASE` commands
- Dropped support for `snake_case` - Dropped support for `snake_case`
- Dropped support for domains - Dropped support for domains
- Dropped support for Redis 2.4 - Dropped support for Redis 2.4
@@ -36,9 +40,7 @@ Breaking Changes
- Removed `parser` option - Removed `parser` option
- Removed `retryMaxDelay` (max_delay) option - Removed `retryMaxDelay` (max_delay) option
- Removed `maxAttempts` (max_attempts) option - Removed `maxAttempts` (max_attempts) option
- Removed `socketNoDelay` (socket_no_delay) option - Removed `socketNoDelay` (socket\_no_delay) option
- Removed `authPass` (auth_pass) option. Please use `password` instead
- Removed `Redis.print` helper function
- Removed backpressure indicator from function return value - Removed backpressure indicator from function return value
- Removed the `stream` parameter from the RedisClient constructor. - Removed the `stream` parameter from the RedisClient constructor.
- Please set the stream in the options instead - Please set the stream in the options instead
@@ -60,11 +62,12 @@ Breaking Changes
- `restore-asking` is now `restore_asking_` - `restore-asking` is now `restore_asking_`
- `host:` is now `host` - `host:` is now `host`
- Changed the `serverInfo` into a nested object and to parse numbers - Changed the `serverInfo` into a nested object and to parse numbers
- Changed the `serverInfo.versions` to `serverInfo.version` - Changed the `serverInfo.versions` to `serverInfo.server.version`
- Changed the `message` and `pmessage` listener to always return a string - Changed the `message` and `pmessage` listener to always return a string
- If you want to receive a buffer, please listen to the `messageBuffer` or `pmessageBuffer` - If you want to receive a buffer, please listen to the `messageBuffer` or `pmessageBuffer`
- Using `.end` without the flush parameter is now going to throw an TypeError - Using `.end` without the flush parameter is now going to throw an TypeError
- Only emit ready when all commands were truly send to Redis - Only emit ready when all commands were truly send to Redis
- `UPPER_CASE` commands are not enumerated anymore
## v.2.7.2 - 14 Mar, 2017 ## v.2.7.2 - 14 Mar, 2017

View File

@@ -20,6 +20,7 @@ const offlineCommand = require('./lib/offlineCommand')
const utils = require('./lib/utils') const utils = require('./lib/utils')
const normalizeAndWriteCommand = require('./lib/writeCommands') const normalizeAndWriteCommand = require('./lib/writeCommands')
const noop = function () {} const noop = function () {}
var connectionId = 0
// Attention: The second parameter might be removed at will and is not officially supported. // Attention: The second parameter might be removed at will and is not officially supported.
// Do not rely on this // Do not rely on this
@@ -34,8 +35,8 @@ class RedisClient extends EventEmitter {
super() super()
// Copy the options so they are not mutated // Copy the options so they are not mutated
options = utils.clone(options) options = utils.clone(options)
// TODO: Add a more restrictive options validation
const cnxOptions = {} const cnxOptions = {}
/* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
for (const tlsOption in options.tls) { for (const tlsOption in options.tls) {
if (options.tls.hasOwnProperty(tlsOption)) { if (options.tls.hasOwnProperty(tlsOption)) {
cnxOptions[tlsOption] = options.tls[tlsOption] cnxOptions[tlsOption] = options.tls[tlsOption]
@@ -140,6 +141,8 @@ class RedisClient extends EventEmitter {
// Do not call internalSendCommand directly, if you are not absolutely certain it handles everything properly // Do not call internalSendCommand directly, if you are not absolutely certain it handles everything properly
// e.g. monitor / info does not work with internalSendCommand only // e.g. monitor / info does not work with internalSendCommand only
// TODO: Move this function out of the client as a private function // TODO: Move this function out of the client as a private function
// TODO: Check how others can intercept (monkey patch) essential parts (e.g. opbeat)
// after making this private.
internalSendCommand (commandObj) { internalSendCommand (commandObj) {
if (this.ready === false || this._stream.writable === false) { if (this.ready === false || this._stream.writable === false) {
// Handle offline commands right away // Handle offline commands right away
@@ -154,7 +157,6 @@ class RedisClient extends EventEmitter {
} }
// Handle `CLIENT REPLY ON|OFF|SKIP` // Handle `CLIENT REPLY ON|OFF|SKIP`
// This has to be checked after callOnWrite // This has to be checked after callOnWrite
/* istanbul ignore else: TODO: Remove this as soon as we test Redis 3.2 on travis */
if (this._reply === 'ON') { if (this._reply === 'ON') {
this.commandQueue.push(commandObj) this.commandQueue.push(commandObj)
} else { } else {
@@ -229,6 +231,10 @@ class RedisClient extends EventEmitter {
} }
// TODO: promisify this // TODO: promisify this
// This can not be done without removing support to return the client sync.
// This would be another BC and it should be fine to return the client sync.
// Therefore a option could be to accept a resolved promise instead of a callback
// to return a promise.
duplicate (options, callback) { duplicate (options, callback) {
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options callback = options
@@ -261,33 +267,31 @@ class RedisClient extends EventEmitter {
// Note: this overrides a native function! // Note: this overrides a native function!
multi (args) { multi (args) {
return new Multi(this, 'multi', args) return new Multi(this, args, 'multi')
} }
// Note: This is not a native function but is still handled as a individual command as it behaves just the same as multi
batch (args) { batch (args) {
return new Multi(this, 'batch', args) return new Multi(this, args, 'batch')
} }
} }
RedisClient.connectionId = 0 RedisClient.debugMode = /\bredis\b/i.test(process.env.NODE_DEBUG)
RedisClient.RedisClient = RedisClient
RedisClient.Multi = Multi
RedisClient.AbortError = Errors.AbortError
RedisClient.ParserError = Errors.ParserError
RedisClient.RedisError = Errors.RedisError
RedisClient.ReplyError = Errors.ReplyError
RedisClient.InterruptError = Errors.InterruptError
RedisClient.print = utils.print
RedisClient.createClient = function () {
return new RedisClient(unifyOptions.apply(null, arguments))
}
Commands.list.forEach((name) => addCommand(RedisClient.prototype, Multi.prototype, name)) Commands.list.forEach((name) => addCommand(RedisClient.prototype, Multi.prototype, name))
module.exports = { module.exports = RedisClient
debugMode: /\bredis\b/i.test(process.env.NODE_DEBUG),
RedisClient,
Multi,
AbortError: Errors.AbortError,
ParserError: Errors.ParserError,
RedisError: Errors.RedisError,
ReplyError: Errors.ReplyError,
InterruptError: Errors.InterruptError,
createClient () {
return new RedisClient(unifyOptions.apply(null, arguments))
}
}
// Add all redis commands / nodeRedis api to the client // Add all redis commands / nodeRedis api to the client
// TODO: Change the way this is included... // TODO: Change the way this is included...

View File

@@ -23,6 +23,12 @@ function addCommand (clientProto, multiProto, command) {
}) })
} }
} }
Object.defineProperty(clientProto, commandName.toUpperCase(), {
enumerable: false,
configurable: false,
writable: false,
value: clientProto[commandName]
})
// Do not override existing functions // Do not override existing functions
if (!multiProto[command] && command !== 'multi') { if (!multiProto[command] && command !== 'multi') {
@@ -41,6 +47,12 @@ function addCommand (clientProto, multiProto, command) {
}) })
} }
} }
Object.defineProperty(multiProto, commandName.toUpperCase(), {
enumerable: false,
configurable: false,
writable: false,
value: clientProto[commandName]
})
} }
module.exports = addCommand module.exports = addCommand

View File

@@ -10,9 +10,9 @@ const replyHandler = require('./replyHandler')
const onResult = replyHandler.onResult const onResult = replyHandler.onResult
const onError = replyHandler.onError const onError = replyHandler.onError
var reconnect = function (client, why, err) { var lazyReconnect = function (client, why, err) {
reconnect = require('./reconnect') lazyReconnect = require('./reconnect')
reconnect(client, why, err) lazyReconnect(client, why, err)
} }
function onStreamError (client, err) { function onStreamError (client, err) {
@@ -31,7 +31,7 @@ function onStreamError (client, err) {
} }
// 'error' events get turned into exceptions if they aren't listened for. If the user handled this error // 'error' events get turned into exceptions if they aren't listened for. If the user handled this error
// then we should try to reconnect. // then we should try to reconnect.
reconnect(client, 'error', err) lazyReconnect(client, 'error', err)
} }
/** /**
@@ -91,9 +91,19 @@ function connect (client) {
client._stream.destroy() client._stream.destroy()
} }
/* istanbul ignore if: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
if (client._options.tls) { if (client._options.tls) {
client._stream = tls.connect(client._connectionOptions) client._stream = tls.connect(client._connectionOptions)
// Whenever a handshake times out.
// Older Node.js versions use "clientError", newer versions use tlsClientError.
stream.once('clientError', (err) => {
debug('clientError occurred')
onStreamError(client, err)
})
stream.once('tlsClientError', (err) => {
debug('clientError occurred')
onStreamError(client, err)
})
} else { } else {
client._stream = net.createConnection(client._connectionOptions) client._stream = net.createConnection(client._connectionOptions)
} }
@@ -105,11 +115,10 @@ function connect (client) {
// TODO: Investigate why this is not properly triggered // TODO: Investigate why this is not properly triggered
stream.setTimeout(client._options.connectTimeout, () => { stream.setTimeout(client._options.connectTimeout, () => {
// Note: This is only tested if a internet connection is established // Note: This is only tested if a internet connection is established
reconnect(client, 'timeout') lazyReconnect(client, 'timeout')
}) })
} }
/* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
const connectEvent = client._options.tls ? 'secureConnect' : 'connect' const connectEvent = client._options.tls ? 'secureConnect' : 'connect'
stream.once(connectEvent, () => { stream.once(connectEvent, () => {
stream.removeAllListeners('timeout') stream.removeAllListeners('timeout')
@@ -126,18 +135,12 @@ function connect (client) {
onStreamError(client, err) onStreamError(client, err)
}) })
/* istanbul ignore next: difficult to test and not important as long as we keep this listener */
stream.on('clientError', (err) => {
debug('clientError occurred')
onStreamError(client, err)
})
stream.once('close', (hadError) => { stream.once('close', (hadError) => {
reconnect(client, 'close') lazyReconnect(client, 'close')
}) })
stream.once('end', () => { stream.once('end', () => {
reconnect(client, 'end') lazyReconnect(client, 'end')
}) })
stream.setNoDelay() stream.setNoDelay()

View File

@@ -3,7 +3,9 @@
const utils = require('./utils') const utils = require('./utils')
const URL = require('url') const URL = require('url')
module.exports = function createClient (portArg, hostArg, options) { // TODO: Improve the unify performance by checking for the arguments length
// before trying to access that argument.
function unifyOptions (portArg, hostArg, options) {
if (typeof portArg === 'number' || (typeof portArg === 'string' && /^\d+$/.test(portArg))) { if (typeof portArg === 'number' || (typeof portArg === 'string' && /^\d+$/.test(portArg))) {
var host var host
if (typeof hostArg === 'string') { if (typeof hostArg === 'string') {
@@ -75,3 +77,5 @@ module.exports = function createClient (portArg, hostArg, options) {
return options return options
} }
module.exports = unifyOptions

View File

@@ -186,7 +186,7 @@ function authCallback (client, pass) {
if (err) { if (err) {
if (noPasswordIsSet.test(err.message)) { if (noPasswordIsSet.test(err.message)) {
utils.warn(client, 'Warning: Redis server does not require a password, but a password was supplied.') utils.warn(client, 'Warning: Redis server does not require a password, but a password was supplied.')
return 'OK' // TODO: Fix this return 'OK'
} }
return err return err
} }
@@ -224,7 +224,6 @@ RedisClient.prototype.client = function client () {
} }
var callOnWrite var callOnWrite
// CLIENT REPLY ON|OFF|SKIP // CLIENT REPLY ON|OFF|SKIP
/* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') { if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
const replyOnOff = arr[1].toString().toUpperCase() const replyOnOff = arr[1].toString().toUpperCase()
if (replyOnOff === 'ON' || replyOnOff === 'OFF' || replyOnOff === 'SKIP') { if (replyOnOff === 'ON' || replyOnOff === 'OFF' || replyOnOff === 'SKIP') {
@@ -244,7 +243,6 @@ Multi.prototype.client = function client () {
} }
var callOnWrite var callOnWrite
// CLIENT REPLY ON|OFF|SKIP // CLIENT REPLY ON|OFF|SKIP
/* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') { if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
const replyOnOff = arr[1].toString().toUpperCase() const replyOnOff = arr[1].toString().toUpperCase()
if (replyOnOff === 'ON' || replyOnOff === 'OFF' || replyOnOff === 'SKIP') { if (replyOnOff === 'ON' || replyOnOff === 'OFF' || replyOnOff === 'SKIP') {

View File

@@ -145,14 +145,14 @@ class Multi {
/** /**
* Creates an instance of Multi. * Creates an instance of Multi.
* @param {RedisClient} client * @param {RedisClient} client
* @param {string} type
* @param {any[]} [args] * @param {any[]} [args]
* @param {string} [type]
* *
* @memberof Multi * @memberof Multi
*/ */
constructor (client, type, args) { constructor (client, args, type) {
this._client = client this._client = client
this._type = type this._type = typeof type === 'string' ? type : 'multi'
this._queue = new Queue() this._queue = new Queue()
// Either undefined or an array. Fail hard if it's not an array // Either undefined or an array. Fail hard if it's not an array
if (args) { if (args) {

View File

@@ -105,7 +105,6 @@ function readyCheck (client) {
// Always fire client info command as first command even if other commands are already queued up // Always fire client info command as first command even if other commands are already queued up
client.ready = true client.ready = true
client.info().then((res) => { client.info().then((res) => {
/* istanbul ignore if: some servers might not respond with any info data. client is just a safety check that is difficult to test */
if (!res) { if (!res) {
debug('The info command returned without any data.') debug('The info command returned without any data.')
readyHandler(client) readyHandler(client)

View File

@@ -40,7 +40,7 @@ function onResult (client, reply) {
// If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands // If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
// As this is not the average use case and monitor is expensive anyway, let's change the code here, to improve // As this is not the average use case and monitor is expensive anyway, let's change the code here, to improve
// the average performance of all other commands in case of no monitor mode // the average performance of all other commands in case of no monitor mode
if (client._monitoring) { if (client._monitoring === true) {
var replyStr var replyStr
if (client.buffers && Buffer.isBuffer(reply)) { if (client.buffers && Buffer.isBuffer(reply)) {
replyStr = reply.toString() replyStr = reply.toString()
@@ -63,6 +63,7 @@ function onResult (client, reply) {
} else if (client._pubSubMode !== 1) { } else if (client._pubSubMode !== 1) {
client._pubSubMode-- client._pubSubMode--
normalReply(client, reply) normalReply(client, reply)
// TODO: Have another look at this if this could be further improved
} else if (!(reply instanceof Array) || reply.length <= 2) { } else if (!(reply instanceof Array) || reply.length <= 2) {
// Only PING and QUIT are allowed in this context besides the pub sub commands // Only PING and QUIT are allowed in this context besides the pub sub commands
// Ping replies with ['pong', null|value] and quit with 'OK' // Ping replies with ['pong', null|value] and quit with 'OK'

View File

@@ -46,25 +46,29 @@ function replyToStrings (reply) {
* @description Deep clone arbitrary objects with arrays. * @description Deep clone arbitrary objects with arrays.
* Can't handle cyclic structures (results in a range error). * Can't handle cyclic structures (results in a range error).
* Any attribute with a non primitive value besides object * Any attribute with a non primitive value besides object
* and array will be passed by reference (e.g. Buffers, Maps, Functions) * and array will be passed by reference (e.g. Buffers, Maps, Functions).
* *
* @param {any} obj * @param {any} obj
* @returns any * @returns any
*/ */
function clone (obj) { function clone (obj) {
var copy
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
copy = new Array(obj.length) const copy = new Array(obj.length)
for (var i = 0; i < obj.length; i++) { for (var i = 0; i < obj.length; i++) {
copy[i] = clone(obj[i]) copy[i] = clone(obj[i])
} }
return copy return copy
} }
if (Object.prototype.toString.call(obj) === '[object Object]') { if (Object.prototype.toString.call(obj) === '[object Object]') {
copy = {} const copy = {}
const elements = Object.keys(obj) const keys = Object.keys(obj)
for (var elem = elements.pop(); elem !== undefined; elem = elements.pop()) { while (keys.length) {
copy[elem] = clone(obj[elem]) const key = keys.pop()
if (key === 'stream') {
copy[key] = obj[key]
} else {
copy[key] = clone(obj[key])
}
} }
return copy return copy
} }
@@ -96,12 +100,10 @@ function convenienceClone (obj) {
* @param {Denque} queue * @param {Denque} queue
*/ */
function replyInOrder (client, callback, err, res, queue) { function replyInOrder (client, callback, err, res, queue) {
var commandObj const commandObj = queue
if (queue) { ? queue.peekBack()
commandObj = queue.peekBack() : (client.offlineQueue.peekBack() || client.commandQueue.peekBack())
} else {
commandObj = client.offlineQueue.peekBack() || client.commandQueue.peekBack()
}
if (!commandObj) { if (!commandObj) {
process.nextTick(callback, err, res) process.nextTick(callback, err, res)
} else { } else {
@@ -149,6 +151,21 @@ function handleReply (client, reply, command) {
return reply return reply
} }
/**
* @description Callback result print helper
*
* @param {Error|null} err
* @param {any} reply
*/
function print (err, reply) {
if (err) {
// A error always begins with Error:
console.error(err.toString())
} else {
console.log('Reply: ' + reply)
}
}
/** /**
* @description Set default reconnect variables * @description Set default reconnect variables
* *
@@ -171,5 +188,6 @@ module.exports = {
replyInOrder, replyInOrder,
warn, warn,
handleReply, handleReply,
setReconnectDefaults setReconnectDefaults,
print
} }

View File

@@ -14,9 +14,7 @@ if (process.platform !== 'win32') {
}) })
}) })
helper.allTests({ helper.allTests((ip, args) => {
allConnections: true
}, (ip, args) => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
const auth = 'porkchopsandwiches' const auth = 'porkchopsandwiches'
let client = null let client = null

View File

@@ -4,7 +4,7 @@ const assert = require('assert')
const config = require('./lib/config') const config = require('./lib/config')
const connect = require('../lib/connect') const connect = require('../lib/connect')
const helper = require('./helper') const helper = require('./helper')
const redis = config.redis const Redis = config.redis
const intercept = require('intercept-stdout') const intercept = require('intercept-stdout')
const net = require('net') const net = require('net')
let client let client
@@ -22,7 +22,7 @@ describe('connection tests', () => {
// Besides that some functions also have to be monkey patched to be safe from errors in this case. // Besides that some functions also have to be monkey patched to be safe from errors in this case.
// Therefore this is not officially supported! // Therefore this is not officially supported!
const socket = new net.Socket() const socket = new net.Socket()
client = new redis.RedisClient({ client = new Redis({
prefix: 'test', prefix: 'test',
stream: socket stream: socket
}) })
@@ -38,7 +38,7 @@ describe('connection tests', () => {
describe('quit on lost connections', () => { describe('quit on lost connections', () => {
it('calling quit while the connection is down should not end in reconnecting version a', (done) => { it('calling quit while the connection is down should not end in reconnecting version a', (done) => {
let called = 0 let called = 0
client = redis.createClient({ client = Redis.createClient({
connectTimeout: 5, connectTimeout: 5,
port: 9999, port: 9999,
retryStrategy (options) { retryStrategy (options) {
@@ -59,7 +59,7 @@ describe('connection tests', () => {
it('calling quit while the connection is down should not end in reconnecting version b', () => { it('calling quit while the connection is down should not end in reconnecting version b', () => {
let called = false let called = false
client = redis.createClient(9999) client = Redis.createClient(9999)
client.set('foo', 'bar').catch((err) => { client.set('foo', 'bar').catch((err) => {
assert.strictEqual(err.message, 'Stream connection ended and command aborted.') assert.strictEqual(err.message, 'Stream connection ended and command aborted.')
called = true called = true
@@ -72,7 +72,7 @@ describe('connection tests', () => {
it('calling quit while the connection is down without offline queue should end the connection right away', () => { it('calling quit while the connection is down without offline queue should end the connection right away', () => {
let called = false let called = false
client = redis.createClient(9999, { client = Redis.createClient(9999, {
enableOfflineQueue: false enableOfflineQueue: false
}) })
client.set('foo', 'bar').catch((err) => { client.set('foo', 'bar').catch((err) => {
@@ -87,7 +87,7 @@ describe('connection tests', () => {
it('calling quit while connected without offline queue should end the connection when all commands have finished', (done) => { it('calling quit while connected without offline queue should end the connection when all commands have finished', (done) => {
let called = false let called = false
client = redis.createClient({ client = Redis.createClient({
enableOfflineQueue: false enableOfflineQueue: false
}) })
client.on('ready', () => { client.on('ready', () => {
@@ -104,7 +104,7 @@ describe('connection tests', () => {
}) })
it('do not quit before connected or a connection issue is detected', () => { it('do not quit before connected or a connection issue is detected', () => {
client = redis.createClient() client = Redis.createClient()
return Promise.all([ return Promise.all([
client.set('foo', 'bar').then(helper.isString('OK')), client.set('foo', 'bar').then(helper.isString('OK')),
client.quit() client.quit()
@@ -112,7 +112,7 @@ describe('connection tests', () => {
}) })
it('quit "succeeds" even if the client connection is closed while doing so', () => { it('quit "succeeds" even if the client connection is closed while doing so', () => {
client = redis.createClient() client = Redis.createClient()
return client.set('foo', 'bar').then((res) => { return client.set('foo', 'bar').then((res) => {
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
const promise = client.quit().then((res) => { const promise = client.quit().then((res) => {
@@ -124,7 +124,7 @@ describe('connection tests', () => {
}) })
it('quit right away if connection drops while quit command is on the fly', (done) => { it('quit right away if connection drops while quit command is on the fly', (done) => {
client = redis.createClient() client = Redis.createClient()
client.once('ready', () => { client.once('ready', () => {
client.set('foo', 'bar').catch(helper.isError()) client.set('foo', 'bar').catch(helper.isError())
client.quit().then(() => done()) client.quit().then(() => done())
@@ -138,7 +138,7 @@ describe('connection tests', () => {
describe('on lost connection', () => { describe('on lost connection', () => {
it('end connection while retry is still ongoing', (done) => { it('end connection while retry is still ongoing', (done) => {
const connectTimeout = 1000 // in ms const connectTimeout = 1000 // in ms
client = redis.createClient({ client = Redis.createClient({
connectTimeout connectTimeout
}) })
@@ -160,7 +160,7 @@ describe('connection tests', () => {
family: ip, family: ip,
retryStrategy () {} retryStrategy () {}
} }
client = redis.createClient(options) client = Redis.createClient(options)
assert.strictEqual(client._connectionOptions.family, ip === 'IPv6' ? 6 : 4) assert.strictEqual(client._connectionOptions.family, ip === 'IPv6' ? 6 : 4)
assert.strictEqual(Object.keys(options).length, 4) assert.strictEqual(Object.keys(options).length, 4)
const end = helper.callFuncAfter(done, 2) const end = helper.callFuncAfter(done, 2)
@@ -172,7 +172,7 @@ describe('connection tests', () => {
}) })
it('retryStrategy used to reconnect with individual error', (done) => { it('retryStrategy used to reconnect with individual error', (done) => {
client = redis.createClient({ client = Redis.createClient({
retryStrategy (options) { retryStrategy (options) {
if (options.totalRetryTime > 150) { if (options.totalRetryTime > 150) {
client.set('foo', 'bar').then(assert, (err) => { client.set('foo', 'bar').then(assert, (err) => {
@@ -191,7 +191,7 @@ describe('connection tests', () => {
}) })
it('retryStrategy used to reconnect', (done) => { it('retryStrategy used to reconnect', (done) => {
client = redis.createClient({ client = Redis.createClient({
retryStrategy (options) { retryStrategy (options) {
if (options.totalRetryTime > 150) { if (options.totalRetryTime > 150) {
client.set('foo', 'bar').catch((err) => { client.set('foo', 'bar').catch((err) => {
@@ -213,17 +213,17 @@ describe('connection tests', () => {
const unhookIntercept = intercept(() => { const unhookIntercept = intercept(() => {
return '' return ''
}) })
redis.debugMode = true Redis.debugMode = true
client = redis.createClient({ client = Redis.createClient({
retryStrategy (options) { retryStrategy (options) {
client.set('foo', 'bar').catch((err) => { client.set('foo', 'bar').catch((err) => {
assert.strictEqual(err.code, 'NR_CLOSED') assert.strictEqual(err.code, 'NR_CLOSED')
assert.strictEqual(err.message, 'Stream connection ended and command aborted.') assert.strictEqual(err.message, 'Stream connection ended and command aborted.')
unhookIntercept() unhookIntercept()
redis.debugMode = false Redis.debugMode = false
done() done()
}) })
assert(redis.debugMode) assert(Redis.debugMode)
return null return null
} }
}) })
@@ -237,7 +237,7 @@ describe('connection tests', () => {
// TODO: Fix this test // TODO: Fix this test
it.skip('emit an error after the socket timeout exceeded the connectTimeout time', (done) => { it.skip('emit an error after the socket timeout exceeded the connectTimeout time', (done) => {
const connectTimeout = 500 // in ms const connectTimeout = 500 // in ms
client = redis.createClient({ client = Redis.createClient({
// Auto detect ipv4 and use non routeable ip to trigger the timeout // Auto detect ipv4 and use non routeable ip to trigger the timeout
host: '10.255.255.1', host: '10.255.255.1',
connectTimeout, connectTimeout,
@@ -271,7 +271,7 @@ describe('connection tests', () => {
}) })
it('use the system socket timeout if the connectTimeout has not been provided', (done) => { it('use the system socket timeout if the connectTimeout has not been provided', (done) => {
client = redis.createClient({ client = Redis.createClient({
host: '2001:db8::ff00:42:8329' // auto detect ip v6 host: '2001:db8::ff00:42:8329' // auto detect ip v6
}) })
assert.strictEqual(client.address, '2001:db8::ff00:42:8329:6379') assert.strictEqual(client.address, '2001:db8::ff00:42:8329:6379')
@@ -284,7 +284,7 @@ describe('connection tests', () => {
}) })
it('clears the socket timeout after a connection has been established', (done) => { it('clears the socket timeout after a connection has been established', (done) => {
client = redis.createClient({ client = Redis.createClient({
connectTimeout: 1000 connectTimeout: 1000
}) })
process.nextTick(assert.strictEqual, client._stream._idleTimeout, 1000) process.nextTick(assert.strictEqual, client._stream._idleTimeout, 1000)
@@ -296,7 +296,7 @@ describe('connection tests', () => {
}) })
it('connect with host and port provided in the options object', (done) => { it('connect with host and port provided in the options object', (done) => {
client = redis.createClient({ client = Redis.createClient({
host: 'localhost', host: 'localhost',
port: '6379', port: '6379',
connectTimeout: 1000 connectTimeout: 1000
@@ -309,7 +309,7 @@ describe('connection tests', () => {
if (process.platform === 'win32') { if (process.platform === 'win32') {
this.skip() this.skip()
} }
client = redis.createClient({ client = Redis.createClient({
path: '/tmp/redis.sock', path: '/tmp/redis.sock',
connectTimeout: 1000 connectTimeout: 1000
}) })
@@ -317,7 +317,7 @@ describe('connection tests', () => {
}) })
it('connects correctly with args', (done) => { it('connects correctly with args', (done) => {
client = redis.createClient.apply(null, args) client = Redis.createClient.apply(null, args)
client.on('error', done) client.on('error', done)
client.once('ready', () => { client.once('ready', () => {
@@ -327,7 +327,7 @@ describe('connection tests', () => {
}) })
it('connects correctly with default values', (done) => { it('connects correctly with default values', (done) => {
client = redis.createClient() client = Redis.createClient()
client.on('error', done) client.on('error', done)
client.once('ready', () => { client.once('ready', () => {
@@ -337,7 +337,7 @@ describe('connection tests', () => {
}) })
it('connects with a port only', (done) => { it('connects with a port only', (done) => {
client = redis.createClient(6379) client = Redis.createClient(6379)
assert.strictEqual(client._connectionOptions.family, 4) assert.strictEqual(client._connectionOptions.family, 4)
client.on('error', done) client.on('error', done)
@@ -348,7 +348,7 @@ describe('connection tests', () => {
}) })
it('connects correctly to localhost', (done) => { it('connects correctly to localhost', (done) => {
client = redis.createClient(null, null) client = Redis.createClient(null, null)
client.on('error', done) client.on('error', done)
client.once('ready', () => { client.once('ready', () => {
@@ -358,7 +358,7 @@ describe('connection tests', () => {
}) })
it('connects correctly to the provided host with the port set to null', (done) => { it('connects correctly to the provided host with the port set to null', (done) => {
client = redis.createClient(null, 'localhost') client = Redis.createClient(null, 'localhost')
client.on('error', done) client.on('error', done)
assert.strictEqual(client.address, 'localhost:6379') assert.strictEqual(client.address, 'localhost:6379')
@@ -371,7 +371,7 @@ describe('connection tests', () => {
}) })
it('connects correctly to localhost and no ready check', (done) => { it('connects correctly to localhost and no ready check', (done) => {
client = redis.createClient(undefined, undefined, { client = Redis.createClient(undefined, undefined, {
noReadyCheck: true noReadyCheck: true
}) })
client.on('error', done) client.on('error', done)
@@ -385,7 +385,7 @@ describe('connection tests', () => {
}) })
it('connects correctly to the provided host with the port set to undefined', (done) => { it('connects correctly to the provided host with the port set to undefined', (done) => {
client = redis.createClient(undefined, 'localhost', { client = Redis.createClient(undefined, 'localhost', {
noReadyCheck: true noReadyCheck: true
}) })
client.on('error', done) client.on('error', done)
@@ -400,7 +400,7 @@ describe('connection tests', () => {
}) })
it('connects correctly even if the info command is not present on the redis server', (done) => { it('connects correctly even if the info command is not present on the redis server', (done) => {
client = redis.createClient.apply(null, args) client = Redis.createClient.apply(null, args)
const end = helper.callFuncAfter(done, 2) const end = helper.callFuncAfter(done, 2)
client.info = function () { client.info = function () {
// Mock the result // Mock the result
@@ -415,13 +415,13 @@ describe('connection tests', () => {
if (ip === 'IPv4') { if (ip === 'IPv4') {
it('allows connecting with the redis url to the default host and port, select db 3 and warn about duplicate db option', (done) => { it('allows connecting with the redis url to the default host and port, select db 3 and warn about duplicate db option', (done) => {
client = redis.createClient('redis:///3?db=3') client = Redis.createClient('redis:///3?db=3')
assert.strictEqual(client.selectedDb, '3') assert.strictEqual(client.selectedDb, '3')
client.on('ready', done) client.on('ready', done)
}) })
it('allows connecting with the redis url and the default port and auth provided even though it is not required', (done) => { it('allows connecting with the redis url and the default port and auth provided even though it is not required', (done) => {
client = redis.createClient(`redis://:porkchopsandwiches@${config.HOST[ip]}/`) client = Redis.createClient(`redis://:porkchopsandwiches@${config.HOST[ip]}/`)
const end = helper.callFuncAfter(done, 2) const end = helper.callFuncAfter(done, 2)
client.on('warning', (msg) => { client.on('warning', (msg) => {
assert.strictEqual(msg, 'Warning: Redis server does not require a password, but a password was supplied.') assert.strictEqual(msg, 'Warning: Redis server does not require a password, but a password was supplied.')
@@ -431,7 +431,7 @@ describe('connection tests', () => {
}) })
it('allows connecting with the redis url as first parameter and the options as second parameter', (done) => { it('allows connecting with the redis url as first parameter and the options as second parameter', (done) => {
client = redis.createClient('//127.0.0.1', { client = Redis.createClient('//127.0.0.1', {
connectTimeout: 1000 connectTimeout: 1000
}) })
assert.strictEqual(client._options.connectTimeout, 1000) assert.strictEqual(client._options.connectTimeout, 1000)
@@ -439,7 +439,7 @@ describe('connection tests', () => {
}) })
it('allows connecting with the redis url in the options object and works with protocols other than the redis protocol (e.g. http)', (done) => { it('allows connecting with the redis url in the options object and works with protocols other than the redis protocol (e.g. http)', (done) => {
client = redis.createClient({ client = Redis.createClient({
url: `http://foo:porkchopsandwiches@${config.HOST[ip]}/3` url: `http://foo:porkchopsandwiches@${config.HOST[ip]}/3`
}) })
assert.strictEqual(client._options.password, 'porkchopsandwiches') assert.strictEqual(client._options.password, 'porkchopsandwiches')
@@ -453,13 +453,13 @@ describe('connection tests', () => {
const options = { const options = {
detectBuffers: false detectBuffers: false
} }
client = redis.createClient(`redis://${config.HOST[ip]}:${config.PORT}`, options) client = Redis.createClient(`redis://${config.HOST[ip]}:${config.PORT}`, options)
assert.strictEqual(Object.keys(options).length, 1) assert.strictEqual(Object.keys(options).length, 1)
client.on('ready', done) client.on('ready', done)
}) })
it('allows connecting with the redis url and no auth and options as third parameter', (done) => { it('allows connecting with the redis url and no auth and options as third parameter', (done) => {
client = redis.createClient(`redis://${config.HOST[ip]}:${config.PORT}`, null, { client = Redis.createClient(`redis://${config.HOST[ip]}:${config.PORT}`, null, {
detectBuffers: false detectBuffers: false
}) })
client.on('ready', done) client.on('ready', done)
@@ -467,7 +467,7 @@ describe('connection tests', () => {
} }
it('redis still loading <= 500', (done) => { it('redis still loading <= 500', (done) => {
client = redis.createClient.apply(null, args) client = Redis.createClient.apply(null, args)
const tmp = client.info.bind(client) const tmp = client.info.bind(client)
const end = helper.callFuncAfter(done, 3) const end = helper.callFuncAfter(done, 3)
let delayed = false let delayed = false
@@ -496,7 +496,7 @@ describe('connection tests', () => {
}) })
it('redis still loading > 1000ms', (done) => { it('redis still loading > 1000ms', (done) => {
client = redis.createClient.apply(null, args) client = Redis.createClient.apply(null, args)
const tmp = client.info.bind(client) const tmp = client.info.bind(client)
const end = helper.callFuncAfter(done, 3) const end = helper.callFuncAfter(done, 3)
let delayed = false let delayed = false

View File

@@ -2,9 +2,34 @@
const assert = require('assert') const assert = require('assert')
const Queue = require('denque') const Queue = require('denque')
const intercept = require('intercept-stdout')
const utils = require('../lib/utils') const utils = require('../lib/utils')
describe('utils.js', () => { describe('utils.js', () => {
describe('print helper', function () {
it('callback with reply', function () {
var text = ''
const unhookIntercept = intercept(function (data) {
text += data
return ''
})
utils.print(null, 'abc')
unhookIntercept()
assert.strictEqual(text, 'Reply: abc\n')
})
it('callback with error', function () {
var text = ''
const unhookIntercept = intercept(function (data) {
text += data
return ''
})
utils.print(new Error('Wonderful exception'))
unhookIntercept()
assert.strictEqual(text, 'Error: Wonderful exception\n')
})
})
describe('clone', () => { describe('clone', () => {
it('ignore the object prototype and clone a nested array / object', () => { it('ignore the object prototype and clone a nested array / object', () => {
const obj = { const obj = {