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

chore: refactor codebase to promises

This commit is contained in:
Ruben Bridgewater
2017-05-19 06:14:29 +02:00
parent b2613b2270
commit 6be5575c5b
85 changed files with 2081 additions and 3690 deletions

View File

@@ -119,7 +119,7 @@ Test.prototype.newClient = function (id) {
Test.prototype.onClientsReady = function () { Test.prototype.onClientsReady = function () {
process.stdout.write(`${lpad(this.args.descr, 13) }, ${this.args.batch ? lpad(`batch ${this.args.batch}`, 9) : lpad(this.args.pipeline, 9) }/${this.clientsReady} `) process.stdout.write(`${lpad(this.args.descr, 13) }, ${this.args.batch ? lpad(`batch ${this.args.batch}`, 9) : lpad(this.args.pipeline, 9) }/${this.clientsReady} `)
this.testStart = Date.now() this.testStart = Date.now()
this.fillPipeline() return this.fillPipeline()
} }
Test.prototype.fillPipeline = function () { Test.prototype.fillPipeline = function () {
@@ -131,19 +131,19 @@ Test.prototype.fillPipeline = function () {
} }
this.ended = true this.ended = true
this.printStats() this.printStats()
this.stopClients() return this.stopClients()
return
} }
if (this.batchPipeline) { if (this.batchPipeline) {
this.batch() return this.batch()
} else {
while (pipeline < this.maxPipeline) {
this.commandsSent++
pipeline++
this.sendNext()
}
} }
const promises = []
while (pipeline < this.maxPipeline) {
this.commandsSent++
pipeline++
promises.push(this.sendNext())
}
return Promise.all(promises)
} }
Test.prototype.batch = function () { Test.prototype.batch = function () {
@@ -158,29 +158,24 @@ Test.prototype.batch = function () {
batch[this.args.command](this.args.args) batch[this.args.command](this.args.args)
} }
batch.exec((err, res) => { batch.exec().then((res) => {
if (err) {
throw err
}
self.commandsCompleted += res.length self.commandsCompleted += res.length
self.commandLatency.update(process.hrtime(start)[1]) self.commandLatency.update(process.hrtime(start)[1])
self.fillPipeline() return self.fillPipeline()
}) })
} }
Test.prototype.stopClients = function () { Test.prototype.stopClients = function () {
const self = this const self = this
this.clients.forEach((client, pos) => { return Promise.all(this.clients.map((client, pos) => {
if (pos === self.clients.length - 1) { if (pos === self.clients.length - 1) {
client.quit((err, res) => { return client.quit().then((res) => {
if (err) throw err
self.callback() self.callback()
}) })
} else {
client.quit()
} }
}) return client.quit()
}))
} }
Test.prototype.sendNext = function () { Test.prototype.sendNext = function () {
@@ -188,13 +183,10 @@ Test.prototype.sendNext = function () {
const curClient = this.commandsSent % this.clients.length const curClient = this.commandsSent % this.clients.length
const start = process.hrtime() const start = process.hrtime()
this.clients[curClient][this.args.command](this.args.args, (err, res) => { this.clients[curClient][this.args.command](this.args.args).then((res) => {
if (err) {
throw err
}
self.commandsCompleted++ self.commandsCompleted++
self.commandLatency.update(process.hrtime(start)[1]) self.commandLatency.update(process.hrtime(start)[1])
self.fillPipeline() return self.fillPipeline()
}) })
} }
@@ -280,7 +272,7 @@ function next () {
next() next()
}) })
} else if (rp) { } else if (rp) {
// Stop the redis process if started by the benchmark // Stop the redis process if started by the benchmark
rp.stop(() => { rp.stop(() => {
rp = undefined rp = undefined
next() next()

View File

@@ -2,10 +2,24 @@
## v.3.0.0-alpha1 - XX XXX, 2017 ## v.3.0.0-alpha1 - XX XXX, 2017
Version three is mainly a release to remove a lot of old cruft and open the doors for new features again.
It became a hassle to implement new things while supporting all the old and deprecated features.
Therefore this is going to a big breaking change. For most of these there's likely not much to change,
but some of the changes are significant like dropping support for callbacks! To mitigate this breakage
there's a migration library you can include. It restores e.g. support for callbacks and some of the dropped
options and it will warn you in case you use any removed feature or option.
It will not restore the support for old Node.js versions, the return value of (p)(un)subscribe calls,
the backpressure indicator and the changed connectTimeout behavior. It will also only partially restore
snake_case support and maybe more.
Breaking Changes Breaking Changes
- Dropped support for `UPPER_CASE` commands - Dropped support for `UPPER_CASE` commands
- Dropped support for `snake_case` - Dropped support for `snake_case`
- Dropped support for domains
- Dropped support for Redis 2.4
- Dropped support for Node.js < 4 - Dropped support for Node.js < 4
- Removed `drain` event - Removed `drain` event
- Removed `idle` event - Removed `idle` event
@@ -13,6 +27,7 @@ Breaking Changes
- 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 `Redis.print` helper function
- Removed backpressure indicator from function return value - Removed backpressure indicator from function return value
- Changed return value of `(p)(un)subscribe` - Changed return value of `(p)(un)subscribe`
@@ -20,6 +35,9 @@ Breaking Changes
- Changed `connectTimeout` (connect_timeout) option - Changed `connectTimeout` (connect_timeout) option
- This timeout does not limit the total retry time anymore - This timeout does not limit the total retry time anymore
- From now on this will only set the stream timeout to connect to the host - From now on this will only set the stream timeout to connect to the host
- Changed return value for `multi` and `batch` in case of an error
- If an error occurs for one of the calls, the result is from now on always going to be an error
- The result of all queries can be inspected on the error through the `result` attribute
- Only emit ready when all commands were truly send to Redis - Only emit ready when all commands were truly send to Redis
## v.2.7.2 - 14 Mar, 2017 ## v.2.7.2 - 14 Mar, 2017

215
index.js
View File

@@ -1,5 +1,6 @@
'use strict' 'use strict'
// TODO: Replace all for in loops!
const Buffer = require('safe-buffer').Buffer const Buffer = require('safe-buffer').Buffer
const net = require('net') const net = require('net')
const tls = require('tls') const tls = require('tls')
@@ -113,6 +114,14 @@ function RedisClient (options, stream) {
this.buffers = options.returnBuffers || options.detectBuffers this.buffers = options.returnBuffers || options.detectBuffers
this.options = options this.options = options
this.reply = 'ON' // Returning replies is the default this.reply = 'ON' // Returning replies is the default
this.retryStrategy = options.retryStrategy || function (options) {
if (options.attempt > 100) {
return
}
// reconnect after
return Math.min(options.attempt * 100, 3000)
}
this.retryStrategyProvided = !!options.retryStrategy
this.subscribeChannels = [] this.subscribeChannels = []
// Init parser // Init parser
this.replyParser = createParser(this) this.replyParser = createParser(this)
@@ -150,8 +159,8 @@ function createParser (self) {
error: err, error: err,
queues: ['commandQueue'] queues: ['commandQueue']
}) })
self.emit('error', err)
self.createStream() self.createStream()
setImmediate(() => self.emit('error', err))
}, },
returnBuffers: self.buffers || self.messageBuffers, returnBuffers: self.buffers || self.messageBuffers,
stringNumbers: self.options.stringNumbers || false stringNumbers: self.options.stringNumbers || false
@@ -195,17 +204,17 @@ RedisClient.prototype.createStream = function () {
} }
if (this.options.connectTimeout) { if (this.options.connectTimeout) {
// TODO: Investigate why this is not properly triggered
this.stream.setTimeout(this.connectTimeout, () => { this.stream.setTimeout(this.connectTimeout, () => {
// Note: This is only tested if a internet connection is established // Note: This is only tested if a internet connection is established
self.retryTotaltime = self.connectTimeout
self.connectionGone('timeout') self.connectionGone('timeout')
}) })
} }
/* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */ /* istanbul ignore next: travis does not work with stunnel atm. Therefore the tls tests are skipped on travis */
const connectEvent = this.options.tls ? 'secureConnect' : 'connect' const connectEvent = this.options.tls ? 'secureConnect' : 'connect'
this.stream.once(connectEvent, function () { this.stream.once(connectEvent, () => {
this.removeAllListeners('timeout') this.stream.removeAllListeners('timeout')
self.timesConnected++ self.timesConnected++
self.onConnect() self.onConnect()
}) })
@@ -234,10 +243,18 @@ RedisClient.prototype.createStream = function () {
self.connectionGone('end') self.connectionGone('end')
}) })
this.stream.setNoDelay()
// Fire the command before redis is connected to be sure it's the first fired command // Fire the command before redis is connected to be sure it's the first fired command
if (this.authPass !== undefined) { if (this.authPass !== undefined) {
this.ready = true this.ready = true
this.auth(this.authPass) this.auth(this.authPass).catch((err) => {
this.closing = true
process.nextTick(() => {
this.emit('error', err)
this.end(true)
})
})
this.ready = false this.ready = false
} }
} }
@@ -255,8 +272,7 @@ RedisClient.prototype.uncork = noop
RedisClient.prototype.initializeRetryVars = function () { RedisClient.prototype.initializeRetryVars = function () {
this.retryTimer = null this.retryTimer = null
this.retryTotaltime = 0 this.retryTotaltime = 0
this.retryDelay = 200 this.retryDelay = 100
this.retryBackoff = 1.7
this.attempts = 1 this.attempts = 1
} }
@@ -276,7 +292,6 @@ RedisClient.prototype.warn = function (msg) {
// Flush provided queues, erroring any items with a callback first // Flush provided queues, erroring any items with a callback first
RedisClient.prototype.flushAndError = function (errorAttributes, options) { RedisClient.prototype.flushAndError = function (errorAttributes, options) {
options = options || {} options = options || {}
const aggregatedErrors = []
const queueNames = options.queues || ['commandQueue', 'offlineQueue'] // Flush the commandQueue first to keep the order intact const queueNames = options.queues || ['commandQueue', 'offlineQueue'] // Flush the commandQueue first to keep the order intact
for (let i = 0; i < queueNames.length; i++) { for (let i = 0; i < queueNames.length; i++) {
// If the command was fired it might have been processed so far // If the command was fired it might have been processed so far
@@ -298,25 +313,9 @@ RedisClient.prototype.flushAndError = function (errorAttributes, options) {
if (options.error) { if (options.error) {
err.origin = options.error err.origin = options.error
} }
if (typeof commandObj.callback === 'function') { commandObj.callback(err)
commandObj.callback(err)
} else {
aggregatedErrors.push(err)
}
} }
} }
// Currently this would be a breaking change, therefore it's only emitted in debugMode
if (exports.debugMode && aggregatedErrors.length) {
let error
if (aggregatedErrors.length === 1) {
error = aggregatedErrors[0]
} else {
errorAttributes.message = errorAttributes.message.replace('It', 'They').replace(/command/i, '$&s')
error = new errorClasses.AggregateError(errorAttributes)
error.errors = aggregatedErrors
}
this.emit('error', error)
}
} }
RedisClient.prototype.onError = function (err) { RedisClient.prototype.onError = function (err) {
@@ -330,7 +329,7 @@ RedisClient.prototype.onError = function (err) {
this.ready = false this.ready = false
// Only emit the error if the retryStrategy option is not set // Only emit the error if the retryStrategy option is not set
if (!this.options.retryStrategy) { if (this.retryStrategyProvided === false) {
this.emit('error', err) this.emit('error', 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
@@ -381,35 +380,62 @@ RedisClient.prototype.onReady = function () {
// Restore modal commands from previous connection. The order of the commands is important // Restore modal commands from previous connection. The order of the commands is important
if (this.selectedDb !== undefined) { if (this.selectedDb !== undefined) {
this.internalSendCommand(new Command('select', [this.selectedDb])) this.internalSendCommand(new Command('select', [this.selectedDb])).catch((err) => {
if (!this.closing) {
// TODO: These internal things should be wrapped in a
// special error that describes what is happening
process.nextTick(() => this.emit('error', err))
}
})
} }
if (this.monitoring) { // Monitor has to be fired before pub sub commands if (this.monitoring) { // Monitor has to be fired before pub sub commands
this.internalSendCommand(new Command('monitor', [])) this.internalSendCommand(new Command('monitor', [])).catch((err) => {
if (!this.closing) {
process.nextTick(() => this.emit('error', err))
}
})
} }
const callbackCount = Object.keys(this.subscriptionSet).length const callbackCount = Object.keys(this.subscriptionSet).length
// TODO: Replace the disableResubscribing by a individual function that may be called
// Add HOOKS!!!
// Replace the disableResubscribing by:
// resubmit: {
// select: true,
// monitor: true,
// subscriptions: true,
// // individual: function noop () {}
// }
if (!this.options.disableResubscribing && callbackCount) { if (!this.options.disableResubscribing && callbackCount) {
debug('Sending pub/sub onReady commands') debug('Sending pub/sub onReady commands')
for (const key in this.subscriptionSet) { for (const key in this.subscriptionSet) {
const command = key.slice(0, key.indexOf('_')) const command = key.slice(0, key.indexOf('_'))
const args = this.subscriptionSet[key] const args = this.subscriptionSet[key]
this[command]([args]) this[command]([args]).catch((err) => {
if (!this.closing) {
process.nextTick(() => this.emit('error', err))
}
})
} }
} }
this.sendOfflineQueue() this.sendOfflineQueue()
this.emit('ready') this.emit('ready')
} }
RedisClient.prototype.onInfoCmd = function (err, res) { RedisClient.prototype.onInfoFail = function (err) {
if (err) { if (this.closing) {
if (err.message === "ERR unknown command 'info'") {
this.onReady()
return
}
err.message = `Ready check failed: ${err.message}`
this.emit('error', err)
return return
} }
if (err.message === "ERR unknown command 'info'") {
this.onReady()
return
}
err.message = `Ready check failed: ${err.message}`
this.emit('error', err)
return
}
RedisClient.prototype.onInfoCmd = function (res) {
/* istanbul ignore if: some servers might not respond with any info data. This is just a safety check that is difficult to test */ /* istanbul ignore if: some servers might not respond with any info data. This 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.')
@@ -434,19 +460,18 @@ RedisClient.prototype.onInfoCmd = function (err, res) {
retryTime = 1000 retryTime = 1000
} }
debug(`Redis server still loading, trying again in ${retryTime}`) debug(`Redis server still loading, trying again in ${retryTime}`)
setTimeout((self) => { return new Promise((resolve) => {
self.readyCheck() setTimeout((self) => resolve(self.readyCheck()), retryTime, this)
}, retryTime, this) })
} }
RedisClient.prototype.readyCheck = function () { RedisClient.prototype.readyCheck = function () {
const self = this
debug('Checking server ready state...') debug('Checking server ready state...')
// Always fire this info command as first command even if other commands are already queued up // Always fire this info command as first command even if other commands are already queued up
this.ready = true this.ready = true
this.info((err, res) => { this.info()
self.onInfoCmd(err, res) .then((res) => this.onInfoCmd(res))
}) .catch((err) => this.onInfoFail(err))
this.ready = false this.ready = false
} }
@@ -471,7 +496,6 @@ const retryConnection = function (self, error) {
self.retryTotaltime += self.retryDelay self.retryTotaltime += self.retryDelay
self.attempts += 1 self.attempts += 1
self.retryDelay = Math.round(self.retryDelay * self.retryBackoff)
self.createStream() self.createStream()
self.retryTimer = null self.retryTimer = null
} }
@@ -498,6 +522,20 @@ RedisClient.prototype.connectionGone = function (why, error) {
this.emittedEnd = true this.emittedEnd = true
} }
if (why === 'timeout') {
var message = 'Redis connection in broken state: connection timeout exceeded.'
const err = new Error(message)
// TODO: Find better error codes...
err.code = 'CONNECTION_BROKEN'
this.flushAndError({
message: message,
code: 'CONNECTION_BROKEN'
})
this.emit('error', err)
this.end(false)
return
}
// If this is a requested shutdown, then don't retry // If this is a requested shutdown, then don't retry
if (this.closing) { if (this.closing) {
debug('Connection ended by quit / end command, not retrying.') debug('Connection ended by quit / end command, not retrying.')
@@ -510,28 +548,29 @@ RedisClient.prototype.connectionGone = function (why, error) {
return return
} }
if (typeof this.options.retryStrategy === 'function') { this.retryDelay = this.retryStrategy({
const retryParams = { attempt: this.attempts,
attempt: this.attempts, error,
error, totalRetryTime: this.retryTotaltime,
totalRetryTime: this.retryTotaltime, timesConnected: this.timesConnected
timesConnected: this.timesConnected })
if (typeof this.retryDelay !== 'number') {
// Pass individual error through
if (this.retryDelay instanceof Error) {
error = this.retryDelay
} }
this.retryDelay = this.options.retryStrategy(retryParams) this.flushAndError({
if (typeof this.retryDelay !== 'number') { message: 'Stream connection ended and command aborted.',
// Pass individual error through code: 'NR_CLOSED'
if (this.retryDelay instanceof Error) { }, {
error = this.retryDelay error
} })
this.flushAndError({ // TODO: Check if this is so smart
message: 'Stream connection ended and command aborted.', if (error) {
code: 'NR_CLOSED' this.emit('error', error)
}, {
error
})
this.end(false)
return
} }
this.end(false)
return
} }
// Retry commands after a reconnect instead of throwing an error. Use this with caution // Retry commands after a reconnect instead of throwing an error. Use this with caution
@@ -574,19 +613,15 @@ RedisClient.prototype.returnError = function (err) {
err.code = match[1] err.code = match[1]
} }
utils.callbackOrEmit(this, commandObj.callback, err) commandObj.callback(err)
} }
function normalReply (self, reply) { function normalReply (self, reply) {
const commandObj = self.commandQueue.shift() const commandObj = self.commandQueue.shift()
if (typeof commandObj.callback === 'function') { if (commandObj.command !== 'exec') {
if (commandObj.command !== 'exec') { reply = self.handleReply(reply, commandObj.command, commandObj.bufferArgs)
reply = self.handleReply(reply, commandObj.command, commandObj.bufferArgs)
}
commandObj.callback(null, reply)
} else {
debug('No callback for reply')
} }
commandObj.callback(null, reply)
} }
function subscribeUnsubscribe (self, reply, type) { function subscribeUnsubscribe (self, reply, type) {
@@ -600,13 +635,13 @@ function subscribeUnsubscribe (self, reply, type) {
// Emit first, then return the callback // Emit first, then return the callback
if (channel !== null) { // Do not emit or "unsubscribe" something if there was no channel to unsubscribe from if (channel !== null) { // Do not emit or "unsubscribe" something if there was no channel to unsubscribe from
self.emit(type, channel, count)
if (type === 'subscribe' || type === 'psubscribe') { if (type === 'subscribe' || type === 'psubscribe') {
self.subscriptionSet[`${type}_${channel}`] = channel self.subscriptionSet[`${type}_${channel}`] = channel
} else { } else {
type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe' // Make types consistent const innerType = type === 'unsubscribe' ? 'subscribe' : 'psubscribe' // Make types consistent
delete self.subscriptionSet[`${type}_${channel}`] delete self.subscriptionSet[`${innerType}_${channel}`]
} }
self.emit(type, channel, count)
self.subscribeChannels.push(channel) self.subscribeChannels.push(channel)
} }
@@ -625,9 +660,7 @@ function subscribeUnsubscribe (self, reply, type) {
} }
} }
self.commandQueue.shift() self.commandQueue.shift()
if (typeof commandObj.callback === 'function') { commandObj.callback(null, [count, self.subscribeChannels])
commandObj.callback(null, [count, self.subscribeChannels])
}
self.subscribeChannels = [] self.subscribeChannels = []
self.subCommandsLeft = 0 self.subCommandsLeft = 0
} else { } else {
@@ -738,16 +771,14 @@ RedisClient.prototype.internalSendCommand = function (commandObj) {
let bigData = false let bigData = false
const argsCopy = new Array(len) const argsCopy = new Array(len)
if (process.domain && commandObj.callback) {
commandObj.callback = process.domain.bind(commandObj.callback)
}
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
handleOfflineCommand(this, commandObj) handleOfflineCommand(this, commandObj)
return return commandObj.promise
} }
// TODO: Refactor this to also accept SET, MAP and ArrayBuffer
// TODO: Add a utility function to create errors with all necessary params
for (i = 0; i < len; i += 1) { for (i = 0; i < len; i += 1) {
if (typeof args[i] === 'string') { if (typeof args[i] === 'string') {
// 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons // 30000 seemed to be a good value to switch to buffers after testing and checking the pros and cons
@@ -764,7 +795,8 @@ RedisClient.prototype.internalSendCommand = function (commandObj) {
const err = new TypeError('The command contains a "null" argument but NodeRedis can only handle strings, numbers and buffers.') const err = new TypeError('The command contains a "null" argument but NodeRedis can only handle strings, numbers and buffers.')
err.command = command.toUpperCase() err.command = command.toUpperCase()
err.args = args err.args = args
return utils.replyInOrder(this, commandObj.callback, err, undefined, this.commandQueue) utils.replyInOrder(this, commandObj.callback, err, undefined, this.commandQueue)
return commandObj.promise
} else if (Buffer.isBuffer(args[i])) { } else if (Buffer.isBuffer(args[i])) {
argsCopy[i] = args[i] argsCopy[i] = args[i]
commandObj.bufferArgs = true commandObj.bufferArgs = true
@@ -773,13 +805,15 @@ RedisClient.prototype.internalSendCommand = function (commandObj) {
const err = new TypeError('The command contains a argument of type "' + args[i].constructor.name + '" but NodeRedis can only handle strings, numbers, and buffers.') const err = new TypeError('The command contains a argument of type "' + args[i].constructor.name + '" but NodeRedis can only handle strings, numbers, and buffers.')
err.command = command.toUpperCase() err.command = command.toUpperCase()
err.args = args err.args = args
return utils.replyInOrder(this, commandObj.callback, err, undefined, this.commandQueue) utils.replyInOrder(this, commandObj.callback, err, undefined, this.commandQueue)
return commandObj.promise
} }
} else if (typeof args[i] === 'undefined') { } else if (typeof args[i] === 'undefined') {
const err = new TypeError('The command contains a "undefined" argument but NodeRedis can only handle strings, numbers and buffers.') const err = new TypeError('The command contains a "undefined" argument but NodeRedis can only handle strings, numbers and buffers.')
err.command = command.toUpperCase() err.command = command.toUpperCase()
err.args = args err.args = args
return utils.replyInOrder(this, commandObj.callback, err, undefined, this.commandQueue) utils.replyInOrder(this, commandObj.callback, err, undefined, this.commandQueue)
return commandObj.promise
} else { } else {
// Seems like numbers are converted fast using string concatenation // Seems like numbers are converted fast using string concatenation
argsCopy[i] = `${args[i]}` argsCopy[i] = `${args[i]}`
@@ -834,16 +868,14 @@ RedisClient.prototype.internalSendCommand = function (commandObj) {
} else { } else {
// Do not expect a reply // Do not expect a reply
// Does this work in combination with the pub sub mode? // Does this work in combination with the pub sub mode?
if (commandObj.callback) { utils.replyInOrder(this, commandObj.callback, null, undefined, this.commandQueue)
utils.replyInOrder(this, commandObj.callback, null, undefined, this.commandQueue)
}
if (this.reply === 'SKIP') { if (this.reply === 'SKIP') {
this.reply = 'SKIP_ONE_MORE' this.reply = 'SKIP_ONE_MORE'
} else if (this.reply === 'SKIP_ONE_MORE') { } else if (this.reply === 'SKIP_ONE_MORE') {
this.reply = 'ON' this.reply = 'ON'
} }
} }
return return commandObj.promise
} }
RedisClient.prototype.writeStrings = function () { RedisClient.prototype.writeStrings = function () {
@@ -884,7 +916,6 @@ exports.AbortError = errorClasses.AbortError
exports.RedisError = Parser.RedisError exports.RedisError = Parser.RedisError
exports.ParserError = Parser.ParserError exports.ParserError = Parser.ParserError
exports.ReplyError = Parser.ReplyError exports.ReplyError = Parser.ReplyError
exports.AggregateError = errorClasses.AggregateError
// Add all redis commands / nodeRedis api to the client // Add all redis commands / nodeRedis api to the client
require('./lib/individualCommands') require('./lib/individualCommands')

View File

@@ -2,10 +2,30 @@
const betterStackTraces = /development/i.test(process.env.NODE_ENV) || /\bredis\b/i.test(process.env.NODE_DEBUG) const betterStackTraces = /development/i.test(process.env.NODE_ENV) || /\bredis\b/i.test(process.env.NODE_DEBUG)
function Command (command, args, callback, callOnWrite) { // TODO: Change the arguments to an object
// callOnWrite could be two things now
function Command (command, args, callOnWrite, transformer) {
this.command = command this.command = command
this.args = args this.args = args
this.bufferArgs = false this.bufferArgs = false
var callback
transformer = transformer || function (err, res) {
return err || res
}
this.promise = new Promise((resolve, reject) => {
callback = (err, res) => {
if (err) {
const transformed = transformer(err)
if (transformed.stack) { // instanceof Error
reject(transformed)
} else {
resolve(transformed)
}
} else {
resolve(transformer(null, res))
}
}
})
this.callback = callback this.callback = callback
this.callOnWrite = callOnWrite this.callOnWrite = callOnWrite
if (betterStackTraces) { if (betterStackTraces) {

View File

@@ -5,6 +5,8 @@ const Multi = require('./multi')
const RedisClient = require('../').RedisClient const RedisClient = require('../').RedisClient
const Command = require('./command') const Command = require('./command')
const EMPTY_ARRAY = []
// TODO: Rewrite this including the individual commands into a Commands class // TODO: Rewrite this including the individual commands into a Commands class
// that provided a functionality to add new commands to the client // that provided a functionality to add new commands to the client
commands.list.forEach((command) => { commands.list.forEach((command) => {
@@ -15,37 +17,26 @@ commands.list.forEach((command) => {
// Do not override existing functions // Do not override existing functions
if (!RedisClient.prototype[command]) { if (!RedisClient.prototype[command]) {
RedisClient.prototype[command] = function () { RedisClient.prototype[command] = function () {
let arr const len = arguments.length
let len = arguments.length var arr, i
let callback if (len === 0) {
let i = 0 arr = EMPTY_ARRAY
if (Array.isArray(arguments[0])) { } else if (arguments[0].shift) {
arr = arguments[0] arr = arguments[0]
if (len === 2) { } else if (len > 1 && arguments[1].shift) {
callback = arguments[1] const innerLen = arguments[1].length
} arr = new Array(innerLen + 1)
} else if (len > 1 && Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length
arr = new Array(len + 1)
arr[0] = arguments[0] arr[0] = arguments[0]
for (; i < len; i += 1) { for (i = 0; i < innerLen; i += 1) {
arr[i + 1] = arguments[1][i] arr[i + 1] = arguments[1][i]
} }
} else { } else {
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (i = 0; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
} }
} }
return this.internalSendCommand(new Command(command, arr, callback)) return this.internalSendCommand(new Command(command, arr))
} }
if (RedisClient.prototype[command] !== commandName) { if (RedisClient.prototype[command] !== commandName) {
Object.defineProperty(RedisClient.prototype[command], 'name', { Object.defineProperty(RedisClient.prototype[command], 'name', {
@@ -57,37 +48,26 @@ commands.list.forEach((command) => {
// Do not override existing functions // Do not override existing functions
if (!Multi.prototype[command]) { if (!Multi.prototype[command]) {
Multi.prototype[command] = function () { Multi.prototype[command] = function () {
let arr const len = arguments.length
let len = arguments.length var arr, i
let callback if (len === 0) {
let i = 0 arr = EMPTY_ARRAY
if (Array.isArray(arguments[0])) { } else if (arguments[0].shift) {
arr = arguments[0] arr = arguments[0]
if (len === 2) { } else if (len > 1 && arguments[1].shift) {
callback = arguments[1] const innerLen = arguments[1].length
} arr = new Array(innerLen + 1)
} else if (len > 1 && Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length
arr = new Array(len + 1)
arr[0] = arguments[0] arr[0] = arguments[0]
for (; i < len; i += 1) { for (i = 0; i < innerLen; i += 1) {
arr[i + 1] = arguments[1][i] arr[i + 1] = arguments[1][i]
} }
} else { } else {
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (i = 0; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
} }
} }
this.queue.push(new Command(command, arr, callback)) this.queue.push(new Command(command, arr))
return this return this
} }
if (Multi.prototype[command] !== commandName) { if (Multi.prototype[command] !== commandName) {

View File

@@ -5,7 +5,7 @@ const URL = require('url')
module.exports = function createClient (portArg, hostArg, options) { module.exports = function createClient (portArg, hostArg, options) {
if (typeof portArg === 'number' || (typeof portArg === 'string' && /^\d+$/.test(portArg))) { if (typeof portArg === 'number' || (typeof portArg === 'string' && /^\d+$/.test(portArg))) {
let host var host
if (typeof hostArg === 'string') { if (typeof hostArg === 'string') {
host = hostArg host = hostArg
} else { } else {
@@ -40,7 +40,7 @@ module.exports = function createClient (portArg, hostArg, options) {
options.port = parsed.port options.port = parsed.port
} }
if (parsed.search !== '') { if (parsed.search !== '') {
let elem var elem
for (elem in parsed.query) { for (elem in parsed.query) {
// If options are passed twice, only the parsed options will be used // If options are passed twice, only the parsed options will be used
if (elem in options) { if (elem in options) {

View File

@@ -23,37 +23,14 @@ function AbortError (obj, stack) {
} }
} }
function AggregateError (obj) {
assert(obj, 'The options argument is required')
assert.strictEqual(typeof obj, 'object', 'The options argument has to be of type object')
AbortError.call(this, obj, ADD_STACKTRACE)
Object.defineProperty(this, 'message', {
value: obj.message || '',
configurable: true,
writable: true
})
Error.captureStackTrace(this, AggregateError)
for (let keys = Object.keys(obj), key = keys.pop(); key; key = keys.pop()) {
this[key] = obj[key]
}
}
util.inherits(AbortError, RedisError) util.inherits(AbortError, RedisError)
util.inherits(AggregateError, AbortError)
Object.defineProperty(AbortError.prototype, 'name', { Object.defineProperty(AbortError.prototype, 'name', {
value: 'AbortError', value: 'AbortError',
configurable: true, configurable: true,
writable: true writable: true
}) })
Object.defineProperty(AggregateError.prototype, 'name', {
value: 'AggregateError',
configurable: true,
writable: true
})
module.exports = { module.exports = {
AbortError, AbortError
AggregateError
} }

View File

@@ -11,7 +11,9 @@ All documented and exposed API belongs in here
**********************************************/ **********************************************/
// Redirect calls to the appropriate function and use to send arbitrary / not supported commands // Redirect calls to the appropriate function and use to send arbitrary / not supported commands
RedisClient.prototype.sendCommand = function (command, args, callback) { // TODO: REMOVE sendCommand and replace it by a function to add new commands
// TODO: Add a library to add the sendCommand back in place for legacy reasons
RedisClient.prototype.sendCommand = function (command, args) {
// Throw to fail early instead of relying in order in this case // Throw to fail early instead of relying in order in this case
if (typeof command !== 'string') { if (typeof command !== 'string') {
throw new TypeError(`Wrong input type "${command !== null && command !== undefined ? command.constructor.name : command}" for command name`) throw new TypeError(`Wrong input type "${command !== null && command !== undefined ? command.constructor.name : command}" for command name`)
@@ -19,16 +21,10 @@ RedisClient.prototype.sendCommand = function (command, args, callback) {
if (!Array.isArray(args)) { if (!Array.isArray(args)) {
if (args === undefined || args === null) { if (args === undefined || args === null) {
args = [] args = []
} else if (typeof args === 'function' && callback === undefined) {
callback = args
args = []
} else { } else {
throw new TypeError(`Wrong input type "${args.constructor.name}" for args`) throw new TypeError(`Wrong input type "${args.constructor.name}" for args`)
} }
} }
if (typeof callback !== 'function' && callback !== undefined) {
throw new TypeError(`Wrong input type "${callback !== null ? callback.constructor.name : 'null' }" for callback function`)
}
// Using the raw multi command is only possible with this function // Using the raw multi command is only possible with this function
// If the command is not yet added to the client, the internal function should be called right away // If the command is not yet added to the client, the internal function should be called right away
@@ -37,10 +33,7 @@ RedisClient.prototype.sendCommand = function (command, args, callback) {
// but this might change from time to time and at the moment there's no good way to distinguish them // but this might change from time to time and at the moment there's no good way to distinguish them
// from each other, so let's just do it do it this way for the time being // from each other, so let's just do it do it this way for the time being
if (command === 'multi' || typeof this[command] !== 'function') { if (command === 'multi' || typeof this[command] !== 'function') {
return this.internalSendCommand(new Command(command, args, callback)) return this.internalSendCommand(new Command(command, args))
}
if (typeof callback === 'function') {
args = args.concat([callback]) // Prevent manipulating the input array
} }
return this[command].apply(this, args) return this[command].apply(this, args)
} }
@@ -83,6 +76,8 @@ RedisClient.prototype.unref = function () {
} }
} }
// TODO: promisify this
// TODO: the sendCommand legacy module should also make duplicate handle callbacks again
RedisClient.prototype.duplicate = function (options, callback) { RedisClient.prototype.duplicate = function (options, callback) {
if (typeof options === 'function') { if (typeof options === 'function') {
callback = options callback = options

View File

@@ -1,11 +1,9 @@
'use strict' 'use strict'
const utils = require('./utils')
const debug = require('./debug') const debug = require('./debug')
const Multi = require('./multi') const Multi = require('./multi')
const Command = require('./command') const Command = require('./command')
const noPasswordIsSet = /no password is set/ const noPasswordIsSet = /no password is set/
const loading = /LOADING/
const RedisClient = require('../').RedisClient const RedisClient = require('../').RedisClient
/******************************************************************************************** /********************************************************************************************
@@ -32,26 +30,26 @@ RedisClient.prototype.batch = function batch (args) {
return new Multi(this, args) return new Multi(this, args)
} }
function selectCallback (self, db, callback) { function selectCallback (self, db) {
return function (err, res) { return function (err, res) {
if (err === null) { if (err === null) {
// Store db in this.selectDb to restore it on reconnect // Store db in this.selectDb to restore it on reconnect
self.selectedDb = db self.selectedDb = db
} }
utils.callbackOrEmit(self, callback, err, res) return err || res
} }
} }
RedisClient.prototype.select = function select (db, callback) { RedisClient.prototype.select = function select (db) {
return this.internalSendCommand(new Command('select', [db], selectCallback(this, db, callback))) return this.internalSendCommand(new Command('select', [db], null, selectCallback(this, db)))
} }
Multi.prototype.select = function select (db, callback) { Multi.prototype.select = function select (db) {
this.queue.push(new Command('select', [db], selectCallback(this._client, db, callback))) this.queue.push(new Command('select', [db], null, selectCallback(this._client, db)))
return this return this
} }
RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor (callback) { RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor () {
// Use a individual command, as this is a special case that does not has to be checked for any other command // Use a individual command, as this is a special case that does not has to be checked for any other command
const self = this const self = this
const callOnWrite = function () { const callOnWrite = function () {
@@ -59,18 +57,18 @@ RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function monitor
// Therefore we expect the command to be properly processed. If this is not the case, it's not an issue either. // Therefore we expect the command to be properly processed. If this is not the case, it's not an issue either.
self.monitoring = true self.monitoring = true
} }
return this.internalSendCommand(new Command('monitor', [], callback, callOnWrite)) return this.internalSendCommand(new Command('monitor', [], callOnWrite))
} }
// Only works with batch, not in a transaction // Only works with batch, not in a transaction
Multi.prototype.monitor = function monitor (callback) { Multi.prototype.monitor = function monitor () {
// Use a individual command, as this is a special case that does not has to be checked for any other command // Use a individual command, as this is a special case that does not has to be checked for any other command
if (this.exec !== this.execTransaction) { if (this.exec !== this.execTransaction) {
const self = this const self = this
const callOnWrite = function () { const callOnWrite = function () {
self._client.monitoring = true self._client.monitoring = true
} }
this.queue.push(new Command('monitor', [], callback, callOnWrite)) this.queue.push(new Command('monitor', [], callOnWrite))
return this return this
} }
// Set multi monitoring to indicate the exec that it should abort // Set multi monitoring to indicate the exec that it should abort
@@ -79,30 +77,29 @@ Multi.prototype.monitor = function monitor (callback) {
return this return this
} }
function quitCallback (self, callback) { function quitCallback (self) {
return function (err, res) { return function (err, res) {
if (self.stream.writable) {
// If the socket is still alive, kill it. This could happen if quit got a NR_CLOSED error code
self.stream.destroy()
}
if (err && err.code === 'NR_CLOSED') { if (err && err.code === 'NR_CLOSED') {
// Pretend the quit command worked properly in this case. // Pretend the quit command worked properly in this case.
// Either the quit landed in the offline queue and was flushed at the reconnect // Either the quit landed in the offline queue and was flushed at the reconnect
// or the offline queue is deactivated and the command was rejected right away // or the offline queue is deactivated and the command was rejected right away
// or the stream is not writable // or the stream is not writable
// or while sending the quit, the connection ended / closed // or while sending the quit, the connection ended / closed
err = null return 'OK'
res = 'OK'
}
utils.callbackOrEmit(self, callback, err, res)
if (self.stream.writable) {
// If the socket is still alive, kill it. This could happen if quit got a NR_CLOSED error code
self.stream.destroy()
} }
return err || res
} }
} }
RedisClient.prototype.quit = function quit (callback) { RedisClient.prototype.quit = function quit () {
// TODO: Consider this for v.3 // TODO: Consider this for v.3
// Allow the quit command to be fired as soon as possible to prevent it landing in the offline queue. // Allow the quit command to be fired as soon as possible to prevent it landing in the offline queue.
// this.ready = this.offlineQueue.length === 0; // this.ready = this.offlineQueue.length === 0;
const backpressureIndicator = this.internalSendCommand(new Command('quit', [], quitCallback(this, callback))) const backpressureIndicator = this.internalSendCommand(new Command('quit', [], null, quitCallback(this)))
// Calling quit should always end the connection, no matter if there's a connection or not // Calling quit should always end the connection, no matter if there's a connection or not
this.closing = true this.closing = true
this.ready = false this.ready = false
@@ -110,130 +107,115 @@ RedisClient.prototype.quit = function quit (callback) {
} }
// Only works with batch, not in a transaction // Only works with batch, not in a transaction
Multi.prototype.quit = function quit (callback) { Multi.prototype.quit = function quit () {
const self = this._client const self = this._client
const callOnWrite = function () { const callOnWrite = function () {
// If called in a multi context, we expect redis is available // If called in a multi context, we expect redis is available
self.closing = true self.closing = true
self.ready = false self.ready = false
} }
this.queue.push(new Command('quit', [], quitCallback(self, callback), callOnWrite)) this.queue.push(new Command('quit', [], null, quitCallback(self), callOnWrite))
return this return this
} }
function infoCallback (self, callback) { function infoCallback (self) {
return function (err, res) { return function (err, res) {
if (res) { if (err) {
const obj = {} self.serverInfo = {}
const lines = res.toString().split('\r\n') return err
let line, parts, subParts }
for (let i = 0; i < lines.length; i++) { const obj = {}
parts = lines[i].split(':') const lines = res.toString().split('\r\n')
if (parts[1]) { var line, parts, subParts
if (parts[0].indexOf('db') === 0) {
subParts = parts[1].split(',') for (let i = 0; i < lines.length; i++) {
obj[parts[0]] = {} parts = lines[i].split(':')
for (line = subParts.pop(); line !== undefined; line = subParts.pop()) { if (parts[1]) {
line = line.split('=') if (parts[0].indexOf('db') === 0) {
obj[parts[0]][line[0]] = +line[1] subParts = parts[1].split(',')
} obj[parts[0]] = {}
} else { for (line = subParts.pop(); line !== undefined; line = subParts.pop()) {
obj[parts[0]] = parts[1] line = line.split('=')
obj[parts[0]][line[0]] = +line[1]
} }
} else {
obj[parts[0]] = parts[1]
} }
} }
obj.versions = []
if (obj.redis_version) {
obj.redis_version.split('.').forEach((num) => {
obj.versions.push(+num)
})
}
// Expose info key/values to users
self.serverInfo = obj
} else {
self.serverInfo = {}
} }
utils.callbackOrEmit(self, callback, err, res) obj.versions = []
if (obj.redis_version) {
obj.redis_version.split('.').forEach((num) => {
obj.versions.push(+num)
})
}
// Expose info key/values to users
self.serverInfo = obj
return res
} }
} }
// Store info in this.serverInfo after each call // Store info in this.serverInfo after each call
RedisClient.prototype.info = function info (section, callback) { RedisClient.prototype.info = function info (section) {
let args = [] var args = []
if (typeof section === 'function') { if (section !== undefined) {
callback = section
} else if (section !== undefined) {
args = Array.isArray(section) ? section : [section] args = Array.isArray(section) ? section : [section]
} }
return this.internalSendCommand(new Command('info', args, infoCallback(this, callback))) return this.internalSendCommand(new Command('info', args, null, infoCallback(this)))
} }
Multi.prototype.info = function info (section, callback) { Multi.prototype.info = function info (section) {
let args = [] var args = []
if (typeof section === 'function') { if (section !== undefined) {
callback = section
} else if (section !== undefined) {
args = Array.isArray(section) ? section : [section] args = Array.isArray(section) ? section : [section]
} }
this.queue.push(new Command('info', args, infoCallback(this._client, callback))) this.queue.push(new Command('info', args, null, infoCallback(this._client)))
return this return this
} }
function authCallback (self, pass, callback) { function authCallback (self, pass) {
return function (err, res) { return function (err, res) {
if (err) { if (err) {
if (noPasswordIsSet.test(err.message)) { if (noPasswordIsSet.test(err.message)) {
self.warn('Warning: Redis server does not require a password, but a password was supplied.') self.warn('Warning: Redis server does not require a password, but a password was supplied.')
err = null return 'OK' // TODO: Fix this
res = 'OK'
} else if (loading.test(err.message)) {
// If redis is still loading the db, it will not authenticate and everything else will fail
debug('Redis still loading, trying to authenticate later')
setTimeout(() => {
self.auth(pass, callback)
}, 100)
return
} }
return err
} }
utils.callbackOrEmit(self, callback, err, res) return res
} }
} }
RedisClient.prototype.auth = function auth (pass, callback) { RedisClient.prototype.auth = function auth (pass) {
debug(`Sending auth to ${this.address} id ${this.connectionId}`) debug(`Sending auth to ${this.address} id ${this.connectionId}`)
// Stash auth for connect and reconnect. // Stash auth for connect and reconnect.
this.authPass = pass this.authPass = pass
const ready = this.ready const ready = this.ready
this.ready = ready || this.offlineQueue.length === 0 this.ready = ready || this.offlineQueue.length === 0
const tmp = this.internalSendCommand(new Command('auth', [pass], authCallback(this, pass, callback))) const tmp = this.internalSendCommand(new Command('auth', [pass], null, authCallback(this, pass)))
this.ready = ready this.ready = ready
return tmp return tmp
} }
// Only works with batch, not in a transaction // Only works with batch, not in a transaction
Multi.prototype.auth = function auth (pass, callback) { Multi.prototype.auth = function auth (pass) {
debug(`Sending auth to ${this.address} id ${this.connectionId}`) debug(`Sending auth to ${this.address} id ${this.connectionId}`)
// Stash auth for connect and reconnect. // Stash auth for connect and reconnect.
this.authPass = pass this.authPass = pass
this.queue.push(new Command('auth', [pass], authCallback(this._client, callback))) this.queue.push(new Command('auth', [pass], null, authCallback(this._client)))
return this return this
} }
RedisClient.prototype.client = function client () { RedisClient.prototype.client = function client () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0] arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) { } else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length len = arguments[1].length
arr = new Array(len + 1) arr = new Array(len + 1)
arr[0] = arguments[0] arr[0] = arguments[0]
@@ -242,18 +224,13 @@ RedisClient.prototype.client = function client () {
} }
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
} }
} }
const self = this const self = this
let 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 */ /* 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') {
@@ -264,21 +241,16 @@ RedisClient.prototype.client = function client () {
} }
} }
} }
return this.internalSendCommand(new Command('client', arr, callback, callOnWrite)) return this.internalSendCommand(new Command('client', arr, callOnWrite))
} }
Multi.prototype.client = function client () { Multi.prototype.client = function client () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0] arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) { } else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length len = arguments[1].length
arr = new Array(len + 1) arr = new Array(len + 1)
arr[0] = arguments[0] arr[0] = arguments[0]
@@ -287,18 +259,13 @@ Multi.prototype.client = function client () {
} }
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
} }
} }
const self = this._client const self = this._client
let 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 */ /* 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') {
@@ -309,104 +276,75 @@ Multi.prototype.client = function client () {
} }
} }
} }
this.queue.push(new Command('client', arr, callback, callOnWrite)) this.queue.push(new Command('client', arr, callOnWrite))
return this return this
} }
RedisClient.prototype.hmset = function hmset () { RedisClient.prototype.hmset = function hmset () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0] arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) { } else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length len = arguments[1].length
arr = new Array(len + 1) arr = new Array(len + 1)
arr[0] = arguments[0] arr[0] = arguments[0]
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i] arr[i + 1] = arguments[1][i]
} }
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || (arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined')))) { } else if (typeof arguments[1] === 'object' && (arguments.length === 2)) {
arr = [arguments[0]] arr = [arguments[0]]
for (const field in arguments[1]) { for (const field in arguments[1]) {
arr.push(field, arguments[1][field]) arr.push(field, arguments[1][field])
} }
callback = arguments[2]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
} }
} }
return this.internalSendCommand(new Command('hmset', arr, callback)) return this.internalSendCommand(new Command('hmset', arr))
} }
Multi.prototype.hmset = function hmset () { Multi.prototype.hmset = function hmset () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0] arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) { } else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length len = arguments[1].length
arr = new Array(len + 1) arr = new Array(len + 1)
arr[0] = arguments[0] arr[0] = arguments[0]
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i + 1] = arguments[1][i] arr[i + 1] = arguments[1][i]
} }
} else if (typeof arguments[1] === 'object' && (arguments.length === 2 || (arguments.length === 3 && (typeof arguments[2] === 'function' || typeof arguments[2] === 'undefined')))) { } else if (typeof arguments[1] === 'object' && (arguments.length === 2)) {
arr = [arguments[0]] arr = [arguments[0]]
for (const field in arguments[1]) { for (const field in arguments[1]) {
arr.push(field, arguments[1][field]) arr.push(field, arguments[1][field])
} }
callback = arguments[2]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
} }
} }
this.queue.push(new Command('hmset', arr, callback)) this.queue.push(new Command('hmset', arr))
return this return this
} }
RedisClient.prototype.subscribe = function subscribe () { RedisClient.prototype.subscribe = function subscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -416,24 +354,17 @@ RedisClient.prototype.subscribe = function subscribe () {
const callOnWrite = function () { const callOnWrite = function () {
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
return this.internalSendCommand(new Command('subscribe', arr, callback, callOnWrite)) return this.internalSendCommand(new Command('subscribe', arr, callOnWrite))
} }
Multi.prototype.subscribe = function subscribe () { Multi.prototype.subscribe = function subscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -443,25 +374,18 @@ Multi.prototype.subscribe = function subscribe () {
const callOnWrite = function () { const callOnWrite = function () {
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
this.queue.push(new Command('subscribe', arr, callback, callOnWrite)) this.queue.push(new Command('subscribe', arr, callOnWrite))
return this return this
} }
RedisClient.prototype.unsubscribe = function unsubscribe () { RedisClient.prototype.unsubscribe = function unsubscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -472,24 +396,17 @@ RedisClient.prototype.unsubscribe = function unsubscribe () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
return this.internalSendCommand(new Command('unsubscribe', arr, callback, callOnWrite)) return this.internalSendCommand(new Command('unsubscribe', arr, callOnWrite))
} }
Multi.prototype.unsubscribe = function unsubscribe () { Multi.prototype.unsubscribe = function unsubscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -500,25 +417,18 @@ Multi.prototype.unsubscribe = function unsubscribe () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
this.queue.push(new Command('unsubscribe', arr, callback, callOnWrite)) this.queue.push(new Command('unsubscribe', arr, callOnWrite))
return this return this
} }
RedisClient.prototype.psubscribe = function psubscribe () { RedisClient.prototype.psubscribe = function psubscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -528,24 +438,17 @@ RedisClient.prototype.psubscribe = function psubscribe () {
const callOnWrite = function () { const callOnWrite = function () {
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
return this.internalSendCommand(new Command('psubscribe', arr, callback, callOnWrite)) return this.internalSendCommand(new Command('psubscribe', arr, callOnWrite))
} }
Multi.prototype.psubscribe = function psubscribe () { Multi.prototype.psubscribe = function psubscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -555,25 +458,18 @@ Multi.prototype.psubscribe = function psubscribe () {
const callOnWrite = function () { const callOnWrite = function () {
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
this.queue.push(new Command('psubscribe', arr, callback, callOnWrite)) this.queue.push(new Command('psubscribe', arr, callOnWrite))
return this return this
} }
RedisClient.prototype.punsubscribe = function punsubscribe () { RedisClient.prototype.punsubscribe = function punsubscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -584,24 +480,17 @@ RedisClient.prototype.punsubscribe = function punsubscribe () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
return this.internalSendCommand(new Command('punsubscribe', arr, callback, callOnWrite)) return this.internalSendCommand(new Command('punsubscribe', arr, callOnWrite))
} }
Multi.prototype.punsubscribe = function punsubscribe () { Multi.prototype.punsubscribe = function punsubscribe () {
let arr var arr
let len = arguments.length var len = arguments.length
let callback var i = 0
let i = 0
if (Array.isArray(arguments[0])) { if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0) arr = arguments[0].slice(0)
callback = arguments[1]
} else { } else {
len = arguments.length len = arguments.length
// The later should not be the average use case
if (len !== 0 && (typeof arguments[len - 1] === 'function' || typeof arguments[len - 1] === 'undefined')) {
len--
callback = arguments[len]
}
arr = new Array(len) arr = new Array(len)
for (; i < len; i += 1) { for (; i < len; i += 1) {
arr[i] = arguments[i] arr[i] = arguments[i]
@@ -612,6 +501,6 @@ Multi.prototype.punsubscribe = function punsubscribe () {
// Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback // Pub sub has to be activated even if not in pub sub mode, as the return value is manipulated in the callback
self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1 self.pubSubMode = self.pubSubMode || self.commandQueue.length + 1
} }
this.queue.push(new Command('punsubscribe', arr, callback, callOnWrite)) this.queue.push(new Command('punsubscribe', arr, callOnWrite))
return this return this
} }

View File

@@ -4,10 +4,12 @@ const Queue = require('double-ended-queue')
const utils = require('./utils') const utils = require('./utils')
const Command = require('./command') const Command = require('./command')
// TODO: Remove support for the non chaining way of using this
// It's confusing and has no benefit
function Multi (client, args) { function Multi (client, args) {
this._client = client this._client = client
this.queue = new Queue() this.queue = new Queue()
let command, tmpArgs var command, tmpArgs
if (args) { // Either undefined or an array. Fail hard if it's not an array if (args) { // Either undefined or an array. Fail hard if it's not an array
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
command = args[i][0] command = args[i][0]
@@ -25,163 +27,116 @@ function pipelineTransactionCommand (self, commandObj, index) {
// Queueing is done first, then the commands are executed // Queueing is done first, then the commands are executed
const tmp = commandObj.callback const tmp = commandObj.callback
commandObj.callback = function (err, reply) { commandObj.callback = function (err, reply) {
// Ignore the multi command. This is applied by nodeRedis and the user does not benefit by it if (err) {
if (err && index !== -1) { tmp(err)
if (tmp) {
tmp(err)
}
err.position = index err.position = index
self.errors.push(err) self.errors.push(err)
return
} }
// Keep track of who wants buffer responses: // Keep track of who wants buffer responses:
// By the time the callback is called the commandObj got the bufferArgs attribute attached // By the time the callback is called the commandObj got the bufferArgs attribute attached
self.wantsBuffers[index] = commandObj.bufferArgs self.wantsBuffers[index] = commandObj.bufferArgs
commandObj.callback = tmp tmp(null, reply)
} }
self._client.internalSendCommand(commandObj) return self._client.internalSendCommand(commandObj)
} }
Multi.prototype.execAtomic = function execAtomic (callback) { Multi.prototype.execAtomic = function execAtomic () {
if (this.queue.length < 2) { if (this.queue.length < 2) {
return this.execBatch(callback) return this.execBatch()
} }
return this.exec(callback) return this.exec()
} }
function multiCallback (self, err, replies) { function multiCallback (self, replies) {
let i = 0 var i = 0
if (err) {
err.errors = self.errors
if (self.callback) {
self.callback(err)
// Exclude connection errors so that those errors won't be emitted twice
} else if (err.code !== 'CONNECTION_BROKEN') {
self._client.emit('error', err)
}
return
}
if (replies) { if (replies) {
for (let commandObj = self.queue.shift(); commandObj !== undefined; commandObj = self.queue.shift()) { for (let commandObj = self.queue.shift(); commandObj !== undefined; commandObj = self.queue.shift()) {
if (replies[i] instanceof Error) { if (replies[i].message) { // instanceof Error
const match = replies[i].message.match(utils.errCode) const match = replies[i].message.match(utils.errCode)
// LUA script could return user errors that don't behave like all other errors! // LUA script could return user errors that don't behave like all other errors!
if (match) { if (match) {
replies[i].code = match[1] replies[i].code = match[1]
} }
replies[i].command = commandObj.command.toUpperCase() replies[i].command = commandObj.command.toUpperCase()
if (typeof commandObj.callback === 'function') { commandObj.callback(replies[i])
commandObj.callback(replies[i])
}
} else { } else {
// If we asked for strings, even in detectBuffers mode, then return strings: // If we asked for strings, even in detectBuffers mode, then return strings:
replies[i] = self._client.handleReply(replies[i], commandObj.command, self.wantsBuffers[i]) replies[i] = self._client.handleReply(replies[i], commandObj.command, self.wantsBuffers[i])
if (typeof commandObj.callback === 'function') { commandObj.callback(null, replies[i])
commandObj.callback(null, replies[i])
}
} }
i++ i++
} }
} }
if (self.callback) { return replies
self.callback(null, replies)
}
} }
Multi.prototype.execTransaction = function execTransaction (callback) { Multi.prototype.execTransaction = function execTransaction () {
if (this.monitoring || this._client.monitoring) { if (this.monitoring || this._client.monitoring) {
const err = new RangeError( const err = new RangeError(
'Using transaction with a client that is in monitor mode does not work due to faulty return values of Redis.' 'Using transaction with a client that is in monitor mode does not work due to faulty return values of Redis.'
) )
err.command = 'EXEC' err.command = 'EXEC'
err.code = 'EXECABORT' err.code = 'EXECABORT'
return utils.replyInOrder(this._client, callback, err) return new Promise((resolve, reject) => {
utils.replyInOrder(this._client, (err, res) => {
if (err) return reject(err)
resolve(res)
}, null, [])
})
} }
const self = this const len = this.queue.length
const len = self.queue.length this.errors = []
self.errors = [] this._client.cork()
self.callback = callback this.wantsBuffers = new Array(len)
self._client.cork() // Silently ignore this error. We'll receive the error for the exec as well
self.wantsBuffers = new Array(len) const promises = [this._client.internalSendCommand(new Command('multi', [])).catch(() => {})]
pipelineTransactionCommand(self, new Command('multi', []), -1)
// Drain queue, callback will catch 'QUEUED' or error // Drain queue, callback will catch 'QUEUED' or error
for (let index = 0; index < len; index++) { for (let index = 0; index < len; index++) {
// The commands may not be shifted off, since they are needed in the result handler // The commands may not be shifted off, since they are needed in the result handler
pipelineTransactionCommand(self, self.queue.get(index), index) promises.push(pipelineTransactionCommand(this, this.queue.get(index), index).catch((e) => e))
} }
self._client.internalSendCommand(new Command('exec', [], (err, replies) => { const main = this._client.internalSendCommand(new Command('exec', []))
multiCallback(self, err, replies) this._client.uncork()
}))
self._client.uncork()
return
}
function batchCallback (self, cb, i) {
return function batchCallback (err, res) {
if (err) {
self.results[i] = err
// Add the position to the error
self.results[i].position = i
} else {
self.results[i] = res
}
cb(err, res)
}
}
Multi.prototype.exec = Multi.prototype.execBatch = function execBatch (callback) {
const self = this const self = this
const len = self.queue.length return Promise.all(promises).then(() => main.then((replies) => multiCallback(self, replies)).catch((err) => {
let index = 0 err.errors = self.errors
let commandObj return Promise.reject(err)
if (len === 0) { }))
utils.replyInOrder(self._client, callback, null, []) }
return
Multi.prototype.exec = Multi.prototype.execBatch = function execBatch () {
if (this.queue.length === 0) {
// TODO: return an error if not "ready"
return new Promise((resolve) => {
utils.replyInOrder(this._client, (e, res) => {
resolve(res)
}, null, [])
})
} }
self._client.cork() var error = false
if (!callback) { this._client.cork()
for (commandObj = self.queue.shift(); commandObj !== undefined; commandObj = self.queue.shift()) { const promises = []
self._client.internalSendCommand(commandObj) while (this.queue.length) {
} const commandObj = this.queue.shift()
self._client.uncork() promises.push(this._client.internalSendCommand(commandObj).catch((e) => {
return error = true
return e
}))
} }
const callbackWithoutOwnCb = function (err, res) { this._client.uncork()
if (err) { return Promise.all(promises).then((res) => {
self.results.push(err) if (error) {
// Add the position to the error const err = new Error('bla failed')
const i = self.results.length - 1 err.code = 'foo'
self.results[i].position = i err.replies = res
} else { return Promise.reject(err)
self.results.push(res)
} }
// Do not emit an error here. Otherwise each error would result in one emit. return res
// The errors will be returned in the result anyway })
}
const lastCallback = function (cb) {
return function (err, res) {
cb(err, res)
callback(null, self.results)
}
}
self.results = []
for (commandObj = self.queue.shift(); commandObj !== undefined; commandObj = self.queue.shift()) {
if (typeof commandObj.callback === 'function') {
commandObj.callback = batchCallback(self, commandObj.callback, index)
} else {
commandObj.callback = callbackWithoutOwnCb
}
if (typeof callback === 'function' && index === len - 1) {
commandObj.callback = lastCallback(commandObj.callback)
}
this._client.internalSendCommand(commandObj)
index++
}
self._client.uncork()
return
} }
module.exports = Multi module.exports = Multi

View File

@@ -15,10 +15,13 @@ function replyToObject (reply) {
} }
function replyToStrings (reply) { function replyToStrings (reply) {
if (reply instanceof Buffer) { if (reply === null) {
return null
}
if (typeof reply.inspect === 'function') { // instanceof Buffer
return reply.toString() return reply.toString()
} }
if (reply instanceof Array) { if (typeof reply.map === 'function') { // instanceof Array
const res = new Array(reply.length) const res = new Array(reply.length)
for (let i = 0; i < reply.length; i++) { for (let i = 0; i < reply.length; i++) {
// Recursively call the function as slowlog returns deep nested replies // Recursively call the function as slowlog returns deep nested replies
@@ -33,7 +36,7 @@ function replyToStrings (reply) {
// Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error) // Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error)
// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions) // Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
function clone (obj) { function clone (obj) {
let copy var copy
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
copy = new Array(obj.length) copy = new Array(obj.length)
for (let i = 0; i < obj.length; i++) { for (let i = 0; i < obj.length; i++) {
@@ -56,18 +59,10 @@ function convenienceClone (obj) {
return clone(obj) || {} return clone(obj) || {}
} }
function callbackOrEmit (self, callback, err, res) {
if (callback) {
callback(err, res)
} else if (err) {
self.emit('error', err)
}
}
function replyInOrder (self, callback, err, res, queue) { function replyInOrder (self, callback, err, res, queue) {
// If the queue is explicitly passed, use that, otherwise fall back to the offline queue first, // If the queue is explicitly passed, use that, otherwise fall back to the offline queue first,
// as there might be commands in both queues at the same time // as there might be commands in both queues at the same time
let commandObj var commandObj
if (queue) { if (queue) {
commandObj = queue.peekBack() commandObj = queue.peekBack()
} else { } else {
@@ -75,21 +70,15 @@ function replyInOrder (self, callback, err, res, queue) {
} }
if (!commandObj) { if (!commandObj) {
process.nextTick(() => { process.nextTick(() => {
callbackOrEmit(self, callback, err, res) callback(err, res)
}) })
} else { } else {
// TODO: Change this to chain promises instead
const tmp = commandObj.callback const tmp = commandObj.callback
commandObj.callback = tmp commandObj.callback = function (e, r) {
? function (e, r) { tmp(e, r)
tmp(e, r) callback(err, res)
callbackOrEmit(self, callback, err, res) }
}
: function (e) {
if (e) {
self.emit('error', e)
}
callbackOrEmit(self, callback, err, res)
}
} }
} }
@@ -99,6 +88,5 @@ module.exports = {
errCode: /^([A-Z]+)\s+(.+)$/, errCode: /^([A-Z]+)\s+(.+)$/,
monitorRegex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]+ .+]( ".+?")+$/, monitorRegex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]+ .+]( ".+?")+$/,
clone: convenienceClone, clone: convenienceClone,
callbackOrEmit,
replyInOrder replyInOrder
} }

View File

@@ -23,7 +23,7 @@
"test": "nyc --cache mocha ./test/*.js ./test/commands/*.js --timeout=8000", "test": "nyc --cache mocha ./test/*.js ./test/commands/*.js --timeout=8000",
"posttest": "npm run coverage", "posttest": "npm run coverage",
"compare": "node benchmarks/diff_multi_bench_output.js beforeBench.txt afterBench.txt", "compare": "node benchmarks/diff_multi_bench_output.js beforeBench.txt afterBench.txt",
"lint": "standard --fix" "lint": "eslint . --fix"
}, },
"dependencies": { "dependencies": {
"double-ended-queue": "^2.1.0-0", "double-ended-queue": "^2.1.0-0",
@@ -35,7 +35,6 @@
"node": ">=6" "node": ">=6"
}, },
"devDependencies": { "devDependencies": {
"bluebird": "^3.0.2",
"coveralls": "^2.11.2", "coveralls": "^2.11.2",
"intercept-stdout": "~0.1.2", "intercept-stdout": "~0.1.2",
"metrics": "^0.1.9", "metrics": "^0.1.9",

View File

@@ -30,81 +30,42 @@ if (process.platform !== 'win32') {
client.end(false) client.end(false)
}) })
it('allows auth to be provided with \'auth\' method', function (done) { it('allows auth to be provided with \'auth\' method', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.auth(auth, (err, res) => { return client.auth(auth).then(helper.isString('OK'))
assert.strictEqual(null, err)
assert.strictEqual('OK', res.toString())
return done(err)
})
}) })
it('support redis 2.4 with retrying auth commands if still loading', function (done) { it('returns error when auth is bad', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
const time = Date.now() client.on('error', helper.isError(/Ready check failed: NOAUTH Authentication required./))
client.auth(auth, (err, res) => { return client.auth(`${auth}bad`).then(assert, (err) => {
assert.strictEqual(err, null)
assert.strictEqual('retry worked', res)
const now = Date.now()
// Hint: setTimeout sometimes triggers early and therefore the value can be like one or two ms to early
assert(now - time >= 98, `Time should be above 100 ms (the reconnect time) and is ${now - time}`)
assert(now - time < 225, `Time should be below 255 ms (the reconnect should only take a bit above 100 ms) and is ${now - time}`)
done()
})
const tmp = client.commandQueue.get(0).callback
client.commandQueue.get(0).callback = function (err) {
assert.strictEqual(err, null)
client.auth = function (pass, callback) {
callback(null, 'retry worked')
}
tmp(new Error('ERR redis is still LOADING'))
}
})
it('emits error when auth is bad without callback', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip()
client = redis.createClient.apply(null, args)
client.once('error', (err) => {
assert.strictEqual(err.command, 'AUTH') assert.strictEqual(err.command, 'AUTH')
assert.ok(/ERR invalid password/.test(err.message)) assert.ok(/ERR invalid password/.test(err.message))
return done()
}) })
client.auth(`${auth}bad`)
}) })
it('returns an error when auth is bad (empty string) with a callback', function (done) { it('returns an error when auth is bad (empty string)', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.on('error', helper.isError(/Ready check failed: NOAUTH Authentication required./))
client.auth('', (err) => { return client.auth('').then(helper.fail).catch((err) => {
assert.strictEqual(err.command, 'AUTH') assert.strictEqual(err.command, 'AUTH')
assert.ok(/ERR invalid password/.test(err.message)) assert.ok(/ERR invalid password/.test(err.message))
done()
}) })
}) })
if (ip === 'IPv4') { if (ip === 'IPv4') {
it('allows auth to be provided as part of redis url and do not fire commands before auth is done', function (done) { it('allows auth to be provided as part of redis url and do not fire commands before auth is done', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
const end = helper.callFuncAfter(done, 2)
client = redis.createClient(`redis://:${auth}@${config.HOST[ip]}:${config.PORT}`) client = redis.createClient(`redis://:${auth}@${config.HOST[ip]}:${config.PORT}`)
client.on('ready', () => { // The info command may be used while loading but not if not yet authenticated
end() return client.info()
})
// The info command may be used while loading but not if not yet authenticated
client.info((err) => {
assert.strictEqual(err, null)
end(err)
})
}) })
it('allows auth and database to be provided as part of redis url query parameter', function (done) { it('allows auth and database to be provided as part of redis url query parameter', function (done) {
@@ -115,16 +76,17 @@ if (process.platform !== 'win32') {
assert.strictEqual(client.options.password, auth) assert.strictEqual(client.options.password, auth)
assert.strictEqual(client.authPass, auth) assert.strictEqual(client.authPass, auth)
client.on('ready', () => { client.on('ready', () => {
// Set a key so the used database is returned in the info command const promises = []
client.set('foo', 'bar') // Set a key so the used database is returned in the info command
client.get('foo') promises.push(client.set('foo', 'bar'))
promises.push(client.get('foo'))
assert.strictEqual(client.serverInfo.db2, undefined) assert.strictEqual(client.serverInfo.db2, undefined)
// Using the info command should update the serverInfo // Using the info command should update the serverInfo
client.info((err) => { promises.push(client.info().then(() => {
assert.strictEqual(err, null)
assert(typeof client.serverInfo.db2 === 'object') assert(typeof client.serverInfo.db2 === 'object')
}) }))
client.flushdb(done) promises.push(client.flushdb())
return Promise.all(promises).then(() => done())
}) })
}) })
} }
@@ -155,7 +117,7 @@ if (process.platform !== 'win32') {
const args = config.configureClient(ip) const args = config.configureClient(ip)
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.auth(auth) client.auth(auth).catch(done)
client.on('ready', done) client.on('ready', done)
}) })
@@ -163,7 +125,7 @@ if (process.platform !== 'win32') {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.auth(auth) client.auth(auth).catch(done)
client.on('ready', function () { client.on('ready', function () {
if (this.timesConnected < 3) { if (this.timesConnected < 3) {
let interval = setInterval(() => { let interval = setInterval(() => {
@@ -173,8 +135,8 @@ if (process.platform !== 'win32') {
clearInterval(interval) clearInterval(interval)
interval = null interval = null
client.stream.destroy() client.stream.destroy()
client.set('foo', 'bar') client.set('foo', 'bar').catch(done)
client.get('foo') // Errors would bubble client.get('foo').catch(done)
assert.strictEqual(client.offlineQueue.length, 2) assert.strictEqual(client.offlineQueue.length, 2)
}, 1) }, 1)
} else { } else {
@@ -190,11 +152,11 @@ if (process.platform !== 'win32') {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
const args = config.configureClient(ip, { const args = config.configureClient(ip, {
authPass: auth password: auth
}) })
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.on('ready', () => { client.on('ready', () => {
client.auth(auth, helper.isString('OK', done)) client.auth(auth).then(helper.isString('OK')).then(done).catch(done)
}) })
}) })
@@ -206,7 +168,7 @@ if (process.platform !== 'win32') {
}) })
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.on('ready', () => { client.on('ready', () => {
client.set('foo', 'bar', (err) => { client.set('foo', 'bar').catch((err) => {
assert.strictEqual(err.message, 'NOAUTH Authentication required.') assert.strictEqual(err.message, 'NOAUTH Authentication required.')
assert.strictEqual(err.code, 'NOAUTH') assert.strictEqual(err.code, 'NOAUTH')
assert.strictEqual(err.command, 'SET') assert.strictEqual(err.command, 'SET')
@@ -245,14 +207,11 @@ if (process.platform !== 'win32') {
}) })
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.set('foo', 'bar') client.set('foo', 'bar')
client.subscribe('somechannel', 'another channel', (err) => { client.subscribe('somechannel', 'another channel').then(() => {
assert.strictEqual(err, null) assert.strictEqual(client.pubSubMode, 1)
client.once('ready', () => { client.get('foo').catch((err) => {
assert.strictEqual(client.pubSubMode, 1) assert(/ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message))
client.get('foo', (err) => { done()
assert(/ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message))
done()
})
}) })
}) })
client.once('ready', () => { client.once('ready', () => {
@@ -282,27 +241,17 @@ if (process.platform !== 'win32') {
}) })
client.batch() client.batch()
.auth(auth) .auth(auth)
.select(5, (err, res) => { .select(5)
assert.strictEqual(err, null)
assert.strictEqual(client.selectedDb, 5)
assert.strictEqual(res, 'OK')
assert.notDeepEqual(client.serverInfo.db5, { avgTtl: 0, expires: 0, keys: 1 })
})
.monitor() .monitor()
.set('foo', 'bar', helper.isString('OK')) .set('foo', 'bar')
.info('stats', (err, res) => { .info('stats')
assert.strictEqual(err, null) .get('foo')
assert.strictEqual(res.indexOf('# Stats\r\n'), 0) .subscribe(['foo', 'bar', 'foo'])
assert.strictEqual(client.serverInfo.sync_full, '0')
})
.get('foo', helper.isString('bar'))
.subscribe(['foo', 'bar', 'foo'], helper.isDeepEqual([2, ['foo', 'bar', 'foo']]))
.unsubscribe('foo') .unsubscribe('foo')
.subscribe('/foo', helper.isDeepEqual([2, ['/foo']])) .subscribe('/foo')
.psubscribe('*') .psubscribe('*')
.quit(helper.isString('OK')) .quit()
.exec((err, res) => { .exec().then((res) => {
assert.strictEqual(err, null)
res[4] = res[4].substr(0, 9) res[4] = res[4].substr(0, 9)
assert.deepStrictEqual( assert.deepStrictEqual(
res, res,

View File

@@ -11,36 +11,21 @@ describe('The \'batch\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('connect', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('returns an empty array for missing commands', (done) => { it('returns an empty array for missing commands', () => {
const batch = client.batch() const batch = client.batch()
batch.exec((err, res) => { return batch.exec().then(helper.isDeepEqual([]))
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0)
done()
})
}) })
it('returns an error for batch with commands', (done) => { it('returns an error for batch with commands', () => {
const batch = client.batch() const batch = client.batch()
batch.set('foo', 'bar') batch.set('foo', 'bar')
batch.exec((err, res) => { return batch.exec().then(helper.fail).catch((err) => {
assert.strictEqual(err, null) assert.strictEqual(err.replies[0].code, 'NR_CLOSED')
assert.strictEqual(res[0].code, 'NR_CLOSED')
done()
})
})
it('returns an empty array for missing commands if promisified', () => {
return client.batch().execAsync().then((res) => {
assert.strictEqual(res.length, 0)
}) })
}) })
}) })
@@ -48,122 +33,85 @@ describe('The \'batch\' method', () => {
describe('when connected', () => { describe('when connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb((err) => {
return done(err)
})
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('returns an empty array and keep the execution order in tact', (done) => { it('returns an empty array and keep the execution order in tact', () => {
let called = false let called = false
client.set('foo', 'bar', () => { client.set('foo', 'bar').then(() => {
called = true called = true
}) })
const batch = client.batch() const batch = client.batch()
batch.exec((err, res) => { return batch.exec().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0) assert.strictEqual(res.length, 0)
assert(called) assert(called)
done()
}) })
}) })
it('runs normal calls in-between batch', (done) => { it('runs normal calls in-between batch', () => {
const batch = client.batch() const batch = client.batch()
batch.set('m1', '123') batch.set('m1', '123')
client.set('m2', '456', done) return client.set('m2', '456')
}) })
it('returns an empty array if promisified', () => { it('returns an empty result array', () => {
return client.batch().execAsync().then((res) => {
assert.strictEqual(res.length, 0)
})
})
it('returns an empty result array', (done) => {
const batch = client.batch() const batch = client.batch()
let async = true let async = true
batch.exec((err, res) => { const promise = batch.exec().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0) assert.strictEqual(res.length, 0)
async = false async = false
done()
}) })
assert(async) assert(async)
return promise
}) })
it('fail individually when one command fails using chaining notation', (done) => { it('fail individually when one command fails using chaining notation', () => {
const batch1 = client.batch() const batch1 = client.batch()
batch1.mset('batchfoo', '10', 'batchbar', '20', helper.isString('OK')) batch1.mset('batchfoo', '10', 'batchbar', '20')
// Provoke an error at queue time
batch1.set('foo2', helper.isError())
batch1.incr('batchfoo')
batch1.incr('batchbar')
batch1.exec(() => {
// Confirm that the previous command, while containing an error, still worked.
const batch2 = client.batch()
batch2.get('foo2', helper.isNull())
batch2.incr('batchbar', helper.isNumber(22))
batch2.incr('batchfoo', helper.isNumber(12))
batch2.exec((err, replies) => {
assert.strictEqual(err, null)
assert.strictEqual(null, replies[0])
assert.strictEqual(22, replies[1])
assert.strictEqual(12, replies[2])
return done()
})
})
})
it('fail individually when one command fails and emit the error if no callback has been provided', (done) => {
client.on('error', (err) => {
done(err)
})
const batch1 = client.batch()
batch1.mset('batchfoo', '10', 'batchbar', '20', helper.isString('OK'))
// Provoke an error at queue time // Provoke an error at queue time
batch1.set('foo2') batch1.set('foo2')
batch1.incr('batchfoo') batch1.incr('batchfoo')
batch1.incr('batchbar') batch1.incr('batchbar')
batch1.exec((err, res) => { return batch1.exec().then(helper.fail).catch(() => {
// TODO: This should actually return an error! // Confirm that the previous command, while containing an error, still worked.
assert.strictEqual(err, null) const batch2 = client.batch()
assert.strictEqual(res[1].command, 'SET') batch2.get('foo2')
assert.strictEqual(res[1].code, 'ERR') batch2.incr('batchbar')
done() batch2.incr('batchfoo')
return batch2.exec().then((replies) => {
assert.strictEqual(null, replies[0])
assert.strictEqual(22, replies[1])
assert.strictEqual(12, replies[2])
})
}) })
}) })
it('fail individually when one command in an array of commands fails', (done) => { it('fail individually when one command in an array of commands fails', () => {
// test nested batch-bulk replies // test nested batch-bulk replies
client.batch([ return client.batch([
['mget', 'batchfoo', 'batchbar', helper.isDeepEqual([null, null])], ['mget', 'batchfoo', 'batchbar'],
['set', 'foo2', helper.isError()], ['set', 'foo2'],
['incr', 'batchfoo'], ['incr', 'batchfoo'],
['incr', 'batchbar'] ['incr', 'batchbar']
]).exec((err, replies) => { ]).exec().then(helper.fail).catch((err) => {
// TODO: This should actually return an error! const replies = err.replies
assert.strictEqual(err, null)
assert.strictEqual(2, replies[0].length) assert.strictEqual(2, replies[0].length)
assert.strictEqual(null, replies[0][0]) assert.strictEqual(null, replies[0][0])
assert.strictEqual(null, replies[0][1]) assert.strictEqual(null, replies[0][1])
assert.strictEqual('SET', replies[1].command) assert.strictEqual('SET', replies[1].command)
assert.strictEqual('1', replies[2].toString()) assert.strictEqual('1', replies[2].toString())
assert.strictEqual('1', replies[3].toString()) assert.strictEqual('1', replies[3].toString())
return done()
}) })
}) })
it('handles multiple operations being applied to a set', (done) => { it('handles multiple operations being applied to a set', () => {
client.sadd('some set', 'mem 1') client.sadd('some set', 'mem 1')
client.sadd(['some set', 'mem 2']) client.sadd(['some set', 'mem 2'])
client.sadd('some set', 'mem 3') client.sadd('some set', 'mem 3')
@@ -171,60 +119,51 @@ describe('The \'batch\' method', () => {
// make sure empty mb reply works // make sure empty mb reply works
client.del('some missing set') client.del('some missing set')
client.smembers('some missing set', (err, reply) => { client.smembers('some missing set').then((reply) => {
assert.strictEqual(err, null)
// make sure empty mb reply works // make sure empty mb reply works
assert.strictEqual(0, reply.length) assert.strictEqual(0, reply.length)
}) })
// test nested batch-bulk replies with empty mb elements. // test nested batch-bulk replies with empty mb elements.
client.batch([ return client.batch([
['smembers', ['some set']], ['smembers', ['some set']],
['del', 'some set'], ['del', 'some set'],
['smembers', 'some set', undefined] // The explicit undefined is handled as a callback that is undefined ['smembers', 'some set']
]) ])
.scard('some set') .scard('some set')
.exec((err, replies) => { .exec().then((replies) => {
assert.strictEqual(err, null)
assert.strictEqual(4, replies[0].length) assert.strictEqual(4, replies[0].length)
assert.strictEqual(0, replies[2].length) assert.strictEqual(0, replies[2].length)
return done()
}) })
}) })
it('allows multiple operations to be performed using constructor with all kinds of syntax', (done) => { it('allows multiple operations to be performed using constructor with all kinds of syntax', () => {
const now = Date.now() const now = Date.now()
const arr = ['batchhmset', 'batchbar', 'batchbaz'] const arr = ['batchhmset', 'batchbar', 'batchbaz']
const arr2 = ['some manner of key', 'otherTypes'] const arr2 = ['some manner of key', 'otherTypes']
const arr3 = [5768, 'batchbarx', 'batchfoox'] const arr3 = [5768, 'batchbarx', 'batchfoox']
const arr4 = ['mset', [578, 'batchbar'], helper.isString('OK')] const arr4 = ['mset', [578, 'batchbar']]
client.batch([ return client.batch([
arr4, arr4,
[['mset', 'batchfoo2', 'batchbar2', 'batchfoo3', 'batchbar3'], helper.isString('OK')], [['mset', 'batchfoo2', 'batchbar2', 'batchfoo3', 'batchbar3']],
['hmset', arr], ['hmset', arr],
[['hmset', 'batchhmset2', 'batchbar2', 'batchfoo3', 'batchbar3', 'test'], helper.isString('OK')], [['hmset', 'batchhmset2', 'batchbar2', 'batchfoo3', 'batchbar3', 'test']],
['hmset', ['batchhmset', 'batchbar', 'batchfoo'], helper.isString('OK')], ['hmset', ['batchhmset', 'batchbar', 'batchfoo']],
['hmset', arr3, helper.isString('OK')], ['hmset', arr3],
['hmset', now, {123456789: 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}], ['hmset', now, {123456789: 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}],
['hmset', 'key2', {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 999}, helper.isString('OK')], ['hmset', 'key2', {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 999}],
['hmset', 'batchhmset', ['batchbar', 'batchbaz']], ['hmset', 'batchhmset', ['batchbar', 'batchbaz']],
['hmset', 'batchhmset', ['batchbar', 'batchbaz'], helper.isString('OK')] ['hmset', 'batchhmset', ['batchbar', 'batchbaz']]
]) ])
.hmget(now, 123456789, 'otherTypes') .hmget(now, 123456789, 'otherTypes')
.hmget('key2', arr2, () => {}) .hmget('key2', arr2)
.hmget(['batchhmset2', 'some manner of key', 'batchbar3']) .hmget(['batchhmset2', 'some manner of key', 'batchbar3'])
.mget('batchfoo2', ['batchfoo3', 'batchfoo'], (err, res) => { .mget('batchfoo2', ['batchfoo3', 'batchfoo'])
assert.strictEqual(err, null) .exec().then((replies) => {
assert.strictEqual(res[0], 'batchbar2')
assert.strictEqual(res[1], 'batchbar3')
assert.strictEqual(res[2], null)
})
.exec((err, replies) => {
assert.strictEqual(arr.length, 3) assert.strictEqual(arr.length, 3)
assert.strictEqual(arr2.length, 2) assert.strictEqual(arr2.length, 2)
assert.strictEqual(arr3.length, 3) assert.strictEqual(arr3.length, 3)
assert.strictEqual(arr4.length, 3) assert.strictEqual(arr4.length, 2)
assert.strictEqual(null, err)
assert.strictEqual(replies[10][1], '555') assert.strictEqual(replies[10][1], '555')
assert.strictEqual(replies[11][0], 'a type of value') assert.strictEqual(replies[11][0], 'a type of value')
assert.strictEqual(replies[12][0], null) assert.strictEqual(replies[12][0], null)
@@ -232,93 +171,62 @@ describe('The \'batch\' method', () => {
assert.strictEqual(replies[13][0], 'batchbar2') assert.strictEqual(replies[13][0], 'batchbar2')
assert.strictEqual(replies[13].length, 3) assert.strictEqual(replies[13].length, 3)
assert.strictEqual(replies.length, 14) assert.strictEqual(replies.length, 14)
return done()
}) })
}) })
it('converts a non string key to a string', (done) => { it('converts a non string key to a string', () => {
// TODO: Converting the key might change soon again. // TODO: Converting the key might change soon again.
client.batch().hmset(true, { return client.batch().hmset(true, {
test: 123, test: 123,
bar: 'baz' bar: 'baz'
}).exec(done) }).exec()
}) })
it('runs a batch without any further commands', (done) => { it('runs a batch without any further commands', () => {
client.batch().exec((err, res) => { return client.batch().exec().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0) assert.strictEqual(res.length, 0)
done()
}) })
}) })
it('runs a batch without any further commands and without callback', () => { it('allows multiple operations to be performed using a chaining API', () => {
client.batch().exec() return client.batch()
})
it('allows multiple operations to be performed using a chaining API', (done) => {
client.batch()
.mset('some', '10', 'keys', '20') .mset('some', '10', 'keys', '20')
.incr('some') .incr('some')
.incr('keys') .incr('keys')
.mget('some', 'keys') .mget('some', 'keys')
.exec(helper.isDeepEqual(['OK', 11, 21, ['11', '21']], done)) .exec().then(helper.isDeepEqual(['OK', 11, 21, ['11', '21']]))
}) })
it('allows multiple commands to work the same as normal to be performed using a chaining API', (done) => { it('allows multiple commands to work the same as normal to be performed using a chaining API', () => {
client.batch()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.mget('some', 'keys')
.exec(helper.isDeepEqual(['OK', 11, 21, ['11', '21']], done))
})
it('allows multiple commands to work the same as normal to be performed using a chaining API promisified', () => {
return client.batch() return client.batch()
.mset(['some', '10', 'keys', '20']) .mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11)) .incr('some')
.incr(['keys'], helper.isNumber(21)) .incr(['keys'])
.mget('some', 'keys') .mget('some', 'keys')
.execAsync() .exec().then(helper.isDeepEqual(['OK', 11, 21, ['11', '21']]))
.then((res) => {
helper.isDeepEqual(['OK', 11, 21, ['11', '21']])(null, res)
})
}) })
it('allows an array to be provided indicating multiple operations to perform', (done) => { it('allows an array to be provided indicating multiple operations to perform', () => {
// test nested batch-bulk replies with nulls. // test nested batch-bulk replies with nulls.
client.batch([ return client.batch([
['mget', ['batchfoo', 'some', 'random value', 'keys']], ['mget', ['batchfoo', 'some', 'random value', 'keys']],
['incr', 'batchfoo'] ['incr', 'batchfoo']
]) ])
.exec((err, replies) => { .exec().then((replies) => {
assert.strictEqual(err, null)
assert.strictEqual(replies.length, 2) assert.strictEqual(replies.length, 2)
assert.strictEqual(replies[0].length, 4) assert.strictEqual(replies[0].length, 4)
return done()
}) })
}) })
it('allows multiple operations to be performed on a hash', (done) => { it('allows multiple operations to be performed on a hash', () => {
client.batch() return client.batch()
.hmset('batchhash', 'a', 'foo', 'b', 1) .hmset('batchhash', 'a', 'foo', 'b', 1)
.hmset('batchhash', { .hmset('batchhash', {
extra: 'fancy', extra: 'fancy',
things: 'here' things: 'here'
}) })
.hgetall('batchhash') .hgetall('batchhash')
.exec(done) .exec()
})
it('should work without any callback or arguments', (done) => {
const batch = client.batch()
batch.set('baz', 'binary')
batch.set('foo', 'bar')
batch.ping()
batch.exec()
client.get('foo', helper.isString('bar', done))
}) })
}) })
}) })

View File

@@ -12,14 +12,12 @@ describe('The \'blpop\' method', () => {
let client let client
let bclient let bclient
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('pops value immediately if list contains values', (done) => { it('pops value immediately if list contains values', () => {
bclient = redis.createClient.apply(null, args) bclient = redis.createClient.apply(null, args)
redis.debugMode = true redis.debugMode = true
let text = '' let text = ''
@@ -27,43 +25,42 @@ describe('The \'blpop\' method', () => {
text += data text += data
return '' return ''
}) })
client.rpush('blocking list', 'initial value', helper.isNumber(1)) const values = ['blocking list', 'initial value']
const promise = client.rpush(values).then(helper.isNumber(1))
unhookIntercept() unhookIntercept()
assert(/^Send 127\.0\.0\.1:6379 id [0-9]+: \*3\r\n\$5\r\nrpush\r\n\$13\r\nblocking list\r\n\$13\r\ninitial value\r\n\n$/.test(text)) assert(/Send 127\.0\.0\.1:6379 id [0-9]+: \*3\r\n\$5\r\nrpush\r\n\$13\r\nblocking list\r\n\$13\r\ninitial value\r\n\n/.test(text), text)
redis.debugMode = false redis.debugMode = false
bclient.blpop('blocking list', 0, (err, value) => { return promise
assert.strictEqual(value[0], 'blocking list') .then(() => bclient.blpop(values[0], 0))
assert.strictEqual(value[1], 'initial value') .then(helper.isDeepEqual(values))
return done(err)
})
}) })
it('pops value immediately if list contains values using array notation', (done) => { it('pops value immediately if list contains values using array notation', () => {
bclient = redis.createClient.apply(null, args) bclient = redis.createClient.apply(null, args)
client.rpush(['blocking list', 'initial value'], helper.isNumber(1)) return client.rpush(['blocking list', 'initial value'])
bclient.blpop(['blocking list', 0], (err, value) => { .then(helper.isNumber(1))
assert.strictEqual(value[0], 'blocking list') .then(() => bclient.blpop(['blocking list', 0]))
assert.strictEqual(value[1], 'initial value') .then(helper.isDeepEqual(['blocking list', 'initial value']))
return done(err)
})
}) })
it('waits for value if list is not yet populated', (done) => { it('waits for value if list is not yet populated', () => {
bclient = redis.createClient.apply(null, args) bclient = redis.createClient.apply(null, args)
bclient.blpop('blocking list 2', 5, (err, value) => { const promises = [
assert.strictEqual(value[0], 'blocking list 2') bclient.blpop('blocking list 2', 5).then(helper.isDeepEqual(['blocking list 2', 'initial value']))
assert.strictEqual(value[1], 'initial value') ]
return done(err) promises.push(new Promise((resolve, reject) => {
}) setTimeout(() => {
client.rpush('blocking list 2', 'initial value', helper.isNumber(1)) resolve(client.rpush('blocking list 2', 'initial value').then(helper.isNumber(1)))
}, 100)
}))
return Promise.all(promises)
}) })
it('times out after specified time', (done) => { it('times out after specified time', () => {
bclient = redis.createClient.apply(null, args) bclient = redis.createClient.apply(null, args)
bclient.blpop('blocking list', 1, (err, res) => { return bclient.blpop('blocking list', 1)
assert.strictEqual(res, null) .then(helper.fail)
return done(err) .catch(helper.isError())
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -13,11 +13,9 @@ describe('The \'client\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
afterEach(() => { afterEach(() => {
@@ -25,93 +23,93 @@ describe('The \'client\' method', () => {
}) })
describe('list', () => { describe('list', () => {
it('lists connected clients', (done) => { it('lists connected clients', () => {
client.client('LIST', helper.match(pattern, done)) return client.client('LIST').then(helper.match(pattern))
}) })
it('lists connected clients when invoked with multi\'s chaining syntax', (done) => { it('lists connected clients when invoked with multi\'s chaining syntax', () => {
client.multi().client('list', helper.isType.string()).exec(helper.match(pattern, done)) return client.multi().client('list').exec().then(helper.match(pattern))
}) })
it('lists connected clients when invoked with array syntax on client', (done) => { it('lists connected clients when invoked with array syntax on client', () => {
client.multi().client(['list']).exec(helper.match(pattern, done)) return client.multi().client(['list']).exec().then(helper.match(pattern))
}) })
it('lists connected clients when invoked with multi\'s array syntax', (done) => { it('lists connected clients when invoked with multi\'s array syntax', () => {
client.multi([ return client.multi([
['client', 'list'] ['client', 'list']
]).exec(helper.match(pattern, done)) ]).exec().then(helper.match(pattern))
}) })
}) })
describe('reply', () => { describe('reply', () => {
describe('as normal command', () => { describe('as normal command', () => {
it('on', function (done) { it('on', function () {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]) helper.serverVersionAtLeast.call(this, client, [3, 2, 0])
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
client.client('reply', 'on', helper.isString('OK')) const promises = [client.client('reply', 'on').then(helper.isString('OK'))]
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
client.set('foo', 'bar', done) promises.push(client.set('foo', 'bar'))
return Promise.all(promises)
}) })
it('off', function (done) { it('off', function () {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]) helper.serverVersionAtLeast.call(this, client, [3, 2, 0])
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
client.client(Buffer.from('REPLY'), 'OFF', helper.isUndefined()) const promises = [client.client(Buffer.from('REPLY'), 'OFF').then(helper.isUndefined())]
assert.strictEqual(client.reply, 'OFF') assert.strictEqual(client.reply, 'OFF')
client.set('foo', 'bar', helper.isUndefined(done)) promises.push(client.set('foo', 'bar').then(helper.isUndefined()))
return Promise.all(promises)
}) })
it('skip', function (done) { it('skip', function () {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]) helper.serverVersionAtLeast.call(this, client, [3, 2, 0])
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
client.client('REPLY', Buffer.from('SKIP'), helper.isUndefined()) const promises = [client.client('REPLY', Buffer.from('SKIP')).then(helper.isUndefined())]
assert.strictEqual(client.reply, 'SKIP_ONE_MORE') assert.strictEqual(client.reply, 'SKIP_ONE_MORE')
client.set('foo', 'bar', helper.isUndefined()) promises.push(client.set('foo', 'bar').then(helper.isUndefined()))
client.get('foo', helper.isString('bar', done)) promises.push(client.get('foo').then(helper.isString('bar')))
return Promise.all(promises)
}) })
}) })
describe('in a batch context', () => { describe('in a batch context', () => {
it('on', function (done) { it('on', function () {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]) helper.serverVersionAtLeast.call(this, client, [3, 2, 0])
const batch = client.batch() const batch = client.batch()
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
batch.client('reply', 'on', helper.isString('OK')) batch.client('reply', 'on')
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
batch.set('foo', 'bar') batch.set('foo', 'bar')
batch.exec((err, res) => { return batch.exec().then(helper.isDeepEqual(['OK', 'OK']))
assert.deepEqual(res, ['OK', 'OK'])
done(err)
})
}) })
it('off', function (done) { it('off', function () {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]) helper.serverVersionAtLeast.call(this, client, [3, 2, 0])
const batch = client.batch() const batch = client.batch()
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
batch.set('hello', 'world') batch.set('hello', 'world')
batch.client(Buffer.from('REPLY'), Buffer.from('OFF'), helper.isUndefined()) batch.client(Buffer.from('REPLY'), Buffer.from('OFF'))
batch.set('foo', 'bar', helper.isUndefined()) batch.get('hello')
batch.exec((err, res) => { batch.get('hello')
return batch.exec().then((res) => {
assert.strictEqual(client.reply, 'OFF') assert.strictEqual(client.reply, 'OFF')
assert.deepEqual(res, ['OK', undefined, undefined]) assert.deepStrictEqual(res, ['OK', undefined, undefined, undefined])
done(err)
}) })
}) })
it('skip', function (done) { it('skip', function () {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]) helper.serverVersionAtLeast.call(this, client, [3, 2, 0])
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
client.batch() return client.batch()
.set('hello', 'world') .set('hello', 'world')
.client('REPLY', 'SKIP', helper.isUndefined()) .client('REPLY', 'SKIP')
.set('foo', 'bar', helper.isUndefined()) .set('foo', 'bar')
.get('foo') .get('foo')
.exec((err, res) => { .exec()
.then((res) => {
assert.strictEqual(client.reply, 'ON') assert.strictEqual(client.reply, 'ON')
assert.deepEqual(res, ['OK', undefined, undefined, 'bar']) assert.deepStrictEqual(res, ['OK', undefined, undefined, 'bar'])
done(err)
}) })
}) })
}) })
@@ -122,25 +120,23 @@ describe('The \'client\' method', () => {
beforeEach((done) => { beforeEach((done) => {
client2 = redis.createClient.apply(null, args) client2 = redis.createClient.apply(null, args)
client2.once('ready', () => { client2.once('ready', done)
done()
})
}) })
afterEach(() => { afterEach(() => {
client2.end(true) client2.end(true)
}) })
it('sets the name', (done) => { it('sets the name', () => {
// The querys are auto pipelined and the response is a response to all querys of one client // The querys are auto pipelined and the response is a response to all querys of one client
// per chunk. So the execution order is only guaranteed on each client // per chunk. So the execution order is only guaranteed on each client
const end = helper.callFuncAfter(done, 2) return Promise.all([
client.client('setname', 'RUTH'),
client.client('setname', 'RUTH') client2.client('setname', ['RENEE']).then(helper.isString('OK')),
client2.client('setname', ['RENEE'], helper.isString('OK')) client2.client(['setname', 'MARTIN']).then(helper.isString('OK')),
client2.client(['setname', 'MARTIN'], helper.isString('OK')) client2.client('getname').then(helper.isString('MARTIN')),
client2.client('getname', helper.isString('MARTIN', end)) client.client('getname').then(helper.isString('RUTH'))
client.client('getname', helper.isString('RUTH', end)) ])
}) })
}) })
}) })

View File

@@ -19,18 +19,14 @@ describe('The \'dbsize\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('reports an error', (done) => { it('reports an error', () => {
client.dbsize([], (err, res) => { return client.dbsize([]).then(helper.fail).catch((err) => {
assert(err.message.match(/The connection is already closed/)) assert(err.message.match(/The connection is already closed/))
done()
}) })
}) })
}) })
@@ -38,52 +34,34 @@ describe('The \'dbsize\' method', () => {
describe('when connected', () => { describe('when connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb().then(helper.isString('OK'))
client.flushdb((err, res) => {
helper.isString('OK')(err, res)
done()
})
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('returns a zero db size', (done) => { it('returns a zero db size', () => {
client.dbsize([], (err, res) => { return client.dbsize([]).then(helper.isNumber(0))
helper.isNotError()(err, res)
helper.isType.number()(err, res)
assert.strictEqual(res, 0, 'Initial db size should be 0')
done()
})
}) })
describe('when more data is added to Redis', () => { describe('when more data is added to Redis', () => {
let oldSize let oldSize
beforeEach((done) => { beforeEach(() => {
client.dbsize((err, res) => { return client.dbsize().then((res) => {
helper.isType.number()(err, res) helper.isNumber(0)(res)
assert.strictEqual(res, 0, 'Initial db size should be 0')
oldSize = res oldSize = res
return client.set(key, value).then(helper.isString('OK'))
client.set(key, value, (err, res) => {
helper.isNotError()(err, res)
done()
})
}) })
}) })
it('returns a larger db size', (done) => { it('returns a larger db size', () => {
client.dbsize([], (err, res) => { return client.dbsize([]).then((res) => {
helper.isNotError()(err, res) assert.strictEqual(typeof res, 'number')
helper.isType.positiveNumber()(err, res)
assert.strictEqual(true, (oldSize < res), 'Adding data should increase db size.') assert.strictEqual(true, (oldSize < res), 'Adding data should increase db size.')
done()
}) })
}) })
}) })

View File

@@ -9,42 +9,39 @@ describe('The \'del\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('allows a single key to be deleted', (done) => { it('allows a single key to be deleted', () => {
client.set('foo', 'bar') return Promise.all([
client.del('foo', helper.isNumber(1)) client.set('foo', 'bar'),
client.get('foo', helper.isNull(done)) client.del('foo').then(helper.isNumber(1)),
client.get('foo').then(helper.isNull())
])
}) })
it('allows del to be called on a key that does not exist', (done) => { it('allows del to be called on a key that does not exist', () => {
client.del('foo', helper.isNumber(0, done)) return client.del('foo').then(helper.isNumber(0))
}) })
it('allows multiple keys to be deleted', (done) => { it('allows multiple keys to be deleted', () => {
client.mset('foo', 'bar', 'apple', 'banana') return Promise.all([
client.del('foo', 'apple', helper.isNumber(2)) client.mset('foo', 'bar', 'apple', 'banana'),
client.get('foo', helper.isNull()) client.del('foo', 'apple').then(helper.isNumber(2)),
client.get('apple', helper.isNull(done)) client.get('foo').then(helper.isNull()),
client.get('apple').then(helper.isNull())
])
}) })
it('allows multiple keys to be deleted with the array syntax', (done) => { it('allows multiple keys to be deleted with the array syntax', () => {
client.mset('foo', 'bar', 'apple', 'banana') return Promise.all([
client.del(['foo', 'apple'], helper.isNumber(2)) client.mset('foo', 'bar', 'apple', 'banana'),
client.get('foo', helper.isNull()) client.del(['foo', 'apple']).then(helper.isNumber(2)),
client.get('apple', helper.isNull(done)) client.get('foo').then(helper.isNull()),
}) client.get('apple').then(helper.isNull())
])
it('allows multiple keys to be deleted with the array syntax and no callback', (done) => {
client.mset('foo', 'bar', 'apple', 'banana')
client.del(['foo', 'apple'])
client.get('foo', helper.isNull())
client.get('apple', helper.isNull(done))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -12,48 +12,44 @@ describe('The \'eval\' method', () => {
let client let client
const source = 'return redis.call(\'set\', \'sha\', \'test\')' const source = 'return redis.call(\'set\', \'sha\', \'test\')'
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('converts a float to an integer when evaluated', (done) => { it('converts a float to an integer when evaluated', () => {
client.eval('return 100.5', 0, helper.isNumber(100, done)) return client.eval('return 100.5', 0).then(helper.isNumber(100))
}) })
it('returns a string', (done) => { it('returns a string', () => {
client.eval('return \'hello world\'', 0, helper.isString('hello world', done)) return client.eval('return \'hello world\'', 0).then(helper.isString('hello world'))
}) })
it('converts boolean true to integer 1', (done) => { it('converts boolean true to integer 1', () => {
client.eval('return true', 0, helper.isNumber(1, done)) return client.eval('return true', 0).then(helper.isNumber(1))
}) })
it('converts boolean false to null', (done) => { it('converts boolean false to null', () => {
client.eval('return false', 0, helper.isNull(done)) return client.eval('return false', 0).then(helper.isNull())
}) })
it('converts lua status code to string representation', (done) => { it('converts lua status code to string representation', () => {
client.eval('return {ok=\'fine\'}', 0, helper.isString('fine', done)) return client.eval('return {ok=\'fine\'}', 0).then(helper.isString('fine'))
}) })
it('converts lua error to an error response', (done) => { it('converts lua error to an error response', () => {
client.eval('return {err=\'this is an error\'}', 0, (err) => { return client.eval('return {err=\'this is an error\'}', 0).then(helper.fail).catch((err) => {
assert(err.code === undefined) assert(err.code === undefined)
helper.isError()(err) helper.isError()(err)
done()
}) })
}) })
it('represents a lua table appropritely', (done) => { it('represents a lua table appropriately', () => {
client.eval('return {1,2,3,\'ciao\',{1,2}}', 0, (err, res) => { return client.eval('return {1,2,3,\'ciao\',{1,2}}', 0).then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(5, res.length) assert.strictEqual(5, res.length)
assert.strictEqual(1, res[0]) assert.strictEqual(1, res[0])
assert.strictEqual(2, res[1]) assert.strictEqual(2, res[1])
@@ -62,134 +58,109 @@ describe('The \'eval\' method', () => {
assert.strictEqual(2, res[4].length) assert.strictEqual(2, res[4].length)
assert.strictEqual(1, res[4][0]) assert.strictEqual(1, res[4][0])
assert.strictEqual(2, res[4][1]) assert.strictEqual(2, res[4][1])
return done()
}) })
}) })
it('populates keys and argv correctly', (done) => { it('populates keys and argv correctly', () => {
client.eval('return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 2, 'a', 'b', 'c', 'd', helper.isDeepEqual(['a', 'b', 'c', 'd'], done)) return client.eval('return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 2, 'a', 'b', 'c', 'd').then(helper.isDeepEqual(['a', 'b', 'c', 'd']))
}) })
it('allows arguments to be provided in array rather than as multiple parameters', (done) => { it('allows arguments to be provided in array rather than as multiple parameters', () => {
client.eval(['return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 2, 'a', 'b', 'c', 'd'], helper.isDeepEqual(['a', 'b', 'c', 'd'], done)) return client.eval(['return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}', 2, 'a', 'b', 'c', 'd']).then(helper.isDeepEqual(['a', 'b', 'c', 'd']))
})
it('allows a script to be executed that accesses the redis API without callback', (done) => {
client.eval(source, 0)
client.get('sha', helper.isString('test', done))
}) })
describe('evalsha', () => { describe('evalsha', () => {
const sha = crypto.createHash('sha1').update(source).digest('hex') const sha = crypto.createHash('sha1').update(source).digest('hex')
it('allows a script to be executed that accesses the redis API', (done) => { it('allows a script to be executed that accesses the redis API', () => {
client.eval(source, 0, helper.isString('OK')) return Promise.all([
client.get('sha', helper.isString('test', done)) client.eval(source, 0).then(helper.isString('OK')),
client.get('sha').then(helper.isString('test'))
])
}) })
it('can execute a script if the SHA exists', (done) => { it('can execute a script if the SHA exists', () => {
client.evalsha(sha, 0, helper.isString('OK')) return Promise.all([
client.get('sha', helper.isString('test', done)) client.evalsha(sha, 0).then(helper.isString('OK')),
client.get('sha').then(helper.isString('test'))
])
}) })
it('returns an error if SHA does not exist', (done) => { it('returns an error if SHA does not exist', () => {
client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0, helper.isError(done)) return client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0)
}) .then(helper.fail)
.catch(helper.isError(/NOSCRIPT No matching script\. Please use EVAL/))
it('emit an error if SHA does not exist without any callback', (done) => {
client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0)
client.on('error', (err) => {
assert.strictEqual(err.code, 'NOSCRIPT')
assert(/NOSCRIPT No matching script. Please use EVAL./.test(err.message))
done()
})
})
it('emits an error if SHA does not exist and no callback has been provided', (done) => {
client.on('error', (err) => {
assert.strictEqual(err.message, 'NOSCRIPT No matching script. Please use EVAL.')
done()
})
client.evalsha('ffffffffffffffffffffffffffffffffffffffff', 0)
}) })
}) })
it('allows a key to be incremented, and performs appropriate conversion from LUA type', (done) => { it('allows a key to be incremented, and performs appropriate conversion from LUA type', () => {
client.set('incr key', 0, (err, reply) => { return Promise.all([
if (err) return done(err) client.set('incr key', 0),
client.eval('local foo = redis.call(\'incr\',\'incr key\')\nreturn {type(foo),foo}', 0, (err, res) => { client.eval('local foo = redis.call(\'incr\',\'incr key\')\nreturn {type(foo),foo}', 0).then((res) => {
assert.strictEqual(2, res.length) assert.strictEqual(2, res.length)
assert.strictEqual('number', res[0]) assert.strictEqual('number', res[0])
assert.strictEqual(1, res[1]) assert.strictEqual(1, res[1])
return done(err)
}) })
}) ])
}) })
it('allows a bulk operation to be performed, and performs appropriate conversion from LUA type', (done) => { it('allows a bulk operation to be performed, and performs appropriate conversion from LUA type', () => {
client.set('bulk reply key', 'bulk reply value', (err, res) => { return Promise.all([
assert.strictEqual(err, null) client.set('bulk reply key', 'bulk reply value'),
client.eval('local foo = redis.call(\'get\',\'bulk reply key\'); return {type(foo),foo}', 0, (err, res) => { client.eval('local foo = redis.call(\'get\',\'bulk reply key\'); return {type(foo),foo}', 0).then((res) => {
assert.strictEqual(2, res.length) assert.strictEqual(2, res.length)
assert.strictEqual('string', res[0]) assert.strictEqual('string', res[0])
assert.strictEqual('bulk reply value', res[1]) assert.strictEqual('bulk reply value', res[1])
return done(err)
}) })
}) ])
}) })
it('allows a multi mulk operation to be performed, with the appropriate type conversion', (done) => { it('allows a multi mulk operation to be performed, with the appropriate type conversion', () => {
client.multi() return client.multi()
.del('mylist') .del('mylist')
.rpush('mylist', 'a') .rpush('mylist', 'a')
.rpush('mylist', 'b') .rpush('mylist', 'b')
.rpush('mylist', 'c') .rpush('mylist', 'c')
.exec((err, replies) => { .exec().then((replies) => {
if (err) return done(err) return client.eval('local foo = redis.call(\'lrange\',\'mylist\',0,-1); return {type(foo),foo[1],foo[2],foo[3],# foo}', 0).then((res) => {
client.eval('local foo = redis.call(\'lrange\',\'mylist\',0,-1); return {type(foo),foo[1],foo[2],foo[3],# foo}', 0, (err, res) => {
assert.strictEqual(5, res.length) assert.strictEqual(5, res.length)
assert.strictEqual('table', res[0]) assert.strictEqual('table', res[0])
assert.strictEqual('a', res[1]) assert.strictEqual('a', res[1])
assert.strictEqual('b', res[2]) assert.strictEqual('b', res[2])
assert.strictEqual('c', res[3]) assert.strictEqual('c', res[3])
assert.strictEqual(3, res[4]) assert.strictEqual(3, res[4])
return done(err)
}) })
}) })
}) })
it('returns an appropriate representation of Lua status reply', (done) => { it('returns an appropriate representation of Lua status reply', () => {
client.eval('local foo = redis.call(\'set\',\'mykey\',\'myval\'); return {type(foo),foo[\'ok\']}', 0, (err, res) => { return client.eval('local foo = redis.call(\'set\',\'mykey\',\'myval\'); return {type(foo),foo[\'ok\']}', 0).then((res) => {
assert.strictEqual(2, res.length) assert.strictEqual(2, res.length)
assert.strictEqual('table', res[0]) assert.strictEqual('table', res[0])
assert.strictEqual('OK', res[1]) assert.strictEqual('OK', res[1])
return done(err)
}) })
}) })
it('returns an appropriate representation of a Lua error reply', (done) => { it('returns an appropriate representation of a Lua error reply', () => {
client.set('error reply key', 'error reply value', (err, res) => { return Promise.all([
if (err) return done(err) client.set('error reply key', 'error reply value'),
client.eval('local foo = redis.pcall(\'incr\',\'error reply key\'); return {type(foo),foo[\'err\']}', 0, (err, res) => { client.eval('local foo = redis.pcall(\'incr\',\'error reply key\'); return {type(foo),foo[\'err\']}', 0).then((res) => {
assert.strictEqual(2, res.length) assert.strictEqual(2, res.length)
assert.strictEqual('table', res[0]) assert.strictEqual('table', res[0])
assert.strictEqual('ERR value is not an integer or out of range', res[1]) assert.strictEqual('ERR value is not an integer or out of range', res[1])
return done(err)
}) })
}) ])
}) })
it('returns an appropriate representation of a Lua nil reply', (done) => { it('returns an appropriate representation of a Lua nil reply', () => {
client.del('nil reply key', (err, res) => { return Promise.all([
if (err) return done(err) client.del('nil reply key'),
client.eval('local foo = redis.call(\'get\',\'nil reply key\'); return {type(foo),foo == false}', 0, (err, res) => { client.eval('local foo = redis.call(\'get\',\'nil reply key\'); return {type(foo),foo == false}', 0).then((res) => {
if (err) throw err
assert.strictEqual(2, res.length) assert.strictEqual(2, res.length)
assert.strictEqual('boolean', res[0]) assert.strictEqual('boolean', res[0])
assert.strictEqual(1, res[1]) assert.strictEqual(1, res[1])
return done(err)
}) })
}) ])
}) })
}) })
}) })

View File

@@ -9,25 +9,27 @@ describe('The \'exists\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns 1 if the key exists', (done) => { it('returns 1 if the key exists', () => {
client.set('foo', 'bar') return Promise.all([
client.exists('foo', helper.isNumber(1, done)) client.set('foo', 'bar'),
client.exists('foo').then(helper.isNumber(1))
])
}) })
it('returns 1 if the key exists with array syntax', (done) => { it('returns 1 if the key exists with array syntax', () => {
client.set('foo', 'bar') return Promise.all([
client.exists(['foo'], helper.isNumber(1, done)) client.set('foo', 'bar'),
client.exists(['foo']).then(helper.isNumber(1))
])
}) })
it('returns 0 if the key does not exist', (done) => { it('returns 0 if the key does not exist', () => {
client.exists('bar', helper.isNumber(0, done)) return client.exists('bar').then(helper.isNumber(0))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -9,27 +9,33 @@ describe('The \'expire\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('expires key after timeout', (done) => { it('expires key after timeout', () => {
client.set(['expiry key', 'bar'], helper.isString('OK')) return Promise.all([
client.expire('expiry key', '1', helper.isNumber(1)) client.set(['expiry key', 'bar']).then(helper.isString('OK')),
setTimeout(() => { client.expire('expiry key', '1').then(helper.isNumber(1)),
client.exists(['expiry key'], helper.isNumber(0, done)) new Promise((resolve, reject) => {
}, 1050) setTimeout(() => {
resolve(client.exists(['expiry key']).then(helper.isNumber(0)))
}, 1050)
})
])
}) })
it('expires key after timeout with array syntax', (done) => { it('expires key after timeout with array syntax', () => {
client.set(['expiry key', 'bar'], helper.isString('OK')) return Promise.all([
client.expire(['expiry key', '1'], helper.isNumber(1)) client.set(['expiry key', 'bar']).then(helper.isString('OK')),
setTimeout(() => { client.expire(['expiry key', '1']).then(helper.isNumber(1)),
client.exists(['expiry key'], helper.isNumber(0, done)) new Promise((resolve, reject) => {
}, 1050) setTimeout(() => {
resolve(client.exists(['expiry key']).then(helper.isNumber(0)))
}, 1050)
})
])
}) })
afterEach(() => { afterEach(() => {

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -19,19 +18,15 @@ describe('The \'flushdb\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('reports an error', (done) => { it('reports an error', () => {
client.flushdb((err, res) => { return client.flushdb()
assert(err.message.match(/The connection is already closed/)) .then(helper.fail)
done() .catch(helper.isError(/The connection is already closed/))
})
}) })
}) })
@@ -40,9 +35,7 @@ describe('The \'flushdb\' method', () => {
beforeEach((done) => { beforeEach((done) => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { client.once('ready', done)
done()
})
}) })
afterEach(() => { afterEach(() => {
@@ -50,42 +43,25 @@ describe('The \'flushdb\' method', () => {
}) })
describe('when there is data in Redis', () => { describe('when there is data in Redis', () => {
beforeEach((done) => { beforeEach(() => {
client.mset(key, uuid.v4(), key2, uuid.v4(), helper.isNotError()) return Promise.all([
client.dbsize([], (err, res) => { client.mset(key, uuid.v4(), key2, uuid.v4()).then(helper.isString('OK')),
helper.isType.positiveNumber()(err, res) client.dbsize([]).then(helper.isNumber(2))
assert.strictEqual(res, 2, 'Two keys should have been inserted') ])
done()
})
}) })
it('deletes all the keys', (done) => { it('deletes all the keys', () => {
client.flushdb((err, res) => { return Promise.all([
assert.strictEqual(err, null) client.flushdb().then(helper.isString('OK')),
assert.strictEqual(res, 'OK') client.mget(key, key2).then(helper.isDeepEqual([null, null]))
client.mget(key, key2, (err, res) => { ])
assert.strictEqual(null, err, 'Unexpected error returned')
assert.strictEqual(true, Array.isArray(res), 'Results object should be an array.')
assert.strictEqual(2, res.length, 'Results array should have length 2.')
assert.strictEqual(null, res[0], 'Redis key should have been flushed.')
assert.strictEqual(null, res[1], 'Redis key should have been flushed.')
done(err)
})
})
}) })
it('results in a db size of zero', (done) => { it('results in a db size of zero', () => {
client.flushdb((err, res) => { return Promise.all([
assert.strictEqual(err, null) client.flushdb(),
client.dbsize([], helper.isNumber(0, done)) client.dbsize([]).then(helper.isNumber(0))
}) ])
})
it('results in a db size of zero without a callback', (done) => {
client.flushdb()
setTimeout(() => {
client.dbsize(helper.isNumber(0, done))
}, 25)
}) })
}) })
}) })

View File

@@ -9,20 +9,14 @@ describe('The \'geoadd\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns 1 if the key exists', function (done) { it('returns 1 if the key exists', function () {
helper.serverVersionAtLeast.call(this, client, [3, 2, 0]) helper.serverVersionAtLeast.call(this, client, [3, 2, 0])
client.geoadd('mycity:21:0:location', '13.361389', '38.115556', 'COR', (err, res) => { return client.geoadd('mycity:21:0:location', '13.361389', '38.115556', 'COR')
console.log(err, res)
// geoadd is still in the unstable branch. As soon as it reaches the stable one, activate this test
done()
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -19,25 +18,15 @@ describe('The \'get\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('reports an error', (done) => { it('reports an error', () => {
client.get(key, (err, res) => { return client.get(key)
assert(err.message.match(/The connection is already closed/)) .then(helper.fail)
done() .catch(helper.isError(/The connection is already closed/))
})
})
it('reports an error promisified', () => {
return client.getAsync(key).then(assert, (err) => {
assert(err.message.match(/The connection is already closed/))
})
}) })
}) })
@@ -46,9 +35,7 @@ describe('The \'get\' method', () => {
beforeEach((done) => { beforeEach((done) => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { client.once('ready', done)
done()
})
}) })
afterEach(() => { afterEach(() => {
@@ -56,35 +43,18 @@ describe('The \'get\' method', () => {
}) })
describe('when the key exists in Redis', () => { describe('when the key exists in Redis', () => {
beforeEach((done) => { beforeEach(() => {
client.set(key, value, (err, res) => { return client.set(key, value).then(helper.isString('OK'))
helper.isNotError()(err, res)
done()
})
}) })
it('gets the value correctly', (done) => { it('gets the value correctly', () => {
client.get(key, (err, res) => { return client.get(key).then(helper.isString(value))
helper.isString(value)(err, res)
done(err)
})
})
it('should not throw on a get without callback (even if it\'s not useful)', (done) => {
client.get(key)
client.on('error', (err) => {
throw err
})
setTimeout(done, 25)
}) })
}) })
describe('when the key does not exist in Redis', () => { describe('when the key does not exist in Redis', () => {
it('gets a null value', (done) => { it('gets a null value', () => {
client.get(key, (err, res) => { return client.get(key).then(helper.isNull())
helper.isNull()(err, res)
done(err)
})
}) })
}) })
}) })

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -20,19 +19,15 @@ describe('The \'getset\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('reports an error', (done) => { it('reports an error', () => {
client.get(key, (err, res) => { return client.get(key)
assert(err.message.match(/The connection is already closed/)) .then(helper.fail)
done() .catch(helper.isError(/The connection is already closed/))
})
}) })
}) })
@@ -41,9 +36,7 @@ describe('The \'getset\' method', () => {
beforeEach((done) => { beforeEach((done) => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { client.once('ready', done)
done()
})
}) })
afterEach(() => { afterEach(() => {
@@ -51,50 +44,35 @@ describe('The \'getset\' method', () => {
}) })
describe('when the key exists in Redis', () => { describe('when the key exists in Redis', () => {
beforeEach((done) => { beforeEach(() => {
client.set(key, value, (err, res) => { return client.set(key, value).then(helper.isString('OK'))
helper.isNotError()(err, res)
done()
})
}) })
it('gets the value correctly', (done) => { it('gets the value correctly', () => {
client.getset(key, value2, (err, res) => { return Promise.all([
helper.isString(value)(err, res) client.getset(key, value2).then(helper.isString(value)),
client.get(key, (err, res) => { client.get(key).then(helper.isString(value2))
helper.isString(value2)(err, res) ])
done(err)
})
})
}) })
it('gets the value correctly with array syntax', (done) => { it('gets the value correctly with array syntax', () => {
client.getset([key, value2], (err, res) => { return Promise.all([
helper.isString(value)(err, res) client.getset([key, value2]).then(helper.isString(value)),
client.get(key, (err, res) => { client.get(key).then(helper.isString(value2))
helper.isString(value2)(err, res) ])
done(err)
})
})
}) })
it('gets the value correctly with array syntax style 2', (done) => { it('gets the value correctly with array syntax style 2', () => {
client.getset(key, [value2], (err, res) => { return Promise.all([
helper.isString(value)(err, res) client.getset(key, [value2]).then(helper.isString(value)),
client.get(key, (err, res) => { client.get(key).then(helper.isString(value2))
helper.isString(value2)(err, res) ])
done(err)
})
})
}) })
}) })
describe('when the key does not exist in Redis', () => { describe('when the key does not exist in Redis', () => {
it('gets a null value', (done) => { it('gets a null value', () => {
client.getset(key, value, (err, res) => { return client.getset(key, value).then(helper.isNull())
helper.isNull()(err, res)
done(err)
})
}) })
}) })
}) })

View File

@@ -8,69 +8,62 @@ const redis = config.redis
describe('The \'hgetall\' method', () => { describe('The \'hgetall\' method', () => {
helper.allTests((ip, args) => { helper.allTests((ip, args) => {
let client
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client
describe('regular client', () => { describe('regular client', () => {
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('handles simple keys and values', (done) => { it('handles simple keys and values', () => {
client.hmset(['hosts', 'hasOwnProperty', '1', 'another', '23', 'home', '1234'], helper.isString('OK')) return Promise.all([
client.hgetall(['hosts'], (err, obj) => { client.hmset(['hosts', 'hasOwnProperty', '1', 'another', '23', 'home', '1234']).then(helper.isString('OK')),
assert.strictEqual(3, Object.keys(obj).length) client.hgetall(['hosts']).then(helper.isDeepEqual({
assert.strictEqual('1', obj.hasOwnProperty.toString()) hasOwnProperty: '1',
assert.strictEqual('23', obj.another.toString()) another: '23',
assert.strictEqual('1234', obj.home.toString()) home: '1234'
done(err) }))
}) ])
}) })
it('handles fetching keys set using an object', (done) => { it('handles fetching keys set using an object', () => {
client.batch().hmset('msgTest', { message: 'hello' }, undefined).exec() return Promise.all([
client.hgetall('msgTest', (err, obj) => { client.batch().hmset('msgTest', { message: 'hello' }).exec(),
assert.strictEqual(1, Object.keys(obj).length) client.hgetall('msgTest').then(helper.isDeepEqual({ message: 'hello' }))
assert.strictEqual(obj.message, 'hello') ])
done(err)
})
}) })
it('handles fetching a messing key', (done) => { it('handles fetching a messing key', () => {
client.hgetall('missing', (err, obj) => { return client.hgetall('missing').then(helper.isNull())
assert.strictEqual(null, obj)
done(err)
})
}) })
}) })
describe('binary client', () => { describe('binary client', () => {
let client
const args = config.configureClient(ip, { const args = config.configureClient(ip, {
returnBuffers: true returnBuffers: true
}) })
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns binary results', (done) => { it('returns binary results', () => {
client.hmset(['bhosts', 'mjr', '1', 'another', '23', 'home', '1234', Buffer.from([0xAA, 0xBB, 0x00, 0xF0]), Buffer.from([0xCC, 0xDD, 0x00, 0xF0])], helper.isString('OK')) const weirdKey = Buffer.from([0xAA, 0xBB, 0x00, 0xF0])
client.hgetall('bhosts', (err, obj) => { const weirdValue = Buffer.from([0xCC, 0xDD, 0x00, 0xF0])
assert.strictEqual(4, Object.keys(obj).length) return Promise.all([
assert.strictEqual('1', obj.mjr.toString()) client.hmset(['bhosts', 'mjr', '1', 'another', '23', 'home', '1234', weirdKey, weirdValue]).then(helper.isString('OK')),
assert.strictEqual('23', obj.another.toString()) client.hgetall('bhosts').then((obj) => {
assert.strictEqual('1234', obj.home.toString()) assert.strictEqual(4, Object.keys(obj).length)
assert.strictEqual((Buffer.from([0xAA, 0xBB, 0x00, 0xF0])).toString('binary'), Object.keys(obj)[3]) assert.strictEqual('1', obj.mjr.toString())
assert.strictEqual((Buffer.from([0xCC, 0xDD, 0x00, 0xF0])).toString('binary'), obj[(Buffer.from([0xAA, 0xBB, 0x00, 0xF0])).toString('binary')].toString('binary')) assert.strictEqual('23', obj.another.toString())
return done(err) assert.strictEqual('1234', obj.home.toString())
}) assert.strictEqual(weirdKey.toString('binary'), Object.keys(obj)[3])
assert.strictEqual(weirdValue.toString('binary'), obj[weirdKey.toString('binary')].toString('binary'))
})
])
}) })
}) })

View File

@@ -10,24 +10,19 @@ describe('The \'hincrby\' method', () => {
let client let client
const hash = 'test hash' const hash = 'test hash'
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('increments a key that has already been set', (done) => { it('increments a key that has already been set', () => {
const field = 'field 1' const field = 'field 1'
client.hset(hash, field, 33) client.hset(hash, field, 33)
client.hincrby(hash, field, 10, helper.isNumber(43, done)) return client.hincrby(hash, field, 10).then(helper.isNumber(43))
}) })
it('increments a key that has not been set', (done) => { it('increments a key that has not been set', () => {
const field = 'field 2' return client.hincrby(hash, 'field 2', 10).then(helper.isNumber(10))
client.hincrby(hash, field, 10, helper.isNumber(10, done))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -10,23 +10,21 @@ describe('The \'hlen\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('reports the count of keys', (done) => { it('reports the count of keys', () => {
const hash = 'test hash' const hash = 'test hash'
const field1 = Buffer.from('0123456789') const field1 = Buffer.from('0123456789')
const value1 = Buffer.from('abcdefghij') const value1 = Buffer.from('abcdefghij')
const field2 = Buffer.from('') const field2 = Buffer.from('')
const value2 = Buffer.from('') const value2 = Buffer.from('')
client.hset(hash, field1, value1, helper.isNumber(1)) client.hset(hash, field1, value1).then(helper.isNumber(1))
client.hset(hash, field2, value2, helper.isNumber(1)) client.hset(hash, field2, value2).then(helper.isNumber(1))
client.hlen(hash, helper.isNumber(2, done)) return client.hlen(hash).then(helper.isNumber(2))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -11,54 +10,36 @@ describe('The \'hmget\' method', () => {
let client let client
const hash = 'test hash' const hash = 'test hash'
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('error', done) client.flushdb()
client.once('ready', () => { return client.hmset(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value'})
client.flushdb() .then(helper.isString('OK'))
client.hmset(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value'}, helper.isString('OK', done))
})
}) })
it('allows keys to be specified using multiple arguments', (done) => { it('allows keys to be specified using multiple arguments', () => {
client.hmget(hash, '0123456789', 'some manner of key', (err, reply) => { return client.hmget(hash, '0123456789', 'some manner of key')
assert.strictEqual('abcdefghij', reply[0].toString()) .then(helper.isDeepEqual(['abcdefghij', 'a type of value']))
assert.strictEqual('a type of value', reply[1].toString())
return done(err)
})
}) })
it('allows keys to be specified by passing an array without manipulating the array', (done) => { it('allows keys to be specified by passing an array without manipulating the array', () => {
const data = ['0123456789', 'some manner of key'] const data = ['0123456789', 'some manner of key']
client.hmget(hash, data, (err, reply) => { return client.hmget(hash, data)
assert.strictEqual(data.length, 2) .then(helper.isDeepEqual(['abcdefghij', 'a type of value']))
assert.strictEqual('abcdefghij', reply[0].toString())
assert.strictEqual('a type of value', reply[1].toString())
return done(err)
})
}) })
it('allows keys to be specified by passing an array as first argument', (done) => { it('allows keys to be specified by passing an array as first argument', () => {
client.hmget([hash, '0123456789', 'some manner of key'], (err, reply) => { return client.hmget([hash, '0123456789', 'some manner of key'])
assert.strictEqual('abcdefghij', reply[0].toString()) .then(helper.isDeepEqual(['abcdefghij', 'a type of value']))
assert.strictEqual('a type of value', reply[1].toString())
return done(err)
})
}) })
it('allows a single key to be specified in an array', (done) => { it('allows a single key to be specified in an array', () => {
client.hmget(hash, ['0123456789'], (err, reply) => { return client.hmget(hash, ['0123456789']).then(helper.isDeepEqual(['abcdefghij']))
assert.strictEqual('abcdefghij', reply[0].toString())
return done(err)
})
}) })
it('allows keys to be specified that have not yet been set', (done) => { it('allows keys to be specified that have not yet been set', () => {
client.hmget(hash, 'missing thing', 'another missing thing', (err, reply) => { return client.hmget(hash, 'missing thing', 'another missing thing')
assert.strictEqual(null, reply[0]) .then(helper.isDeepEqual([null, null]))
assert.strictEqual(null, reply[1])
return done(err)
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -11,100 +10,57 @@ describe('The \'hmset\' method', () => {
let client let client
const hash = 'test hash' const hash = 'test hash'
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('handles redis-style syntax', (done) => { it('handles redis-style syntax', () => {
client.hmset(hash, '0123456789', 'abcdefghij', 'some manner of key', 'a type of value', 'otherTypes', 555, helper.isString('OK')) client.hmset(hash, '0123456789', 'abcdefghij', 'some manner of key', 'a type of value', 'otherTypes', 555).then(helper.isString('OK'))
client.hgetall(hash, (err, obj) => { return client.hgetall(hash).then(helper.isDeepEqual({
assert.strictEqual(obj['0123456789'], 'abcdefghij') '0123456789': 'abcdefghij',
assert.strictEqual(obj['some manner of key'], 'a type of value') 'some manner of key': 'a type of value',
return done(err) 'otherTypes': '555'
}) }))
}) })
it('handles object-style syntax', (done) => { it('handles object-style syntax', () => {
client.hmset(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}, helper.isString('OK')) client.hmset(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}).then(helper.isString('OK'))
client.hgetall(hash, (err, obj) => { return client.hgetall(hash).then(helper.isDeepEqual({
assert.strictEqual(obj['0123456789'], 'abcdefghij') '0123456789': 'abcdefghij',
assert.strictEqual(obj['some manner of key'], 'a type of value') 'some manner of key': 'a type of value',
return done(err) 'otherTypes': '555'
}) }))
}) })
it('handles object-style syntax and the key being a number', (done) => { it('handles object-style syntax and the key being a number', () => {
client.hmset(231232, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}, undefined) client.hmset(231232, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555})
client.hgetall(231232, (err, obj) => { return client.hgetall(231232).then(helper.isDeepEqual({
assert.strictEqual(obj['0123456789'], 'abcdefghij') '0123456789': 'abcdefghij',
assert.strictEqual(obj['some manner of key'], 'a type of value') 'some manner of key': 'a type of value',
return done(err) 'otherTypes': '555'
}) }))
}) })
it('allows a numeric key', (done) => { it('allows a numeric key', () => {
client.hmset(hash, 99, 'banana', helper.isString('OK')) client.hmset(hash, 99, 'banana').then(helper.isString('OK'))
client.hgetall(hash, (err, obj) => { return client.hgetall(hash).then(helper.isDeepEqual({ 99: 'banana' }))
assert.strictEqual(obj['99'], 'banana')
return done(err)
})
}) })
it('allows a numeric key without callback', (done) => { it('allows an array', () => {
client.hmset(hash, 99, 'banana', 'test', 25) client.hmset([hash, 99, 'banana', 'test', 25]).then(helper.isString('OK'))
client.hgetall(hash, (err, obj) => { return client.hgetall(hash).then(helper.isDeepEqual({
assert.strictEqual(obj['99'], 'banana') 99: 'banana',
assert.strictEqual(obj.test, '25') test: '25'
return done(err) }))
})
}) })
it('allows an array without callback', (done) => { it('allows a key plus array', () => {
client.hmset([hash, 99, 'banana', 'test', 25]) client.hmset(hash, [99, 'banana', 'test', 25]).then(helper.isString('OK'))
client.hgetall(hash, (err, obj) => { return client.hgetall(hash).then(helper.isDeepEqual({
assert.strictEqual(obj['99'], 'banana') 99: 'banana',
assert.strictEqual(obj.test, '25') test: '25'
return done(err) }))
})
})
it('allows an array and a callback', (done) => {
client.hmset([hash, 99, 'banana', 'test', 25], helper.isString('OK'))
client.hgetall(hash, (err, obj) => {
assert.strictEqual(obj['99'], 'banana')
assert.strictEqual(obj.test, '25')
return done(err)
})
})
it('allows a key plus array without callback', (done) => {
client.hmset(hash, [99, 'banana', 'test', 25])
client.hgetall(hash, (err, obj) => {
assert.strictEqual(obj['99'], 'banana')
assert.strictEqual(obj.test, '25')
return done(err)
})
})
it('allows a key plus array and a callback', (done) => {
client.hmset(hash, [99, 'banana', 'test', 25], helper.isString('OK'))
client.hgetall(hash, (err, obj) => {
assert.strictEqual(obj['99'], 'banana')
assert.strictEqual(obj.test, '25')
return done(err)
})
})
it('handles object-style syntax without callback', (done) => {
client.hmset(hash, {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value'})
client.hgetall(hash, (err, obj) => {
assert.strictEqual(obj['0123456789'], 'abcdefghij')
assert.strictEqual(obj['some manner of key'], 'a type of value')
return done(err)
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -12,66 +12,51 @@ describe('The \'hset\' method', () => {
let client let client
const hash = 'test hash' const hash = 'test hash'
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('allows a value to be set in a hash', (done) => { it('allows a value to be set in a hash', () => {
const field = Buffer.from('0123456789') const field = Buffer.from('0123456789')
const value = Buffer.from('abcdefghij') const value = Buffer.from('abcdefghij')
client.hset(hash, field, value, helper.isNumber(1)) client.hset(hash, field, value).then(helper.isNumber(1))
client.hget(hash, field, helper.isString(value.toString(), done)) return client.hget(hash, field).then(helper.isString(value.toString()))
}) })
it('handles an empty value', (done) => { it('handles an empty value', () => {
const field = Buffer.from('0123456789') const field = Buffer.from('0123456789')
const value = Buffer.from('') const value = Buffer.from('')
client.hset(hash, field, value, helper.isNumber(1)) client.hset(hash, field, value).then(helper.isNumber(1))
client.hget([hash, field], helper.isString('', done)) return client.hget([hash, field]).then(helper.isString(''))
}) })
it('handles empty key and value', (done) => { it('handles empty key and value', () => {
const field = Buffer.from('') const field = Buffer.from('')
const value = Buffer.from('') const value = Buffer.from('')
client.hset([hash, field, value], (err, res) => { client.hset([hash, field, value]).then(helper.isNumber(1))
assert.strictEqual(err, null) return client.hset(hash, field, value).then(helper.isNumber(0))
assert.strictEqual(res, 1)
client.hset(hash, field, value, helper.isNumber(0, done))
})
}) })
it('warns if someone passed a array either as field or as value', (done) => { it('warns if someone passed a array either as field or as value', () => {
const hash = 'test hash' const hash = 'test hash'
const field = 'array' const field = 'array'
// This would be converted to "array contents" but if you use more than one entry, // This would be converted to "array contents" but if you use more than one entry,
// it'll result in e.g. "array contents,second content" and this is not supported and considered harmful // it'll result in e.g. "array contents,second content" and this is not supported and considered harmful
const value = ['array contents'] const value = ['array contents']
client.hmset(hash, field, value, helper.isError(done)) return client.hmset(hash, field, value).then(assert, helper.isError())
}) })
it('does not error when a buffer and date are set as values on the same hash', (done) => { it('does not error when a buffer and date are set as values on the same hash', () => {
const hash = 'test hash' return client.hmset('test hash', 'buffer', Buffer.from('abcdefghij'), 'data', new Date())
const field1 = 'buffer' .then(helper.isString('OK'))
const value1 = Buffer.from('abcdefghij')
const field2 = 'date'
const value2 = new Date()
client.hmset(hash, field1, value1, field2, value2, helper.isString('OK', done))
}) })
it('does not error when a buffer and date are set as fields on the same hash', (done) => { it('does not error when a buffer and date are set as fields on the same hash', () => {
const hash = 'test hash' return client.hmset('test hash', Buffer.from('abcdefghij'), 'buffer', new Date(), 'date')
const value1 = 'buffer' .then(helper.isString('OK'))
const field1 = Buffer.from('abcdefghij')
const value2 = 'date'
const field2 = new Date()
client.hmset(hash, field1, value1, field2, value2, helper.isString('OK', done))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -17,53 +16,47 @@ describe('The \'incr\' method', () => {
}) })
/* /*
Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 === 9007199254740991 Number.MAX_SAFE_INTEGER === Math.pow(2, 53) - 1 === 9007199254740991
9007199254740992 -> 9007199254740992 9007199254740992 -> 9007199254740992
9007199254740993 -> 9007199254740992 9007199254740993 -> 9007199254740992
9007199254740994 -> 9007199254740994 9007199254740994 -> 9007199254740994
9007199254740995 -> 9007199254740996 9007199254740995 -> 9007199254740996
9007199254740996 -> 9007199254740996 9007199254740996 -> 9007199254740996
9007199254740997 -> 9007199254740996 9007199254740997 -> 9007199254740996
... ...
*/ */
it('count above the safe integers as numbers', (done) => { it('count above the safe integers as numbers', () => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
// Set a value to the maximum safe allowed javascript number (2^53) - 1 // Set a value to the maximum safe allowed javascript number (2^53) - 1
client.set(key, Number.MAX_SAFE_INTEGER, helper.isNotError()) client.set(key, Number.MAX_SAFE_INTEGER).then(helper.isString('OK'))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 1)) client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 1))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 2)) client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 2))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 3)) client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 3))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 4)) client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 4))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 5)) client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 5))
client.incr(key, (err, res) => { client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 6))
helper.isNumber(Number.MAX_SAFE_INTEGER + 6)(err, res) client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 7))
assert.strictEqual(typeof res, 'number') client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 8))
}) client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 9))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 7)) return client.incr(key).then(helper.isNumber(Number.MAX_SAFE_INTEGER + 10))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 8))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 9))
client.incr(key, helper.isNumber(Number.MAX_SAFE_INTEGER + 10, done))
}) })
it('count above the safe integers as strings', (done) => { it('count above the safe integers as strings', () => {
args[2].stringNumbers = true args[2].stringNumbers = true
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
// Set a value to the maximum safe allowed javascript number (2^53) // Set a value to the maximum safe allowed javascript number (2^53)
client.set(key, Number.MAX_SAFE_INTEGER, helper.isNotError()) client.set(key, Number.MAX_SAFE_INTEGER).then(helper.isString('OK'))
client.incr(key, helper.isString('9007199254740992')) client.incr(key).then(helper.isString('9007199254740992'))
client.incr(key, helper.isString('9007199254740993')) client.incr(key).then(helper.isString('9007199254740993'))
client.incr(key, helper.isString('9007199254740994')) client.incr(key).then(helper.isString('9007199254740994'))
client.incr(key, helper.isString('9007199254740995')) client.incr(key).then(helper.isString('9007199254740995'))
client.incr(key, helper.isString('9007199254740996')) client.incr(key).then(helper.isString('9007199254740996'))
client.incr(key, (err, res) => { client.incr(key).then(helper.isString('9007199254740997'))
helper.isString('9007199254740997')(err, res) client.incr(key).then(helper.isString('9007199254740998'))
assert.strictEqual(typeof res, 'string') client.incr(key).then(helper.isString('9007199254740999'))
}) client.incr(key).then(helper.isString('9007199254741000'))
client.incr(key, helper.isString('9007199254740998')) return client.incr(key).then(helper.isString('9007199254741001'))
client.incr(key, helper.isString('9007199254740999'))
client.incr(key, helper.isString('9007199254741000'))
client.incr(key, helper.isString('9007199254741001', done))
}) })
}) })
}) })

View File

@@ -10,68 +10,50 @@ describe('The \'info\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushall()
client.flushall(done)
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('update serverInfo after a info command', (done) => { it('update serverInfo after a info command', () => {
client.set('foo', 'bar') client.set('foo', 'bar')
client.info() return client.info().then(() => {
client.select(2, () => {
assert.strictEqual(client.serverInfo.db2, undefined) assert.strictEqual(client.serverInfo.db2, undefined)
client.select(2)
client.set('foo', 'bar')
return client.info().then(() => {
assert.strictEqual(typeof client.serverInfo.db2, 'object')
})
}) })
client.set('foo', 'bar')
client.info()
setTimeout(() => {
assert.strictEqual(typeof client.serverInfo.db2, 'object')
done()
}, 30)
}) })
it('works with optional section provided with and without callback', (done) => { it('works with optional section provided', () => {
client.set('foo', 'bar') client.set('foo', 'bar')
client.info('keyspace') client.info('keyspace')
client.select(2, () => { return client.select(2).then(() => {
assert.strictEqual(Object.keys(client.serverInfo).length, 2, 'Key length should be three') assert.strictEqual(Object.keys(client.serverInfo).length, 2, 'Key length should be three')
assert.strictEqual(typeof client.serverInfo.db0, 'object', 'db0 keyspace should be an object') assert.strictEqual(typeof client.serverInfo.db0, 'object', 'db0 keyspace should be an object')
}) client.info(['keyspace'])
client.info(['keyspace']) client.set('foo', 'bar')
client.set('foo', 'bar') return client.info('all').then((res) => {
client.info('all', (err, res) => { assert(Object.keys(client.serverInfo).length > 3, 'Key length should be way above three')
assert.strictEqual(err, null) assert.strictEqual(typeof client.serverInfo.redis_version, 'string')
assert(Object.keys(client.serverInfo).length > 3, 'Key length should be way above three') assert.strictEqual(typeof client.serverInfo.db2, 'object')
assert.strictEqual(typeof client.serverInfo.redis_version, 'string') })
assert.strictEqual(typeof client.serverInfo.db2, 'object')
done()
}) })
}) })
it('check redis v.2.4 support', (done) => { it('return error after a failure', () => {
const end = helper.callFuncAfter(done, 2) const promise = client.info().then(helper.fail).catch((err) => {
client.internalSendCommand = function (commandObj) {
assert.strictEqual(commandObj.args.length, 0)
assert.strictEqual(commandObj.command, 'info')
end()
}
client.info()
client.info(() => {})
})
it('emit error after a failure', (done) => {
client.info()
client.once('error', (err) => {
assert.strictEqual(err.code, 'UNCERTAIN_STATE') assert.strictEqual(err.code, 'UNCERTAIN_STATE')
assert.strictEqual(err.command, 'INFO') assert.strictEqual(err.command, 'INFO')
done()
}) })
client.stream.destroy() client.stream.destroy()
return promise
}) })
}) })
}) })

View File

@@ -11,24 +11,21 @@ describe('The \'keys\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushall()
client.flushall(done)
})
}) })
it('returns matching keys', (done) => { it('returns matching keys', () => {
client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2'], helper.isString('OK')) client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2']).then(helper.isString('OK'))
client.keys('test keys*', (err, results) => { return client.keys('test keys*').then((results) => {
assert.strictEqual(2, results.length) assert.strictEqual(2, results.length)
assert.ok(~results.indexOf('test keys 1')) assert.ok(~results.indexOf('test keys 1'))
assert.ok(~results.indexOf('test keys 2')) assert.ok(~results.indexOf('test keys 2'))
return done(err)
}) })
}) })
it('handles a large packet size', (done) => { it('handles a large packet size', () => {
const keysValues = [] const keysValues = []
for (let i = 0; i < 200; i++) { for (let i = 0; i < 200; i++) {
@@ -39,24 +36,15 @@ describe('The \'keys\' method', () => {
keysValues.push(keyValue) keysValues.push(keyValue)
} }
client.mset(keysValues.reduce((a, b) => { client.mset(keysValues.reduce((a, b) => a.concat(b))).then(helper.isString('OK'))
return a.concat(b)
}), helper.isString('OK'))
client.keys('multibulk:*', (err, results) => { return client.keys('multibulk:*').then((results) => {
assert.deepEqual(keysValues.map((val) => { assert.deepStrictEqual(keysValues.map((val) => val[0]).sort(), results.sort())
return val[0]
}).sort(), results.sort())
return done(err)
}) })
}) })
it('handles an empty response', (done) => { it('handles an empty response', () => {
client.keys(['users:*'], (err, results) => { return client.keys(['users:*']).then(helper.isDeepEqual([]))
assert.strictEqual(results.length, 0)
assert.ok(Array.isArray(results))
return done(err)
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -10,54 +9,29 @@ describe('The \'mget\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('error', done) client.flushdb()
client.once('ready', () => { return client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3'])
client.flushdb()
client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3'], done)
})
}) })
it('handles fetching multiple keys in argument form', (done) => { it('handles fetching multiple keys in argument form', () => {
client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3'], helper.isString('OK')) client.mset(['mget keys 1', 'mget val 1', 'mget keys 2', 'mget val 2', 'mget keys 3', 'mget val 3']).then(helper.isString('OK'))
client.mget('mget keys 1', 'mget keys 2', 'mget keys 3', (err, results) => { return client.mget('mget keys 1', 'mget keys 2', 'mget keys 3').then(helper.isDeepEqual([
assert.strictEqual(3, results.length) 'mget val 1', 'mget val 2', 'mget val 3'
assert.strictEqual('mget val 1', results[0].toString()) ]))
assert.strictEqual('mget val 2', results[1].toString())
assert.strictEqual('mget val 3', results[2].toString())
return done(err)
})
}) })
it('handles fetching multiple keys via an array', (done) => { it('handles fetching multiple keys via an array', () => {
client.mget(['mget keys 1', 'mget keys 2', 'mget keys 3'], (err, results) => { return client.mget(['mget keys 1', 'mget keys 2', 'mget keys 3']).then(helper.isDeepEqual([
assert.strictEqual('mget val 1', results[0].toString()) 'mget val 1', 'mget val 2', 'mget val 3'
assert.strictEqual('mget val 2', results[1].toString()) ]))
assert.strictEqual('mget val 3', results[2].toString())
return done(err)
})
}) })
it('handles fetching multiple keys, when some keys do not exist', (done) => { it('handles fetching multiple keys, when some keys do not exist', () => {
client.mget('mget keys 1', ['some random shit', 'mget keys 2', 'mget keys 3'], (err, results) => { return client.mget('mget keys 1', ['some random shit', 'mget keys 2', 'mget keys 3']).then(helper.isDeepEqual([
assert.strictEqual(4, results.length) 'mget val 1', null, 'mget val 2', 'mget val 3'
assert.strictEqual('mget val 1', results[0].toString()) ]))
assert.strictEqual(null, results[1])
assert.strictEqual('mget val 2', results[2].toString())
assert.strictEqual('mget val 3', results[3].toString())
return done(err)
})
})
it('handles fetching multiple keys, when some keys do not exist promisified', () => {
return client.mgetAsync('mget keys 1', ['some random shit', 'mget keys 2', 'mget keys 3']).then((results) => {
assert.strictEqual(4, results.length)
assert.strictEqual('mget val 1', results[0].toString())
assert.strictEqual(null, results[1])
assert.strictEqual('mget val 2', results[2].toString())
assert.strictEqual('mget val 3', results[3].toString())
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -15,11 +15,9 @@ describe('The \'monitor\' method', () => {
client.end(true) client.end(true)
}) })
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('connect', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('monitors commands on all redis clients and works in the correct order', (done) => { it('monitors commands on all redis clients and works in the correct order', (done) => {
@@ -38,8 +36,7 @@ describe('The \'monitor\' method', () => {
monitorClient.set('foo', 'bar') monitorClient.set('foo', 'bar')
monitorClient.flushdb() monitorClient.flushdb()
monitorClient.monitor((err, res) => { monitorClient.monitor().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
client.mget('some', 'keys', 'foo', 'bar') client.mget('some', 'keys', 'foo', 'bar')
client.set('json', JSON.stringify({ client.set('json', JSON.stringify({
@@ -48,33 +45,28 @@ describe('The \'monitor\' method', () => {
another: false another: false
})) }))
client.eval('return redis.call(\'set\', \'sha\', \'test\')', 0) client.eval('return redis.call(\'set\', \'sha\', \'test\')', 0)
monitorClient.get('baz', (err, res) => { monitorClient.get('baz').then((res) => {
assert.strictEqual(res, null) assert.strictEqual(res, null)
end(err) end()
}) })
monitorClient.set('foo', 'bar" "s are " " good!"', (err, res) => { monitorClient.set('foo', 'bar" "s are " " good!"').then((res) => {
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
end(err) end()
}) })
monitorClient.mget('foo', 'baz', (err, res) => { monitorClient.mget('foo', 'baz').then((res) => {
assert.strictEqual(res[0], 'bar" "s are " " good!"') assert.strictEqual(res[0], 'bar" "s are " " good!"')
assert.strictEqual(res[1], null) assert.strictEqual(res[1], null)
end(err) end()
})
monitorClient.subscribe('foo', 'baz', (err, res) => {
// The return value might change in v.3
// assert.strictEqual(res, 'baz');
// TODO: Fix the return value of subscribe calls
end(err)
}) })
monitorClient.subscribe('foo', 'baz').then(() => end())
}) })
monitorClient.on('monitor', (time, args, rawOutput) => { monitorClient.on('monitor', (time, args, rawOutput) => {
assert.strictEqual(monitorClient.monitoring, true) assert.strictEqual(monitorClient.monitoring, true)
assert.deepEqual(args, responses.shift()) assert.deepStrictEqual(args, responses.shift())
assert(utils.monitorRegex.test(rawOutput), rawOutput) assert(utils.monitorRegex.test(rawOutput), rawOutput)
if (responses.length === 0) { if (responses.length === 0) {
monitorClient.quit(end) monitorClient.quit().then(() => end())
} }
}) })
}) })
@@ -88,8 +80,7 @@ describe('The \'monitor\' method', () => {
path: '/tmp/redis.sock' path: '/tmp/redis.sock'
}) })
monitorClient.monitor((err, res) => { monitorClient.monitor().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(monitorClient.monitoring, true) assert.strictEqual(monitorClient.monitoring, true)
assert.strictEqual(res.inspect(), Buffer.from('OK').inspect()) assert.strictEqual(res.inspect(), Buffer.from('OK').inspect())
monitorClient.mget('hello', Buffer.from('world')) monitorClient.mget('hello', Buffer.from('world'))
@@ -98,20 +89,20 @@ describe('The \'monitor\' method', () => {
monitorClient.on('monitor', (time, args, rawOutput) => { monitorClient.on('monitor', (time, args, rawOutput) => {
assert.strictEqual(typeof rawOutput, 'string') assert.strictEqual(typeof rawOutput, 'string')
assert(utils.monitorRegex.test(rawOutput), rawOutput) assert(utils.monitorRegex.test(rawOutput), rawOutput)
assert.deepEqual(args, ['mget', 'hello', 'world']) assert.deepStrictEqual(args, ['mget', 'hello', 'world'])
// Quit immediately ends monitoring mode and therefore does not stream back the quit command // Quit immediately ends monitoring mode and therefore does not stream back the quit command
monitorClient.quit(done) monitorClient.quit().then(() => done())
}) })
}) })
it('monitors reconnects properly and works with the offline queue', (done) => { it('monitors reconnects properly and works with the offline queue', (done) => {
let called = false let called = false
client.monitor(helper.isString('OK')) client.monitor().then(helper.isString('OK'))
client.mget('hello', 'world') client.mget('hello', 'world')
client.on('monitor', (time, args, rawOutput) => { client.on('monitor', (time, args, rawOutput) => {
assert.strictEqual(client.monitoring, true) assert.strictEqual(client.monitoring, true)
assert(utils.monitorRegex.test(rawOutput), rawOutput) assert(utils.monitorRegex.test(rawOutput), rawOutput)
assert.deepEqual(args, ['mget', 'hello', 'world']) assert.deepStrictEqual(args, ['mget', 'hello', 'world'])
if (called) { if (called) {
// End after a reconnect // End after a reconnect
return done() return done()
@@ -125,13 +116,13 @@ describe('The \'monitor\' method', () => {
it('monitors reconnects properly and works with the offline queue in a batch statement', (done) => { it('monitors reconnects properly and works with the offline queue in a batch statement', (done) => {
let called = false let called = false
const multi = client.batch() const multi = client.batch()
multi.monitor(helper.isString('OK')) multi.monitor()
multi.mget('hello', 'world') multi.mget('hello', 'world')
multi.exec(helper.isDeepEqual(['OK', [null, null]])) multi.exec().then(helper.isDeepEqual(['OK', [null, null]]))
client.on('monitor', (time, args, rawOutput) => { client.on('monitor', (time, args, rawOutput) => {
assert.strictEqual(client.monitoring, true) assert.strictEqual(client.monitoring, true)
assert(utils.monitorRegex.test(rawOutput), rawOutput) assert(utils.monitorRegex.test(rawOutput), rawOutput)
assert.deepEqual(args, ['mget', 'hello', 'world']) assert.deepStrictEqual(args, ['mget', 'hello', 'world'])
if (called) { if (called) {
// End after a reconnect // End after a reconnect
return done() return done()
@@ -143,7 +134,7 @@ describe('The \'monitor\' method', () => {
}) })
it('monitor activates even if the command could not be processed properly after a reconnect', (done) => { it('monitor activates even if the command could not be processed properly after a reconnect', (done) => {
client.monitor((err, res) => { client.monitor().then(assert, (err) => {
assert.strictEqual(err.code, 'UNCERTAIN_STATE') assert.strictEqual(err.code, 'UNCERTAIN_STATE')
}) })
client.on('error', () => {}) // Ignore error here client.on('error', () => {}) // Ignore error here
@@ -154,8 +145,7 @@ describe('The \'monitor\' method', () => {
end() end()
}) })
client.on('reconnecting', () => { client.on('reconnecting', () => {
client.get('foo', (err, res) => { client.get('foo').then((res) => {
assert(!err)
assert.strictEqual(client.monitoring, true) assert.strictEqual(client.monitoring, true)
end() end()
}) })
@@ -174,10 +164,9 @@ describe('The \'monitor\' method', () => {
] ]
const pub = redis.createClient() const pub = redis.createClient()
pub.on('ready', () => { pub.on('ready', () => {
client.monitor((err, res) => { client.monitor().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
pub.get('foo', helper.isNull()) pub.get('foo').then(helper.isNull())
}) })
client.subscribe('/foo', '/bar') client.subscribe('/foo', '/bar')
client.unsubscribe('/bar') client.unsubscribe('/bar')
@@ -186,21 +175,22 @@ describe('The \'monitor\' method', () => {
client.once('ready', () => { client.once('ready', () => {
pub.publish('/foo', 'hello world') pub.publish('/foo', 'hello world')
}) })
client.set('foo', 'bar', helper.isError()) client.set('foo', 'bar')
.then(assert, helper.isError(/ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/))
client.subscribe('baz') client.subscribe('baz')
client.unsubscribe('baz') client.unsubscribe('baz')
}, 150) }, 150)
let called = false let called = false
client.on('monitor', (time, args, rawOutput) => { client.on('monitor', (time, args, rawOutput) => {
assert.deepEqual(args, responses.shift()) assert.deepStrictEqual(args, responses.shift())
assert(utils.monitorRegex.test(rawOutput), rawOutput) assert(utils.monitorRegex.test(rawOutput), rawOutput)
if (responses.length === 0) { if (responses.length === 0) {
// The publish is called right after the reconnect and the monitor is called before the message is emitted. // The publish is called right after the reconnect and the monitor is called before the message is emitted.
// Therefore we have to wait till the next tick // Therefore we have to wait till the next tick
process.nextTick(() => { process.nextTick(() => {
assert(called) assert(called)
client.quit(done)
pub.end(false) pub.end(false)
client.quit().then(() => done())
}) })
} }
}) })

View File

@@ -21,19 +21,14 @@ describe('The \'mset\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('reports an error', (done) => { it('reports an error', () => {
client.mset(key, value, key2, value2, (err, res) => { return client.mset(key, value, key2, value2)
assert(err.message.match(/The connection is already closed/)) .then(assert, helper.isError(/The connection is already closed/))
done()
})
}) })
}) })
@@ -42,63 +37,18 @@ describe('The \'mset\' method', () => {
beforeEach((done) => { beforeEach((done) => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { client.once('ready', done)
done()
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
describe('and a callback is specified', () => { describe('with valid parameters', () => {
describe('with valid parameters', () => { it('sets the value correctly', () => {
it('sets the value correctly', (done) => { return client.mset(key, value, key2, value2).then(() => {
client.mset(key, value, key2, value2, (err) => { client.get(key).then(helper.isString(value))
if (err) { return client.get(key2).then(helper.isString(value2))
return done(err)
}
client.get(key, helper.isString(value))
client.get(key2, helper.isString(value2, done))
})
})
})
describe('with undefined \'key\' parameter and missing \'value\' parameter', () => {
it('reports an error', (done) => {
client.mset(undefined, (err, res) => {
helper.isError()(err, null)
done()
})
})
})
})
describe('and no callback is specified', () => {
describe('with valid parameters', () => {
it('sets the value correctly', (done) => {
client.mset(key, value2, key2, value)
client.get(key, helper.isString(value2))
client.get(key2, helper.isString(value, done))
})
it('sets the value correctly with array syntax', (done) => {
client.mset([key, value2, key2, value])
client.get(key, helper.isString(value2))
client.get(key2, helper.isString(value, done))
})
})
describe('with undefined \'key\' and missing \'value\' parameter', () => {
// this behavior is different from the 'set' behavior.
it('emits an error', (done) => {
client.on('error', (err) => {
assert.strictEqual(err.message, 'ERR wrong number of arguments for \'mset\' command')
assert.strictEqual(err.name, 'ReplyError')
done()
})
client.mset()
}) })
}) })
}) })

View File

@@ -9,23 +9,21 @@ describe('The \'msetnx\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('if any keys exist entire operation fails', (done) => { it('if any keys exist entire operation fails', () => {
client.mset(['mset1', 'val1', 'mset2', 'val2', 'mset3', 'val3'], helper.isString('OK')) client.mset(['mset1', 'val1', 'mset2', 'val2', 'mset3', 'val3']).then(helper.isString('OK'))
client.msetnx(['mset3', 'val3', 'mset4', 'val4'], helper.isNumber(0)) client.msetnx(['mset3', 'val3', 'mset4', 'val4']).then(helper.isNumber(0))
client.exists(['mset4'], helper.isNumber(0, done)) return client.exists(['mset4']).then(helper.isNumber(0))
}) })
it('sets multiple keys if all keys are not set', (done) => { it('sets multiple keys if all keys are not set', () => {
client.msetnx(['mset3', 'val3', 'mset4', 'val4'], helper.isNumber(1)) client.msetnx(['mset3', 'val3', 'mset4', 'val4']).then(helper.isNumber(1))
client.exists(['mset3'], helper.isNumber(1)) client.exists(['mset3']).then(helper.isNumber(1))
client.exists(['mset3'], helper.isNumber(1, done)) return client.exists(['mset3']).then(helper.isNumber(1))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -10,18 +10,15 @@ describe('The \'randomkey\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns a random key', (done) => { it('returns a random key', () => {
client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2'], helper.isString('OK')) client.mset(['test keys 1', 'test val 1', 'test keys 2', 'test val 2']).then(helper.isString('OK'))
client.randomkey([], (err, results) => { return client.randomkey([]).then((results) => {
assert.strictEqual(true, /test keys.+/.test(results)) assert.strictEqual(true, /test keys.+/.test(results))
return done(err)
}) })
}) })

View File

@@ -9,23 +9,21 @@ describe('The \'rename\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('populates the new key', (done) => { it('populates the new key', () => {
client.set(['foo', 'bar'], helper.isString('OK')) client.set(['foo', 'bar']).then(helper.isString('OK'))
client.rename(['foo', 'new foo'], helper.isString('OK')) client.rename(['foo', 'new foo']).then(helper.isString('OK'))
client.exists(['new foo'], helper.isNumber(1, done)) return client.exists(['new foo']).then(helper.isNumber(1))
}) })
it('removes the old key', (done) => { it('removes the old key', () => {
client.set(['foo', 'bar'], helper.isString('OK')) client.set(['foo', 'bar']).then(helper.isString('OK'))
client.rename(['foo', 'new foo'], helper.isString('OK')) client.rename(['foo', 'new foo']).then(helper.isString('OK'))
client.exists(['foo'], helper.isNumber(0, done)) return client.exists(['foo']).then(helper.isNumber(0))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -9,26 +9,24 @@ describe('The \'renamenx\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('renames the key if target does not yet exist', (done) => { it('renames the key if target does not yet exist', () => {
client.set('foo', 'bar', helper.isString('OK')) client.set('foo', 'bar').then(helper.isString('OK'))
client.renamenx('foo', 'foo2', helper.isNumber(1)) client.renamenx('foo', 'foo2').then(helper.isNumber(1))
client.exists('foo', helper.isNumber(0)) client.exists('foo').then(helper.isNumber(0))
client.exists(['foo2'], helper.isNumber(1, done)) return client.exists(['foo2']).then(helper.isNumber(1))
}) })
it('does not rename the key if the target exists', (done) => { it('does not rename the key if the target exists', () => {
client.set('foo', 'bar', helper.isString('OK')) client.set('foo', 'bar').then(helper.isString('OK'))
client.set('foo2', 'apple', helper.isString('OK')) client.set('foo2', 'apple').then(helper.isString('OK'))
client.renamenx('foo', 'foo2', helper.isNumber(0)) client.renamenx('foo', 'foo2').then(helper.isNumber(0))
client.exists('foo', helper.isNumber(1)) client.exists('foo').then(helper.isNumber(1))
client.exists(['foo2'], helper.isNumber(1, done)) return client.exists(['foo2']).then(helper.isNumber(1))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -3,27 +3,21 @@
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
const assert = require('assert')
describe('The \'rpush\' command', () => { describe('The \'rpush\' command', () => {
helper.allTests((ip, args) => { helper.allTests((ip, args) => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('inserts multiple values at a time into a list', (done) => { it('inserts multiple values at a time into a list', () => {
client.rpush('test', ['list key', 'should be a list']) const list = ['list key', 'should be a list']
client.lrange('test', 0, -1, (err, res) => { client.rpush('test', list)
assert.strictEqual(res[0], 'list key') return client.lrange('test', 0, -1).then(helper.isDeepEqual(list))
assert.strictEqual(res[1], 'should be a list')
done(err)
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -10,45 +10,40 @@ describe('The \'sadd\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('allows a single value to be added to the set', (done) => { it('allows a single value to be added to the set', () => {
client.sadd('set0', 'member0', helper.isNumber(1)) client.sadd('set0', 'member0').then(helper.isNumber(1))
client.smembers('set0', (err, res) => { return client.smembers('set0').then((res) => {
assert.ok(~res.indexOf('member0')) assert.ok(~res.indexOf('member0'))
return done(err)
}) })
}) })
it('does not add the same value to the set twice', (done) => { it('does not add the same value to the set twice', () => {
client.sadd('set0', 'member0', helper.isNumber(1)) client.sadd('set0', 'member0').then(helper.isNumber(1))
client.sadd('set0', 'member0', helper.isNumber(0, done)) return client.sadd('set0', 'member0').then(helper.isNumber(0))
}) })
it('allows multiple values to be added to the set', (done) => { it('allows multiple values to be added to the set', () => {
client.sadd('set0', ['member0', 'member1', 'member2'], helper.isNumber(3)) client.sadd('set0', ['member0', 'member1', 'member2']).then(helper.isNumber(3))
client.smembers('set0', (err, res) => { return client.smembers('set0').then((res) => {
assert.strictEqual(res.length, 3) assert.strictEqual(res.length, 3)
assert.ok(~res.indexOf('member0')) assert.ok(~res.indexOf('member0'))
assert.ok(~res.indexOf('member1')) assert.ok(~res.indexOf('member1'))
assert.ok(~res.indexOf('member2')) assert.ok(~res.indexOf('member2'))
return done(err)
}) })
}) })
it('allows multiple values to be added to the set with a different syntax', (done) => { it('allows multiple values to be added to the set with a different syntax', () => {
client.sadd(['set0', 'member0', 'member1', 'member2'], helper.isNumber(3)) client.sadd(['set0', 'member0', 'member1', 'member2']).then(helper.isNumber(3))
client.smembers('set0', (err, res) => { return client.smembers('set0').then((res) => {
assert.strictEqual(res.length, 3) assert.strictEqual(res.length, 3)
assert.ok(~res.indexOf('member0')) assert.ok(~res.indexOf('member0'))
assert.ok(~res.indexOf('member1')) assert.ok(~res.indexOf('member1'))
assert.ok(~res.indexOf('member2')) assert.ok(~res.indexOf('member2'))
return done(err)
}) })
}) })

View File

@@ -9,16 +9,14 @@ describe('The \'scard\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns the number of values in a set', (done) => { it('returns the number of values in a set', () => {
client.sadd('foo', [1, 2, 3], helper.isNumber(3)) client.sadd('foo', [1, 2, 3]).then(helper.isNumber(3))
client.scard('foo', helper.isNumber(3, done)) return client.scard('foo').then(helper.isNumber(3))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -13,31 +13,29 @@ describe('The \'script\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('loads script with client.script(\'load\')', (done) => { it('loads script with client.script(\'load\')', () => {
client.script('load', command, helper.isString(commandSha, done)) return client.script('load', command).then(helper.isString(commandSha))
}) })
it('allows a loaded script to be evaluated', (done) => { it('allows a loaded script to be evaluated', () => {
client.evalsha(commandSha, 0, helper.isNumber(99, done)) return client.evalsha(commandSha, 0).then(helper.isNumber(99))
}) })
it('allows a script to be loaded as part of a chained transaction', (done) => { it('allows a script to be loaded as part of a chained transaction', () => {
client.multi().script('load', command).exec(helper.isDeepEqual([commandSha], done)) return client.multi().script('load', command).exec(helper.isDeepEqual([commandSha]))
}) })
it('allows a script to be loaded using a transaction\'s array syntax', (done) => { it('allows a script to be loaded using a transaction\'s array syntax', () => {
client.multi([['script', 'load', command]]).exec(helper.isDeepEqual([commandSha], done)) return client.multi([['script', 'load', command]]).exec(helper.isDeepEqual([commandSha]))
}) })
}) })
}) })

View File

@@ -10,30 +10,25 @@ describe('The \'sdiff\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns set difference', (done) => { it('returns set difference', () => {
client.sadd('foo', 'x', helper.isNumber(1)) client.sadd('foo', 'x').then(helper.isNumber(1))
client.sadd('foo', ['a'], helper.isNumber(1)) client.sadd('foo', ['a']).then(helper.isNumber(1))
client.sadd('foo', 'b', helper.isNumber(1)) client.sadd('foo', 'b').then(helper.isNumber(1))
client.sadd(['foo', 'c'], helper.isNumber(1)) client.sadd(['foo', 'c']).then(helper.isNumber(1))
client.sadd(['bar', 'c']).then(helper.isNumber(1))
client.sadd('baz', 'a').then(helper.isNumber(1))
client.sadd('baz', 'd').then(helper.isNumber(1))
client.sadd(['bar', 'c', helper.isNumber(1)]) return client.sdiff('foo', 'bar', 'baz').then((values) => {
client.sadd('baz', 'a', helper.isNumber(1))
client.sadd('baz', 'd', helper.isNumber(1))
client.sdiff('foo', 'bar', 'baz', (err, values) => {
values.sort() values.sort()
assert.strictEqual(values.length, 2) assert.strictEqual(values.length, 2)
assert.strictEqual(values[0], 'b') assert.strictEqual(values[0], 'b')
assert.strictEqual(values[1], 'x') assert.strictEqual(values[1], 'x')
return done(err)
}) })
}) })

View File

@@ -10,30 +10,25 @@ describe('The \'sdiffstore\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('calculates set difference ands stores it in a key', (done) => { it('calculates set difference ands stores it in a key', () => {
client.sadd('foo', 'x', helper.isNumber(1)) client.sadd('foo', 'x').then(helper.isNumber(1))
client.sadd('foo', 'a', helper.isNumber(1)) client.sadd('foo', 'a').then(helper.isNumber(1))
client.sadd('foo', 'b', helper.isNumber(1)) client.sadd('foo', 'b').then(helper.isNumber(1))
client.sadd('foo', 'c', helper.isNumber(1)) client.sadd('foo', 'c').then(helper.isNumber(1))
client.sadd('bar', 'c').then(helper.isNumber(1))
client.sadd('baz', 'a').then(helper.isNumber(1))
client.sadd('baz', 'd').then(helper.isNumber(1))
client.sadd('bar', 'c', helper.isNumber(1)) client.sdiffstore('quux', 'foo', 'bar', 'baz').then(helper.isNumber(2))
client.sadd('baz', 'a', helper.isNumber(1)) return client.smembers('quux').then((values) => {
client.sadd('baz', 'd', helper.isNumber(1))
client.sdiffstore('quux', 'foo', 'bar', 'baz', helper.isNumber(2))
client.smembers('quux', (err, values) => {
const members = values.sort() const members = values.sort()
assert.deepEqual(members, [ 'b', 'x' ]) assert.deepStrictEqual(members, [ 'b', 'x' ])
return done(err)
}) })
}) })

View File

@@ -11,104 +11,60 @@ describe('The \'select\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('returns an error if redis is not connected', (done) => { it('returns an error if redis is not connected', () => {
client.select(1, (err, res) => { return client.select(1).then(assert, helper.isError(/The connection is already closed/))
assert(err.message.match(/The connection is already closed/))
done()
})
}) })
}) })
describe('when connected', () => { describe('when connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('changes the database and calls the callback', (done) => { it('changes the database', () => {
// default value of null means database 0 will be used. // default value of null means database 0 will be used.
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined') assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(1, (err, res) => { return client.select(1).then((res) => {
helper.isNotError()(err, res)
assert.strictEqual(client.selectedDb, 1, 'db should be 1 after select') assert.strictEqual(client.selectedDb, 1, 'db should be 1 after select')
done()
}) })
}) })
describe('and a callback is specified', () => { describe('with a valid db index', () => {
describe('with a valid db index', () => { it('selects the appropriate database', () => {
it('selects the appropriate database', (done) => { assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined') return client.select(1).then(() => {
client.select(1, (err) => { assert.strictEqual(client.selectedDb, 1, 'we should have selected the new valid DB')
assert.strictEqual(err, null)
assert.strictEqual(client.selectedDb, 1, 'we should have selected the new valid DB')
done()
})
})
})
describe('with an invalid db index', () => {
it('returns an error', (done) => {
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(9999, (err) => {
assert.strictEqual(err.code, 'ERR')
assert.strictEqual(err.message, 'ERR invalid DB index')
done()
})
}) })
}) })
}) })
describe('and no callback is specified', () => { describe('with an invalid db index', () => {
describe('with a valid db index', () => { it('returns an error', () => {
it('selects the appropriate database', (done) => { assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined') return client.select(9999).then(assert, (err) => {
client.select(1) assert.strictEqual(err.code, 'ERR')
setTimeout(() => { assert.strictEqual(err.message, 'ERR invalid DB index')
assert.strictEqual(client.selectedDb, 1, 'we should have selected the new valid DB')
done()
}, 25)
})
})
describe('with an invalid db index', () => {
it('emits an error when callback not provided', (done) => {
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.on('error', (err) => {
assert.strictEqual(err.command, 'SELECT')
assert.strictEqual(err.message, 'ERR invalid DB index')
done()
})
client.select(9999)
}) })
}) })
}) })
describe('reconnection occurs', () => { describe('reconnecting', () => {
it('selects the appropriate database after a reconnect', (done) => { it('selects the appropriate database after a reconnect', (done) => {
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined') assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(3) client.select(3)
client.set('foo', 'bar', () => { client.set('foo', 'bar').then(() => client.stream.destroy())
client.stream.destroy()
})
client.once('ready', () => { client.once('ready', () => {
assert.strictEqual(client.selectedDb, 3) assert.strictEqual(client.selectedDb, 3)
assert(typeof client.serverInfo.db3 === 'object') assert(typeof client.serverInfo.db3 === 'object')

View File

@@ -19,145 +19,65 @@ describe('The \'set\' method', () => {
describe('when not connected', () => { describe('when not connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit()
})
client.on('end', done)
}) })
it('reports an error', (done) => { it('reports an error', () => {
client.set(key, value, (err, res) => { return client.set(key, value).then(assert, helper.isError(/The connection is already closed/))
assert(err.message.match(/The connection is already closed/))
done()
})
}) })
}) })
describe('when connected', () => { describe('when connected', () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
describe('and a callback is specified', () => { describe('with valid parameters', () => {
describe('with valid parameters', () => { it('sets the value correctly', () => {
it('sets the value correctly', (done) => { client.set(key, value)
client.set(key, value, (err, res) => { return client.get(key).then(helper.isString(value))
helper.isNotError()(err, res) })
client.get(key, (err, res) => {
helper.isString(value)(err, res)
done()
})
})
})
it('set expire date in seconds', (done) => { it('set expire date in seconds', () => {
client.set('foo', 'bar', 'ex', 10, helper.isString('OK')) client.set('foo', 'bar', 'ex', 10).then(helper.isString('OK'))
client.pttl('foo', (err, res) => { return client.pttl('foo').then((res) => {
assert(res >= 10000 - 50) // Max 50 ms should have passed assert(res >= 10000 - 50) // Max 50 ms should have passed
assert(res <= 10000) // Max possible should be 10.000 assert(res <= 10000) // Max possible should be 10.000
done(err)
})
})
it('set expire date in milliseconds', (done) => {
client.set('foo', 'bar', 'px', 100, helper.isString('OK'))
client.pttl('foo', (err, res) => {
assert(res >= 50) // Max 50 ms should have passed
assert(res <= 100) // Max possible should be 100
done(err)
})
})
it('only set the key if (not) already set', (done) => {
client.set('foo', 'bar', 'NX', helper.isString('OK'))
client.set('foo', 'bar', 'nx', helper.isNull())
client.set('foo', 'bar', 'EX', '10', 'XX', helper.isString('OK'))
client.ttl('foo', (err, res) => {
assert(res >= 9) // Min 9s should be left
assert(res <= 10) // Max 10s should be left
done(err)
})
}) })
}) })
describe('reports an error with invalid parameters', () => { it('set expire date in milliseconds', () => {
it('undefined \'key\' and missing \'value\' parameter', (done) => { client.set('foo', 'bar', 'px', 100).then(helper.isString('OK'))
client.set(undefined, (err, res) => { return client.pttl('foo').then((res) => {
helper.isError()(err, null) assert(res >= 50) // Max 50 ms should have passed
assert.strictEqual(err.command, 'SET') assert(res <= 100) // Max possible should be 100
done()
})
}) })
})
it('empty array as second parameter', (done) => { it('only set the key if (not) already set', () => {
client.set('foo', [], (err, res) => { client.set('foo', 'bar', 'NX').then(helper.isString('OK'))
assert.strictEqual(err.message, 'ERR wrong number of arguments for \'set\' command') client.set('foo', 'bar', 'nx').then(helper.isNull())
done() client.set('foo', 'bar', 'EX', '10', 'XX').then(helper.isString('OK'))
}) return client.ttl('foo').then((res) => {
assert(res >= 9) // Min 9s should be left
assert(res <= 10) // Max 10s should be left
}) })
}) })
}) })
describe('and no callback is specified', () => { describe('reports an error with invalid parameters', () => {
describe('with valid parameters', () => { it('empty array as second parameter', () => {
it('sets the value correctly', (done) => { return client.set('foo', [])
client.set(key, value) .then(assert, helper.isError(/ERR wrong number of arguments for 'set' command/))
client.get(key, helper.isString(value, done))
})
it('sets the value correctly even if the callback is explicitly set to undefined', (done) => {
client.set(key, value, undefined)
client.get(key, helper.isString(value, done))
})
it('sets the value correctly with the array syntax', (done) => {
client.set([key, value])
client.get(key, helper.isString(value, done))
})
})
describe('with undefined \'key\' and missing \'value\' parameter', () => {
it('emits an error without callback', (done) => {
client.on('error', (err) => {
assert.strictEqual(err.message, 'ERR wrong number of arguments for \'set\' command')
assert.strictEqual(err.command, 'SET')
done()
})
client.set(undefined)
})
})
it('returns an error on \'null\'', (done) => {
client.set('foo', null, helper.isError(done))
})
it('emit an error with only the key set', (done) => {
client.on('error', (err) => {
assert.strictEqual(err.message, 'ERR wrong number of arguments for \'set\' command')
done()
})
client.set('foo')
})
it('emit an error without any parameters', (done) => {
client.once('error', (err) => {
assert.strictEqual(err.message, 'ERR wrong number of arguments for \'set\' command')
assert.strictEqual(err.command, 'SET')
done()
})
client.set()
}) })
}) })
}) })

View File

@@ -10,21 +10,15 @@ describe('The \'setex\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('sets a key with an expiry', (done) => { it('sets a key with an expiry', () => {
client.setex(['setex key', '100', 'setex val'], helper.isString('OK')) client.setex(['setex key', '100', 'setex val']).then(helper.isString('OK'))
client.exists(['setex key'], helper.isNumber(1)) client.exists(['setex key']).then(helper.isNumber(1))
client.ttl(['setex key'], (err, ttl) => { return client.ttl(['setex key']).then(assert)
assert.strictEqual(err, null)
assert(ttl > 0)
return done()
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -9,22 +9,20 @@ describe('The \'setnx\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('sets key if it does not have a value', (done) => { it('sets key if it does not have a value', () => {
client.setnx('foo', 'banana', helper.isNumber(1)) client.setnx('foo', 'banana').then(helper.isNumber(1))
client.get('foo', helper.isString('banana', done)) return client.get('foo').then(helper.isString('banana'))
}) })
it('does not set key if it already has a value', (done) => { it('does not set key if it already has a value', () => {
client.set('foo', 'bar', helper.isString('OK')) client.set('foo', 'bar').then(helper.isString('OK'))
client.setnx('foo', 'banana', helper.isNumber(0)) client.setnx('foo', 'banana').then(helper.isNumber(0))
client.get('foo', helper.isString('bar', done)) return client.get('foo').then(helper.isString('bar'))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -10,46 +10,42 @@ describe('The \'sinter\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('handles two sets being intersected', (done) => { it('handles two sets being intersected', () => {
client.sadd('sa', 'a', helper.isNumber(1)) client.sadd('sa', 'a').then(helper.isNumber(1))
client.sadd('sa', 'b', helper.isNumber(1)) client.sadd('sa', 'b').then(helper.isNumber(1))
client.sadd('sa', 'c', helper.isNumber(1)) client.sadd('sa', 'c').then(helper.isNumber(1))
client.sadd('sb', 'b', helper.isNumber(1)) client.sadd('sb', 'b').then(helper.isNumber(1))
client.sadd('sb', 'c', helper.isNumber(1)) client.sadd('sb', 'c').then(helper.isNumber(1))
client.sadd('sb', 'd', helper.isNumber(1)) client.sadd('sb', 'd').then(helper.isNumber(1))
client.sinter('sa', 'sb', (err, intersection) => { return client.sinter('sa', 'sb').then((intersection) => {
assert.strictEqual(intersection.length, 2) assert.strictEqual(intersection.length, 2)
assert.deepEqual(intersection.sort(), [ 'b', 'c' ]) assert.deepStrictEqual(intersection.sort(), [ 'b', 'c' ])
return done(err)
}) })
}) })
it('handles three sets being intersected', (done) => { it('handles three sets being intersected', () => {
client.sadd('sa', 'a', helper.isNumber(1)) client.sadd('sa', 'a').then(helper.isNumber(1))
client.sadd('sa', 'b', helper.isNumber(1)) client.sadd('sa', 'b').then(helper.isNumber(1))
client.sadd('sa', 'c', helper.isNumber(1)) client.sadd('sa', 'c').then(helper.isNumber(1))
client.sadd('sb', 'b', helper.isNumber(1)) client.sadd('sb', 'b').then(helper.isNumber(1))
client.sadd('sb', 'c', helper.isNumber(1)) client.sadd('sb', 'c').then(helper.isNumber(1))
client.sadd('sb', 'd', helper.isNumber(1)) client.sadd('sb', 'd').then(helper.isNumber(1))
client.sadd('sc', 'c', helper.isNumber(1)) client.sadd('sc', 'c').then(helper.isNumber(1))
client.sadd('sc', 'd', helper.isNumber(1)) client.sadd('sc', 'd').then(helper.isNumber(1))
client.sadd('sc', 'e', helper.isNumber(1)) client.sadd('sc', 'e').then(helper.isNumber(1))
client.sinter('sa', 'sb', 'sc', (err, intersection) => { return client.sinter('sa', 'sb', 'sc').then((intersection) => {
assert.strictEqual(intersection.length, 1) assert.strictEqual(intersection.length, 1)
assert.strictEqual(intersection[0], 'c') assert.strictEqual(intersection[0], 'c')
return done(err)
}) })
}) })

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -10,32 +9,27 @@ describe('The \'sinterstore\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('calculates set intersection and stores it in a key', (done) => { it('calculates set intersection and stores it in a key', () => {
client.sadd('sa', 'a', helper.isNumber(1)) client.sadd('sa', 'a').then(helper.isNumber(1))
client.sadd('sa', 'b', helper.isNumber(1)) client.sadd('sa', 'b').then(helper.isNumber(1))
client.sadd('sa', 'c', helper.isNumber(1)) client.sadd('sa', 'c').then(helper.isNumber(1))
client.sadd('sb', 'b', helper.isNumber(1)) client.sadd('sb', 'b').then(helper.isNumber(1))
client.sadd('sb', 'c', helper.isNumber(1)) client.sadd('sb', 'c').then(helper.isNumber(1))
client.sadd('sb', 'd', helper.isNumber(1)) client.sadd('sb', 'd').then(helper.isNumber(1))
client.sadd('sc', 'c', helper.isNumber(1)) client.sadd('sc', 'c').then(helper.isNumber(1))
client.sadd('sc', 'd', helper.isNumber(1)) client.sadd('sc', 'd').then(helper.isNumber(1))
client.sadd('sc', 'e', helper.isNumber(1)) client.sadd('sc', 'e').then(helper.isNumber(1))
client.sinterstore('foo', 'sa', 'sb', 'sc', helper.isNumber(1)) client.sinterstore('foo', 'sa', 'sb', 'sc').then(helper.isNumber(1))
client.smembers('foo', (err, members) => { return client.smembers('foo').then(helper.isDeepEqual(['c']))
assert.deepEqual(members, [ 'c' ])
return done(err)
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -9,20 +9,18 @@ describe('The \'sismember\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns 0 if the value is not in the set', (done) => { it('returns 0 if the value is not in the set', () => {
client.sismember('foo', 'banana', helper.isNumber(0, done)) return client.sismember('foo', 'banana').then(helper.isNumber(0))
}) })
it('returns 1 if the value is in the set', (done) => { it('returns 1 if the value is in the set', () => {
client.sadd('foo', 'banana', helper.isNumber(1)) client.sadd('foo', 'banana').then(helper.isNumber(1))
client.sismember('foo', 'banana', helper.isNumber(1, done)) return client.sismember('foo', 'banana').then(helper.isNumber(1))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -10,24 +10,21 @@ describe('The \'slowlog\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('logs operations in slowlog', (done) => { it('logs operations in slowlog', () => {
client.config('set', 'slowlog-log-slower-than', 0, helper.isString('OK')) client.config('set', 'slowlog-log-slower-than', 0).then(helper.isString('OK'))
client.slowlog('reset', helper.isString('OK')) client.slowlog('reset').then(helper.isString('OK'))
client.set('foo', 'bar', helper.isString('OK')) client.set('foo', 'bar').then(helper.isString('OK'))
client.get('foo', helper.isString('bar')) client.get('foo').then(helper.isString('bar'))
client.slowlog('get', (err, res) => { return client.slowlog('get').then((res) => {
assert.strictEqual(res.length, 3) assert.strictEqual(res.length, 3)
assert.strictEqual(res[0][3].length, 2) assert.strictEqual(res[0][3].length, 2)
assert.deepEqual(res[1][3], ['set', 'foo', 'bar']) assert.deepStrictEqual(res[1][3], ['set', 'foo', 'bar'])
assert.deepEqual(res[2][3], ['slowlog', 'reset']) assert.deepStrictEqual(res[2][3], ['slowlog', 'reset'])
return done(err)
}) })
}) })

View File

@@ -10,21 +10,18 @@ describe('The \'smembers\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns all values in a set', (done) => { it('returns all values in a set', () => {
client.sadd('foo', 'x', helper.isNumber(1)) client.sadd('foo', 'x').then(helper.isNumber(1))
client.sadd('foo', 'y', helper.isNumber(1)) client.sadd('foo', 'y').then(helper.isNumber(1))
client.smembers('foo', (err, values) => { return client.smembers('foo').then((values) => {
assert.strictEqual(values.length, 2) assert.strictEqual(values.length, 2)
const members = values.sort() const members = values.sort()
assert.deepEqual(members, [ 'x', 'y' ]) assert.deepStrictEqual(members, [ 'x', 'y' ])
return done(err)
}) })
}) })

View File

@@ -9,25 +9,23 @@ describe('The \'smove\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('moves a value to a set that does not yet exist', (done) => { it('moves a value to a set that does not yet exist', () => {
client.sadd('foo', 'x', helper.isNumber(1)) client.sadd('foo', 'x').then(helper.isNumber(1))
client.smove('foo', 'bar', 'x', helper.isNumber(1)) client.smove('foo', 'bar', 'x').then(helper.isNumber(1))
client.sismember('foo', 'x', helper.isNumber(0)) client.sismember('foo', 'x').then(helper.isNumber(0))
client.sismember('bar', 'x', helper.isNumber(1, done)) return client.sismember('bar', 'x').then(helper.isNumber(1))
}) })
it('does not move a value if it does not exist in the first set', (done) => { it('does not move a value if it does not exist in the first set', () => {
client.sadd('foo', 'x', helper.isNumber(1)) client.sadd('foo', 'x').then(helper.isNumber(1))
client.smove('foo', 'bar', 'y', helper.isNumber(0)) client.smove('foo', 'bar', 'y').then(helper.isNumber(0))
client.sismember('foo', 'y', helper.isNumber(0)) client.sismember('foo', 'y').then(helper.isNumber(0))
client.sismember('bar', 'y', helper.isNumber(0, done)) return client.sismember('bar', 'y').then(helper.isNumber(0))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -1,11 +1,10 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
function setupData (client, done) { function setupData (client) {
client.rpush('y', 'd') client.rpush('y', 'd')
client.rpush('y', 'b') client.rpush('y', 'b')
client.rpush('y', 'a') client.rpush('y', 'a')
@@ -29,7 +28,7 @@ function setupData (client, done) {
client.set('p2', 'qux') client.set('p2', 'qux')
client.set('p3', 'bux') client.set('p3', 'bux')
client.set('p4', 'lux') client.set('p4', 'lux')
client.set('p9', 'tux', done) return client.set('p9', 'tux')
} }
describe('The \'sort\' method', () => { describe('The \'sort\' method', () => {
@@ -37,85 +36,57 @@ describe('The \'sort\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('error', done) client.flushdb()
client.once('connect', () => { return setupData(client)
client.flushdb()
setupData(client, done)
})
}) })
describe('alphabetical', () => { describe('alphabetical', () => {
it('sorts in ascending alphabetical order', (done) => { it('sorts in ascending alphabetical order', () => {
client.sort('y', 'asc', 'alpha', (err, sorted) => { return client.sort('y', 'asc', 'alpha').then(helper.isDeepEqual(['a', 'b', 'c', 'd']))
assert.deepEqual(sorted, ['a', 'b', 'c', 'd'])
return done(err)
})
}) })
it('sorts in descending alphabetical order', (done) => { it('sorts in descending alphabetical order', () => {
client.sort('y', 'desc', 'alpha', (err, sorted) => { return client.sort('y', 'desc', 'alpha').then(helper.isDeepEqual(['d', 'c', 'b', 'a']))
assert.deepEqual(sorted, ['d', 'c', 'b', 'a'])
return done(err)
})
}) })
}) })
describe('numeric', () => { describe('numeric', () => {
it('sorts in ascending numeric order', (done) => { it('sorts in ascending numeric order', () => {
client.sort('x', 'asc', (err, sorted) => { return client.sort('x', 'asc').then(helper.isDeepEqual(['2', '3', '4', '9']))
assert.deepEqual(sorted, [2, 3, 4, 9])
return done(err)
})
}) })
it('sorts in descending numeric order', (done) => { it('sorts in descending numeric order', () => {
client.sort('x', 'desc', (err, sorted) => { return client.sort('x', 'desc').then(helper.isDeepEqual(['9', '4', '3', '2']))
assert.deepEqual(sorted, [9, 4, 3, 2])
return done(err)
})
}) })
}) })
describe('pattern', () => { describe('pattern', () => {
it('handles sorting with a pattern', (done) => { it('handles sorting with a pattern', () => {
client.sort('x', 'by', 'w*', 'asc', (err, sorted) => { return client.sort('x', 'by', 'w*', 'asc').then(helper.isDeepEqual(['3', '9', '4', '2']))
assert.deepEqual(sorted, [3, 9, 4, 2])
return done(err)
})
}) })
it('handles sorting with a \'by\' pattern and 1 \'get\' pattern', (done) => { it('handles sorting with a \'by\' pattern and 1 \'get\' pattern', () => {
client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', (err, sorted) => { return client.sort('x', 'by', 'w*', 'asc', 'get', 'o*')
assert.deepEqual(sorted, ['foo', 'bar', 'baz', 'buz']) .then(helper.isDeepEqual(['foo', 'bar', 'baz', 'buz']))
return done(err)
})
}) })
it('handles sorting with a \'by\' pattern and 2 \'get\' patterns', (done) => { it('handles sorting with a \'by\' pattern and 2 \'get\' patterns', () => {
client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', (err, sorted) => { return client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*')
assert.deepEqual(sorted, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']) .then(helper.isDeepEqual(['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']))
return done(err)
})
}) })
it('handles sorting with a \'by\' pattern and 2 \'get\' patterns with the array syntax', (done) => { it('handles sorting with a \'by\' pattern and 2 \'get\' patterns with the array syntax', () => {
client.sort(['x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*'], (err, sorted) => { return client.sort(['x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*'])
assert.deepEqual(sorted, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']) .then(helper.isDeepEqual(['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']))
return done(err)
})
}) })
it('sorting with a \'by\' pattern and 2 \'get\' patterns and stores results', (done) => { it('sorting with a \'by\' pattern and 2 \'get\' patterns and stores results', () => {
client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', 'store', 'bacon', (err) => { client.sort('x', 'by', 'w*', 'asc', 'get', 'o*', 'get', 'p*', 'store', 'bacon')
if (err) return done(err)
})
client.lrange('bacon', 0, -1, (err, values) => { return client.lrange('bacon', 0, -1)
assert.deepEqual(values, ['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']) .then(helper.isDeepEqual(['foo', 'bux', 'bar', 'tux', 'baz', 'lux', 'buz', 'qux']))
return done(err)
})
}) })
}) })

View File

@@ -1,6 +1,5 @@
'use strict' 'use strict'
const assert = require('assert')
const config = require('../lib/config') const config = require('../lib/config')
const helper = require('../helper') const helper = require('../helper')
const redis = config.redis const redis = config.redis
@@ -10,22 +9,16 @@ describe('The \'spop\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns a random element from the set', (done) => { it('returns a random element from the set', () => {
client.sadd('zzz', 'member0', helper.isNumber(1)) client.sadd('zzz', 'member0').then(helper.isNumber(1))
client.scard('zzz', helper.isNumber(1)) client.scard('zzz').then(helper.isNumber(1))
client.spop('zzz').then(helper.isString('member0'))
client.spop('zzz', (err, value) => { return client.scard('zzz').then(helper.isNumber(0))
if (err) return done(err)
assert.strictEqual(value, 'member0')
client.scard('zzz', helper.isNumber(0, done))
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -10,52 +10,47 @@ describe('The \'srem\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('removes a value', (done) => { it('removes a value', () => {
client.sadd('set0', 'member0', helper.isNumber(1)) client.sadd('set0', 'member0').then(helper.isNumber(1))
client.srem('set0', 'member0', helper.isNumber(1)) client.srem('set0', 'member0').then(helper.isNumber(1))
client.scard('set0', helper.isNumber(0, done)) return client.scard('set0').then(helper.isNumber(0))
}) })
it('handles attempting to remove a missing value', (done) => { it('handles attempting to remove a missing value', () => {
client.srem('set0', 'member0', helper.isNumber(0, done)) return client.srem('set0', 'member0').then(helper.isNumber(0))
}) })
it('allows multiple values to be removed', (done) => { it('allows multiple values to be removed', () => {
client.sadd('set0', ['member0', 'member1', 'member2'], helper.isNumber(3)) client.sadd('set0', ['member0', 'member1', 'member2']).then(helper.isNumber(3))
client.srem('set0', ['member1', 'member2'], helper.isNumber(2)) client.srem('set0', ['member1', 'member2']).then(helper.isNumber(2))
client.smembers('set0', (err, res) => { return client.smembers('set0').then((res) => {
assert.strictEqual(res.length, 1) assert.strictEqual(res.length, 1)
assert.ok(~res.indexOf('member0')) assert.ok(~res.indexOf('member0'))
return done(err)
}) })
}) })
it('allows multiple values to be removed with sendCommand', (done) => { it('allows multiple values to be removed with sendCommand', () => {
client.sendCommand('sadd', ['set0', 'member0', 'member1', 'member2'], helper.isNumber(3)) client.sendCommand('sadd', ['set0', 'member0', 'member1', 'member2']).then(helper.isNumber(3))
client.sendCommand('srem', ['set0', 'member1', 'member2'], helper.isNumber(2)) client.sendCommand('srem', ['set0', 'member1', 'member2']).then(helper.isNumber(2))
client.smembers('set0', (err, res) => { return client.smembers('set0').then((res) => {
assert.strictEqual(res.length, 1) assert.strictEqual(res.length, 1)
assert.ok(~res.indexOf('member0')) assert.ok(~res.indexOf('member0'))
return done(err)
}) })
}) })
it('handles a value missing from the set of values being removed', (done) => { it('handles a value missing from the set of values being removed', () => {
client.sadd(['set0', 'member0', 'member1', 'member2'], helper.isNumber(3)) client.sadd(['set0', 'member0', 'member1', 'member2']).then(helper.isNumber(3))
client.srem(['set0', 'member3', 'member4'], helper.isNumber(0)) client.srem(['set0', 'member3', 'member4']).then(helper.isNumber(0))
client.smembers('set0', (err, res) => { return client.smembers('set0').then((res) => {
assert.strictEqual(res.length, 3) assert.strictEqual(res.length, 3)
assert.ok(~res.indexOf('member0')) assert.ok(~res.indexOf('member0'))
assert.ok(~res.indexOf('member1')) assert.ok(~res.indexOf('member1'))
assert.ok(~res.indexOf('member2')) assert.ok(~res.indexOf('member2'))
return done(err)
}) })
}) })

View File

@@ -10,29 +10,26 @@ describe('The \'sunion\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns the union of a group of sets', (done) => { it('returns the union of a group of sets', () => {
client.sadd('sa', 'a', helper.isNumber(1)) client.sadd('sa', 'a').then(helper.isNumber(1))
client.sadd('sa', 'b', helper.isNumber(1)) client.sadd('sa', 'b').then(helper.isNumber(1))
client.sadd('sa', 'c', helper.isNumber(1)) client.sadd('sa', 'c').then(helper.isNumber(1))
client.sadd('sb', 'b', helper.isNumber(1)) client.sadd('sb', 'b').then(helper.isNumber(1))
client.sadd('sb', 'c', helper.isNumber(1)) client.sadd('sb', 'c').then(helper.isNumber(1))
client.sadd('sb', 'd', helper.isNumber(1)) client.sadd('sb', 'd').then(helper.isNumber(1))
client.sadd('sc', 'c', helper.isNumber(1)) client.sadd('sc', 'c').then(helper.isNumber(1))
client.sadd('sc', 'd', helper.isNumber(1)) client.sadd('sc', 'd').then(helper.isNumber(1))
client.sadd('sc', 'e', helper.isNumber(1)) client.sadd('sc', 'e').then(helper.isNumber(1))
client.sunion('sa', 'sb', 'sc', (err, union) => { return client.sunion('sa', 'sb', 'sc').then((union) => {
assert.deepEqual(union.sort(), ['a', 'b', 'c', 'd', 'e']) assert.deepStrictEqual(union.sort(), ['a', 'b', 'c', 'd', 'e'])
return done(err)
}) })
}) })

View File

@@ -10,32 +10,29 @@ describe('The \'sunionstore\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('stores the result of a union', (done) => { it('stores the result of a union', () => {
client.sadd('sa', 'a', helper.isNumber(1)) client.sadd('sa', 'a').then(helper.isNumber(1))
client.sadd('sa', 'b', helper.isNumber(1)) client.sadd('sa', 'b').then(helper.isNumber(1))
client.sadd('sa', 'c', helper.isNumber(1)) client.sadd('sa', 'c').then(helper.isNumber(1))
client.sadd('sb', 'b', helper.isNumber(1)) client.sadd('sb', 'b').then(helper.isNumber(1))
client.sadd('sb', 'c', helper.isNumber(1)) client.sadd('sb', 'c').then(helper.isNumber(1))
client.sadd('sb', 'd', helper.isNumber(1)) client.sadd('sb', 'd').then(helper.isNumber(1))
client.sadd('sc', 'c', helper.isNumber(1)) client.sadd('sc', 'c').then(helper.isNumber(1))
client.sadd('sc', 'd', helper.isNumber(1)) client.sadd('sc', 'd').then(helper.isNumber(1))
client.sadd('sc', 'e', helper.isNumber(1)) client.sadd('sc', 'e').then(helper.isNumber(1))
client.sunionstore('foo', 'sa', 'sb', 'sc', helper.isNumber(5)) client.sunionstore('foo', 'sa', 'sb', 'sc').then(helper.isNumber(5))
client.smembers('foo', (err, members) => { return client.smembers('foo').then((members) => {
assert.strictEqual(members.length, 5) assert.strictEqual(members.length, 5)
assert.deepEqual(members.sort(), ['a', 'b', 'c', 'd', 'e']) assert.deepStrictEqual(members.sort(), ['a', 'b', 'c', 'd', 'e'])
return done(err)
}) })
}) })

View File

@@ -10,20 +10,17 @@ describe('The \'ttl\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('returns the current ttl on a key', (done) => { it('returns the current ttl on a key', () => {
client.set(['ttl key', 'ttl val'], helper.isString('OK')) client.set(['ttl key', 'ttl val']).then(helper.isString('OK'))
client.expire(['ttl key', '100'], helper.isNumber(1)) client.expire(['ttl key', '100']).then(helper.isNumber(1))
client.ttl(['ttl key'], (err, ttl) => { return client.ttl(['ttl key']).then((ttl) => {
assert(ttl >= 99) assert(ttl >= 99)
assert(ttl <= 100) assert(ttl <= 100)
done(err)
}) })
}) })

View File

@@ -9,40 +9,38 @@ describe('The \'type\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('reports string type', (done) => { it('reports string type', () => {
client.set(['string key', 'should be a string'], helper.isString('OK')) client.set(['string key', 'should be a string']).then(helper.isString('OK'))
client.type(['string key'], helper.isString('string', done)) return client.type(['string key']).then(helper.isString('string'))
}) })
it('reports list type', (done) => { it('reports list type', () => {
client.rpush(['list key', 'should be a list'], helper.isNumber(1)) client.rpush(['list key', 'should be a list']).then(helper.isNumber(1))
client.type(['list key'], helper.isString('list', done)) return client.type(['list key']).then(helper.isString('list'))
}) })
it('reports set type', (done) => { it('reports set type', () => {
client.sadd(['set key', 'should be a set'], helper.isNumber(1)) client.sadd(['set key', 'should be a set']).then(helper.isNumber(1))
client.type(['set key'], helper.isString('set', done)) return client.type(['set key']).then(helper.isString('set'))
}) })
it('reports zset type', (done) => { it('reports zset type', () => {
client.zadd('zset key', ['10.0', 'should be a zset'], helper.isNumber(1)) client.zadd('zset key', ['10.0', 'should be a zset']).then(helper.isNumber(1))
client.type(['zset key'], helper.isString('zset', done)) return client.type(['zset key']).then(helper.isString('zset'))
}) })
it('reports hash type', (done) => { it('reports hash type', () => {
client.hset('hash key', 'hashtest', 'should be a hash', helper.isNumber(1)) client.hset('hash key', 'hashtest', 'should be a hash').then(helper.isNumber(1))
client.type(['hash key'], helper.isString('hash', done)) return client.type(['hash key']).then(helper.isString('hash'))
}) })
it('reports none for null key', (done) => { it('reports none for null key', () => {
client.type('not here yet', helper.isString('none', done)) return client.type('not here yet').then(helper.isString('none'))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -12,37 +12,34 @@ describe('The \'watch\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('does not execute transaction if watched key was modified prior to execution', (done) => { it('does not execute transaction if watched key was modified prior to execution', () => {
client.watch(watched) client.watch(watched)
client.incr(watched) client.incr(watched)
const multi = client.multi() const multi = client.multi()
multi.incr(watched) multi.incr(watched)
multi.exec(helper.isNull(done)) return multi.exec().then(helper.isNull())
}) })
it('successfully modifies other keys independently of transaction', (done) => { it('successfully modifies other keys independently of transaction', () => {
client.set('unwatched', 200) client.set('unwatched', 200)
client.set(watched, 0) client.set(watched, 0)
client.watch(watched) client.watch(watched)
client.incr(watched) client.incr(watched)
client.multi().incr(watched).exec((err, replies) => { return client.multi().incr(watched).exec().then((replies) => {
assert.strictEqual(err, null)
assert.strictEqual(replies, null, 'Aborted transaction multi-bulk reply should be null.') assert.strictEqual(replies, null, 'Aborted transaction multi-bulk reply should be null.')
client.get('unwatched', helper.isString('200', done)) return client.get('unwatched').then(helper.isString('200'))
}) })
}) })
}) })

View File

@@ -10,31 +10,27 @@ describe('The \'zadd\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('reports an error', function (done) { it('reports an error', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client.zadd('infinity', [+'5t', 'should not be possible'], helper.isError(done)) return client.zadd('infinity', [+'5t', 'should not be possible']).then(assert, helper.isError())
}) })
it('return inf / -inf', function (done) { it('return inf / -inf', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
helper.serverVersionAtLeast.call(this, client, [3, 0, 2]) helper.serverVersionAtLeast.call(this, client, [3, 0, 2])
client.zadd('infinity', [+Infinity, 'should be inf'], helper.isNumber(1)) client.zadd('infinity', [+Infinity, 'should be inf']).then(helper.isNumber(1))
client.zadd('infinity', ['inf', 'should be also be inf'], helper.isNumber(1)) client.zadd('infinity', ['inf', 'should be also be inf']).then(helper.isNumber(1))
client.zadd('infinity', -Infinity, 'should be negative inf', helper.isNumber(1)) client.zadd('infinity', -Infinity, 'should be negative inf').then(helper.isNumber(1))
client.zadd('infinity', [99999999999999999999999, 'should not be inf'], helper.isNumber(1)) client.zadd('infinity', [99999999999999999999999, 'should not be inf']).then(helper.isNumber(1))
client.zrange('infinity', 0, -1, 'WITHSCORES', (err, res) => { return client.zrange('infinity', 0, -1, 'WITHSCORES').then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res[5], 'inf') assert.strictEqual(res[5], 'inf')
assert.strictEqual(res[1], '-inf') assert.strictEqual(res[1], '-inf')
assert.strictEqual(res[3], '9.9999999999999992e+22') assert.strictEqual(res[3], '9.9999999999999992e+22')
done()
}) })
}) })

View File

@@ -10,14 +10,12 @@ describe('The \'zscan\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('return values', function (done) { it('return values', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
helper.serverVersionAtLeast.call(this, client, [2, 8, 0]) helper.serverVersionAtLeast.call(this, client, [2, 8, 0])
const hash = {} const hash = {}
@@ -31,11 +29,9 @@ describe('The \'zscan\' method', () => {
client.hmset('hash:1', hash) client.hmset('hash:1', hash)
client.sadd('set:1', set) client.sadd('set:1', set)
client.zadd(zset) client.zadd(zset)
client.zscan('zset:1', 0, 'MATCH', '*', 'COUNT', 500, (err, res) => { return client.zscan('zset:1', 0, 'MATCH', '*', 'COUNT', 500).then((res) => {
assert(!err)
assert.strictEqual(res.length, 2) assert.strictEqual(res.length, 2)
assert.strictEqual(res[1].length, 1000) assert.strictEqual(res[1].length, 1000)
done()
}) })
}) })

View File

@@ -9,16 +9,14 @@ describe('The \'zscore\' method', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client let client
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
it('should return the score of member in the sorted set at key', (done) => { it('should return the score of member in the sorted set at key', () => {
client.zadd('myzset', 1, 'one') client.zadd('myzset', 1, 'one')
client.zscore('myzset', 'one', helper.isString('1', done)) return client.zscore('myzset', 'one').then(helper.isString('1'))
}) })
afterEach(() => { afterEach(() => {

View File

@@ -20,8 +20,8 @@ if (process.platform !== 'win32') {
}) })
}) })
before((done) => { before(() => {
if (helper.redisProcess().spawnFailed()) return done() if (helper.redisProcess().spawnFailed()) return
master = redis.createClient({ master = redis.createClient({
password: 'porkchopsandwiches' password: 'porkchopsandwiches'
}) })
@@ -32,7 +32,7 @@ if (process.platform !== 'win32') {
// Write some data in the redis instance, so there's something to sync // Write some data in the redis instance, so there's something to sync
multi.set(`foo${i}`, `bar${new Array(500).join(Math.random())}`) multi.set(`foo${i}`, `bar${new Array(500).join(Math.random())}`)
} }
multi.exec(done) return multi.exec()
}) })
it('sync process and no master should delay ready being emitted for slaves', function (done) { it('sync process and no master should delay ready being emitted for slaves', function (done) {
@@ -50,12 +50,13 @@ if (process.platform !== 'win32') {
const tmp = slave.info.bind(slave) const tmp = slave.info.bind(slave)
let i = 0 let i = 0
slave.info = function (err, res) { slave.info = function () {
i++ i++
tmp(err, res) const promise = tmp()
if (!firstInfo || Object.keys(firstInfo).length === 0) { if (!firstInfo || Object.keys(firstInfo).length === 0) {
firstInfo = slave.serverInfo firstInfo = slave.serverInfo
} }
return promise
} }
slave.on('connect', () => { slave.on('connect', () => {
@@ -68,9 +69,9 @@ if (process.platform !== 'win32') {
assert.strictEqual(this.serverInfo.master_link_status, 'up') assert.strictEqual(this.serverInfo.master_link_status, 'up')
assert.strictEqual(firstInfo.master_link_status, 'down') assert.strictEqual(firstInfo.master_link_status, 'down')
assert(i > 1) assert(i > 1)
this.get('foo300', (err, res) => { this.get('foo300').then((res) => {
assert.strictEqual(res.substr(0, 3), 'bar') assert.strictEqual(res.substr(0, 3), 'bar')
end(err) end()
}) })
}) })
@@ -85,10 +86,10 @@ if (process.platform !== 'win32') {
const end = helper.callFuncAfter(done, 3) const end = helper.callFuncAfter(done, 3)
rp.stop(end) rp.stop(end)
slave.end(true) slave.end(true)
master.flushdb((err) => { master.flushdb().then(() => {
end(err) end()
master.end(true) master.end(true)
}) }).catch(done)
helper.stopRedis(() => { helper.stopRedis(() => {
helper.startRedis('./conf/redis.conf', end) helper.startRedis('./conf/redis.conf', end)
}) })

View File

@@ -17,7 +17,7 @@ describe('connection tests', () => {
}) })
it('unofficially support for a private stream', () => { it('unofficially support for a private stream', () => {
// While using a private stream, reconnection and other features are not going to work properly. // While using a private stream, reconnecting and other features are not going to work properly.
// 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()
@@ -37,53 +37,49 @@ describe('connection tests', () => {
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,
port: 9999, port: 9999,
retryStrategy (options) { retryStrategy (options) {
client.quit((err, res) => { client.quit().then((res) => {
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
assert.strictEqual(called++, -1) assert.strictEqual(called++, -1)
setTimeout(done, 25) setTimeout(done, 25)
}) }).catch(helper.fail)
assert.strictEqual(called++, 0) assert.strictEqual(called++, 0)
return 5 return 5
} }
}) })
client.set('foo', 'bar', (err, res) => { 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 = -1 called = -1
}) })
}) })
it('calling quit while the connection is down should not end in reconnecting version b', (done) => { 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', (err, res) => { 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
}) })
client.quit((err, res) => { return client.quit().then((res) => {
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
assert(called) assert(called)
done()
}) })
}) })
it('calling quit while the connection is down without offline queue should end the connection right away', (done) => { 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', (err, res) => { client.set('foo', 'bar').catch((err) => {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection is not yet established and the offline queue is deactivated.') assert.strictEqual(err.message, 'SET can\'t be processed. The connection is not yet established and the offline queue is deactivated.')
called = true called = true
}) })
client.quit((err, res) => { return client.quit().then((res) => {
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
assert(called) assert(called)
done()
}) })
}) })
@@ -93,44 +89,43 @@ describe('connection tests', () => {
enableOfflineQueue: false enableOfflineQueue: false
}) })
client.on('ready', () => { client.on('ready', () => {
client.set('foo', 'bar', (err, res) => { client.set('foo', 'bar').then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
called = true called = true
}) })
client.quit((err, res) => { client.quit().then((res) => {
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
assert(called) assert(called)
done() done()
}) }).catch(done)
}) })
}) })
it('do not quit before connected or a connection issue is detected', (done) => { it('do not quit before connected or a connection issue is detected', () => {
client = redis.createClient() client = redis.createClient()
client.set('foo', 'bar', helper.isString('OK')) return Promise.all([
client.quit(done) client.set('foo', 'bar').then(helper.isString('OK')),
client.quit()
])
}) })
it('quit "succeeds" even if the client connection is closed while doing so', (done) => { it('quit "succeeds" even if the client connection is closed while doing so', () => {
client = redis.createClient() client = redis.createClient()
client.set('foo', 'bar', (err, res) => { return client.set('foo', 'bar').then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
client.quit((err, res) => { const promise = client.quit().then((res) => {
assert.strictEqual(res, 'OK') assert.strictEqual(res, 'OK')
done(err)
}) })
client.end(true) // Flushing the quit command should result in a success client.end(true) // Flushing the quit command should result in a success
return promise
}) })
}) })
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', helper.isError()) client.set('foo', 'bar').catch(helper.isError())
client.quit(done) client.quit().then(() => done())
process.nextTick(() => { process.nextTick(() => {
client.stream.destroy() client.stream.destroy()
}) })
@@ -176,24 +171,11 @@ describe('connection tests', () => {
}) })
}) })
it('emits error once if reconnecting after command has been executed but not yet returned without callback', (done) => {
client = redis.createClient.apply(null, args)
client.on('ready', () => {
client.set('foo', 'bar', (err) => {
assert.strictEqual(err.code, 'UNCERTAIN_STATE')
done()
})
// Abort connection before the value returned
client.stream.destroy()
})
})
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', (err, res) => { client.set('foo', 'bar').then(assert, (err) => {
assert.strictEqual(err.message, 'Stream connection ended and command aborted.') assert.strictEqual(err.message, 'Stream connection ended and command aborted.')
assert.strictEqual(err.origin.message, 'Connection timeout') assert.strictEqual(err.origin.message, 'Connection timeout')
done() done()
@@ -205,13 +187,14 @@ describe('connection tests', () => {
}, },
port: 9999 port: 9999
}) })
client.on('error', helper.isError(/Connection timeout/))
}) })
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', (err, res) => { 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.')
assert.strictEqual(err.code, 'NR_CLOSED') assert.strictEqual(err.code, 'NR_CLOSED')
assert.strictEqual(err.origin.code, 'ECONNREFUSED') assert.strictEqual(err.origin.code, 'ECONNREFUSED')
@@ -223,6 +206,7 @@ describe('connection tests', () => {
}, },
port: 9999 port: 9999
}) })
client.on('error', helper.isError(/Redis connection to 127\.0\.0\.1:9999 failed/))
}) })
it('retryStrategy used to reconnect with defaults', (done) => { it('retryStrategy used to reconnect with defaults', (done) => {
@@ -232,7 +216,13 @@ describe('connection tests', () => {
redis.debugMode = true redis.debugMode = true
client = redis.createClient({ client = redis.createClient({
retryStrategy (options) { retryStrategy (options) {
client.set('foo', 'bar') client.set('foo', 'bar').catch((err) => {
assert.strictEqual(err.code, 'NR_CLOSED')
assert.strictEqual(err.message, 'Stream connection ended and command aborted.')
unhookIntercept()
redis.debugMode = false
done()
})
assert(redis.debugMode) assert(redis.debugMode)
return null return null
} }
@@ -240,13 +230,6 @@ describe('connection tests', () => {
setTimeout(() => { setTimeout(() => {
client.stream.destroy() client.stream.destroy()
}, 50) }, 50)
client.on('error', (err) => {
assert.strictEqual(err.code, 'NR_CLOSED')
assert.strictEqual(err.message, 'Stream connection ended and command aborted.')
unhookIntercept()
redis.debugMode = false
done()
})
}) })
}) })
@@ -255,23 +238,25 @@ describe('connection tests', () => {
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 routable 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,
}) retryStrategy () {
process.nextTick(() => { return 5000
assert.strictEqual(client.stream.listeners('timeout').length, 1) }
}) })
process.nextTick(() => assert.strictEqual(client.stream.listeners('timeout').length, 1))
assert.strictEqual(client.address, '10.255.255.1:6379') assert.strictEqual(client.address, '10.255.255.1:6379')
assert.strictEqual(client.connectionOptions.family, 4) assert.strictEqual(client.connectionOptions.family, 4)
client.on('reconnecting', (params) => { client.on('reconnecting', () => {
throw new Error('No reconnect, since no connection was ever established') throw new Error('No reconnect, since no connection was ever established')
}) })
const time = Date.now() const time = Date.now()
client.on('error', (err) => { client.on('error', (err) => {
if (err.code === 'ENETUNREACH') { // The test is run without a internet connection. Pretent it works console.log('errrrrr', err)
if (err.code === 'ENETUNREACH') { // The test is run without a internet connection. Pretend it works
return done() return done()
} }
assert(/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message), err.message) assert(/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message), err.message)
@@ -322,7 +307,7 @@ describe('connection tests', () => {
client.once('ready', done) client.once('ready', done)
}) })
it('connect with path provided in the options object', function (done) { it('connect with path provided in the options object', function () {
if (process.platform === 'win32') { if (process.platform === 'win32') {
this.skip() this.skip()
} }
@@ -330,11 +315,7 @@ describe('connection tests', () => {
path: '/tmp/redis.sock', path: '/tmp/redis.sock',
connectTimeout: 1000 connectTimeout: 1000
}) })
return client.set('foo', 'bar')
const end = helper.callFuncAfter(done, 2)
client.once('ready', end)
client.set('foo', 'bar', end)
}) })
it('connects correctly with args', (done) => { it('connects correctly with args', (done) => {
@@ -343,7 +324,7 @@ describe('connection tests', () => {
client.once('ready', () => { client.once('ready', () => {
client.removeListener('error', done) client.removeListener('error', done)
client.get('recon 1', done) client.get('recon 1').then(() => done())
}) })
}) })
@@ -353,7 +334,7 @@ describe('connection tests', () => {
client.once('ready', () => { client.once('ready', () => {
client.removeListener('error', done) client.removeListener('error', done)
client.get('recon 1', done) client.get('recon 1').then(() => done())
}) })
}) })
@@ -364,7 +345,7 @@ describe('connection tests', () => {
client.once('ready', () => { client.once('ready', () => {
client.removeListener('error', done) client.removeListener('error', done)
client.get('recon 1', done) client.get('recon 1').then(() => done())
}) })
}) })
@@ -374,7 +355,7 @@ describe('connection tests', () => {
client.once('ready', () => { client.once('ready', () => {
client.removeListener('error', done) client.removeListener('error', done)
client.get('recon 1', done) client.get('recon 1').then(() => done())
}) })
}) })
@@ -385,10 +366,9 @@ describe('connection tests', () => {
client.once('ready', () => { client.once('ready', () => {
client.set('foo', 'bar') client.set('foo', 'bar')
client.get('foo', (err, res) => { client.get('foo')
assert.strictEqual(res, 'bar') .then(helper.isString('bar'))
done(err) .then(done)
})
}) })
}) })
@@ -400,10 +380,9 @@ describe('connection tests', () => {
client.once('ready', () => { client.once('ready', () => {
client.set('foo', 'bar') client.set('foo', 'bar')
client.get('foo', (err, res) => { client.get('foo')
assert.strictEqual(res, 'bar') .then(helper.isString('bar'))
done(err) .then(done)
})
}) })
}) })
@@ -416,22 +395,23 @@ describe('connection tests', () => {
client.once('ready', () => { client.once('ready', () => {
client.set('foo', 'bar') client.set('foo', 'bar')
client.get('foo', (err, res) => { client.get('foo')
assert.strictEqual(res, 'bar') .then(helper.isString('bar'))
done(err) .then(done)
})
}) })
}) })
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)
client.info = function (cb) { const end = helper.callFuncAfter(done, 2)
client.info = function () {
// Mock the result // Mock the result
cb(new Error('ERR unknown command \'info\'')) end()
return Promise.reject(new Error('ERR unknown command \'info\''))
} }
client.once('ready', () => { client.once('ready', () => {
assert.strictEqual(Object.keys(client.serverInfo).length, 0) assert.strictEqual(Object.keys(client.serverInfo).length, 0)
done() end()
}) })
}) })
@@ -512,18 +492,17 @@ describe('connection tests', () => {
const end = helper.callFuncAfter(done, 3) const end = helper.callFuncAfter(done, 3)
let delayed = false let delayed = false
let time let time
// Mock original function and pretent redis is still loading // Mock original function and pretend redis is still loading
client.info = function (cb) { client.info = function () {
tmp((err, res) => { return tmp().then((res) => {
if (!delayed) { if (!delayed) {
assert(!err)
client.serverInfo.loading = 1 client.serverInfo.loading = 1
client.serverInfo.loading_eta_seconds = 0.5 client.serverInfo.loading_eta_seconds = 0.5
delayed = true delayed = true
time = Date.now() time = Date.now()
} }
end() end()
cb(err, res) return res
}) })
} }
client.on('ready', () => { client.on('ready', () => {
@@ -542,11 +521,10 @@ describe('connection tests', () => {
const end = helper.callFuncAfter(done, 3) const end = helper.callFuncAfter(done, 3)
let delayed = false let delayed = false
let time let time
// Mock original function and pretent redis is still loading // Mock original function and pretend redis is still loading
client.info = function (cb) { client.info = function () {
tmp((err, res) => { return tmp().then((res) => {
if (!delayed) { if (!delayed) {
assert(!err)
// Try reconnecting after one second even if redis tells us the time needed is above one second // Try reconnecting after one second even if redis tells us the time needed is above one second
client.serverInfo.loading = 1 client.serverInfo.loading = 1
client.serverInfo.loading_eta_seconds = 2.5 client.serverInfo.loading_eta_seconds = 2.5
@@ -554,7 +532,7 @@ describe('connection tests', () => {
time = Date.now() time = Date.now()
} }
end() end()
cb(err, res) return res
}) })
} }
client.on('ready', () => { client.on('ready', () => {

View File

@@ -43,46 +43,4 @@ describe('errors', () => {
assert.strictEqual(e.message, 'foobar') assert.strictEqual(e.message, 'foobar')
}) })
}) })
describe('AggregateError', () => {
it('should inherit from Error and AbortError', () => {
const e = new errors.AggregateError({})
assert.strictEqual(e.message, '')
assert.strictEqual(e.name, 'AggregateError')
assert.strictEqual(Object.keys(e).length, 0)
assert(e instanceof Error)
assert(e instanceof errors.AggregateError)
assert(e instanceof errors.AbortError)
})
it('should list options properties but not name and message', () => {
const e = new errors.AggregateError({
name: 'weird',
message: 'hello world',
property: true
})
assert.strictEqual(e.message, 'hello world')
assert.strictEqual(e.name, 'weird')
assert.strictEqual(e.property, true)
assert.strictEqual(Object.keys(e).length, 2)
assert(e instanceof Error)
assert(e instanceof errors.AggregateError)
assert(e instanceof errors.AbortError)
assert(delete e.name)
assert.strictEqual(e.name, 'AggregateError')
})
it('should change name and message', () => {
const e = new errors.AggregateError({
message: 'hello world',
property: true
})
assert.strictEqual(e.name, 'AggregateError')
assert.strictEqual(e.message, 'hello world')
e.name = 'foo'
e.message = 'foobar'
assert.strictEqual(e.name, 'foo')
assert.strictEqual(e.message, 'foobar')
})
})
}) })

View File

@@ -12,16 +12,13 @@ describe('detectBuffers', () => {
detectBuffers: true detectBuffers: true
}) })
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('error', done) return Promise.all([
client.once('connect', () => { client.flushdb(),
client.flushdb((err) => { client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2'),
client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2') client.set('string key 1', 'string value')
client.set('string key 1', 'string value') ])
return done(err)
})
})
}) })
afterEach(() => { afterEach(() => {
@@ -30,45 +27,41 @@ describe('detectBuffers', () => {
describe('get', () => { describe('get', () => {
describe('first argument is a string', () => { describe('first argument is a string', () => {
it('returns a string', (done) => { it('returns a string', () => {
client.get('string key 1', helper.isString('string value', done)) return client.get('string key 1').then(helper.isString('string value'))
}) })
it('returns a string when executed as part of transaction', (done) => { it('returns a string when executed as part of transaction', () => {
client.multi().get('string key 1').exec((err, res) => { return client.multi().get('string key 1').exec().then(helper.isString('string value'))
helper.isString('string value', done)(err, res[0])
})
}) })
}) })
describe('first argument is a buffer', () => { describe('first argument is a buffer', () => {
it('returns a buffer', (done) => { it('returns a buffer', () => {
client.get(Buffer.from('string key 1'), (err, reply) => { return client.get(Buffer.from('string key 1')).then((reply) => {
assert.strictEqual(true, Buffer.isBuffer(reply)) assert.strictEqual(true, Buffer.isBuffer(reply))
assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply.inspect()) assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply.inspect())
return done(err)
}) })
}) })
it('returns a bufffer when executed as part of transaction', (done) => { it('returns a buffer when executed as part of transaction', () => {
client.multi().get(Buffer.from('string key 1')).exec((err, reply) => { return client.multi().get(Buffer.from('string key 1')).exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(true, Buffer.isBuffer(reply[0])) assert.strictEqual(true, Buffer.isBuffer(reply[0]))
assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply[0].inspect()) assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply[0].inspect())
return done(err)
}) })
}) })
}) })
}) })
describe('multi.hget', () => { describe('multi.hget', () => {
it('can interleave string and buffer results', (done) => { it('can interleave string and buffer results', () => {
client.multi() return client.multi()
.hget('hash key 2', 'key 1') .hget('hash key 2', 'key 1')
.hget(Buffer.from('hash key 2'), 'key 1') .hget(Buffer.from('hash key 2'), 'key 1')
.hget('hash key 2', Buffer.from('key 2')) .hget('hash key 2', Buffer.from('key 2'))
.hget('hash key 2', 'key 2') .hget('hash key 2', 'key 2')
.exec((err, reply) => { .exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply)) assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(4, reply.length) assert.strictEqual(4, reply.length)
assert.strictEqual('val 1', reply[0]) assert.strictEqual('val 1', reply[0])
@@ -77,19 +70,18 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[2])) assert.strictEqual(true, Buffer.isBuffer(reply[2]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect())
assert.strictEqual('val 2', reply[3]) assert.strictEqual('val 2', reply[3])
return done(err)
}) })
}) })
}) })
describe('batch.hget', () => { describe('batch.hget', () => {
it('can interleave string and buffer results', (done) => { it('can interleave string and buffer results', () => {
client.batch() return client.batch()
.hget('hash key 2', 'key 1') .hget('hash key 2', 'key 1')
.hget(Buffer.from('hash key 2'), 'key 1') .hget(Buffer.from('hash key 2'), 'key 1')
.hget('hash key 2', Buffer.from('key 2')) .hget('hash key 2', Buffer.from('key 2'))
.hget('hash key 2', 'key 2') .hget('hash key 2', 'key 2')
.exec((err, reply) => { .exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply)) assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(4, reply.length) assert.strictEqual(4, reply.length)
assert.strictEqual('val 1', reply[0]) assert.strictEqual('val 1', reply[0])
@@ -98,71 +90,56 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[2])) assert.strictEqual(true, Buffer.isBuffer(reply[2]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect())
assert.strictEqual('val 2', reply[3]) assert.strictEqual('val 2', reply[3])
return done(err)
}) })
}) })
}) })
describe('hmget', () => { describe('hmget', () => {
describe('first argument is a string', () => { describe('first argument is a string', () => {
it('returns strings for keys requested', (done) => { it('returns strings for keys requested', () => {
client.hmget('hash key 2', 'key 1', 'key 2', (err, reply) => { return client.hmget('hash key 2', 'key 1', 'key 2').then((reply) => {
assert.strictEqual(true, Array.isArray(reply)) assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(2, reply.length) assert.strictEqual(2, reply.length)
assert.strictEqual('val 1', reply[0]) assert.strictEqual('val 1', reply[0])
assert.strictEqual('val 2', reply[1]) assert.strictEqual('val 2', reply[1])
return done(err)
}) })
}) })
it('returns strings for keys requested in transaction', (done) => { it('returns strings for keys requested in transaction', () => {
client.multi().hmget('hash key 2', 'key 1', 'key 2').exec((err, reply) => { return client.multi().hmget('hash key 2', 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply)) assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length) assert.strictEqual(2, reply[0].length)
assert.strictEqual('val 1', reply[0][0]) assert.strictEqual('val 1', reply[0][0])
assert.strictEqual('val 2', reply[0][1]) assert.strictEqual('val 2', reply[0][1])
return done(err)
}) })
}) })
it('handles array of strings with undefined values (repro #344)', (done) => { it('handles array of strings with undefined values (repro #344)', () => {
client.hmget('hash key 2', 'key 3', 'key 4', (err, reply) => { return client.hmget('hash key 2', 'key 3', 'key 4')
assert.strictEqual(true, Array.isArray(reply)) .then(helper.isDeepEqual([null, null]))
assert.strictEqual(2, reply.length)
assert.strictEqual(null, reply[0])
assert.strictEqual(null, reply[1])
return done(err)
})
}) })
it('handles array of strings with undefined values in transaction (repro #344)', (done) => { it('handles array of strings with undefined values in transaction (repro #344)', () => {
client.multi().hmget('hash key 2', 'key 3', 'key 4').exec((err, reply) => { return client.multi().hmget('hash key 2', 'key 3', 'key 4').exec()
assert.strictEqual(true, Array.isArray(reply)) .then(helper.isDeepEqual([[null, null]]))
assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length)
assert.strictEqual(null, reply[0][0])
assert.strictEqual(null, reply[0][1])
return done(err)
})
}) })
}) })
describe('first argument is a buffer', () => { describe('first argument is a buffer', () => {
it('returns buffers for keys requested', (done) => { it('returns buffers for keys requested', () => {
client.hmget(Buffer.from('hash key 2'), 'key 1', 'key 2', (err, reply) => { return client.hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').then((reply) => {
assert.strictEqual(true, Array.isArray(reply)) assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(2, reply.length) assert.strictEqual(2, reply.length)
assert.strictEqual(true, Buffer.isBuffer(reply[0])) assert.strictEqual(true, Buffer.isBuffer(reply[0]))
assert.strictEqual(true, Buffer.isBuffer(reply[1])) assert.strictEqual(true, Buffer.isBuffer(reply[1]))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[1].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[1].inspect())
return done(err)
}) })
}) })
it('returns buffers for keys requested in transaction', (done) => { it('returns buffers for keys requested in transaction', () => {
client.multi().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => { return client.multi().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply)) assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length) assert.strictEqual(2, reply[0].length)
@@ -170,12 +147,11 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[0][1])) assert.strictEqual(true, Buffer.isBuffer(reply[0][1]))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect())
return done(err)
}) })
}) })
it('returns buffers for keys requested in .batch', (done) => { it('returns buffers for keys requested in .batch', () => {
client.batch().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => { return client.batch().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply)) assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length) assert.strictEqual(2, reply[0].length)
@@ -183,63 +159,49 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[0][1])) assert.strictEqual(true, Buffer.isBuffer(reply[0][1]))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect())
return done(err)
}) })
}) })
}) })
}) })
describe('hgetall', (done) => { describe('hgetall', () => {
describe('first argument is a string', () => { describe('first argument is a string', () => {
it('returns string values', (done) => { it('returns string values', () => {
client.hgetall('hash key 2', (err, reply) => { return client.hgetall('hash key 2').then(helper.isDeepEqual({
assert.strictEqual('object', typeof reply) 'key 1': 'val 1',
assert.strictEqual(2, Object.keys(reply).length) 'key 2': 'val 2'
assert.strictEqual('val 1', reply['key 1']) }))
assert.strictEqual('val 2', reply['key 2'])
return done(err)
})
}) })
it('returns string values when executed in transaction', (done) => { it('returns string values when executed in transaction', () => {
client.multi().hgetall('hash key 2').exec((err, reply) => { return client.multi().hgetall('hash key 2').exec().then(helper.isDeepEqual([{
assert.strictEqual(1, reply.length) 'key 1': 'val 1',
assert.strictEqual('object', typeof reply[0]) 'key 2': 'val 2'
assert.strictEqual(2, Object.keys(reply[0]).length) }]))
assert.strictEqual('val 1', reply[0]['key 1'])
assert.strictEqual('val 2', reply[0]['key 2'])
return done(err)
})
}) })
it('returns string values when executed in .batch', (done) => { it('returns string values when executed in .batch', () => {
client.batch().hgetall('hash key 2').exec((err, reply) => { return client.batch().hgetall('hash key 2').exec().then(helper.isDeepEqual([{
assert.strictEqual(1, reply.length) 'key 1': 'val 1',
assert.strictEqual('object', typeof reply[0]) 'key 2': 'val 2'
assert.strictEqual(2, Object.keys(reply[0]).length) }]))
assert.strictEqual('val 1', reply[0]['key 1'])
assert.strictEqual('val 2', reply[0]['key 2'])
return done(err)
})
}) })
}) })
describe('first argument is a buffer', () => { describe('first argument is a buffer', () => {
it('returns buffer values', (done) => { it('returns buffer values', () => {
client.hgetall(Buffer.from('hash key 2'), (err, reply) => { return client.hgetall(Buffer.from('hash key 2')).then((reply) => {
assert.strictEqual(null, err)
assert.strictEqual('object', typeof reply) assert.strictEqual('object', typeof reply)
assert.strictEqual(2, Object.keys(reply).length) assert.strictEqual(2, Object.keys(reply).length)
assert.strictEqual(true, Buffer.isBuffer(reply['key 1'])) assert.strictEqual(true, Buffer.isBuffer(reply['key 1']))
assert.strictEqual(true, Buffer.isBuffer(reply['key 2'])) assert.strictEqual(true, Buffer.isBuffer(reply['key 2']))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply['key 2'].inspect())
return done(err)
}) })
}) })
it('returns buffer values when executed in transaction', (done) => { it('returns buffer values when executed in transaction', () => {
client.multi().hgetall(Buffer.from('hash key 2')).exec((err, reply) => { return client.multi().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0]) assert.strictEqual('object', typeof reply[0])
assert.strictEqual(2, Object.keys(reply[0]).length) assert.strictEqual(2, Object.keys(reply[0]).length)
@@ -247,12 +209,11 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2'])) assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect())
return done(err)
}) })
}) })
it('returns buffer values when executed in .batch', (done) => { it('returns buffer values when executed in .batch', () => {
client.batch().hgetall(Buffer.from('hash key 2')).exec((err, reply) => { return client.batch().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0]) assert.strictEqual('object', typeof reply[0])
assert.strictEqual(2, Object.keys(reply[0]).length) assert.strictEqual(2, Object.keys(reply[0]).length)
@@ -260,7 +221,6 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2'])) assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect())
return done(err)
}) })
}) })
}) })

View File

@@ -2,6 +2,7 @@
const assert = require('assert') const assert = require('assert')
const config = require('./lib/config') const config = require('./lib/config')
const helper = require('./helper')
const fork = require('child_process').fork const fork = require('child_process').fork
const redis = config.redis const redis = config.redis
@@ -46,13 +47,13 @@ describe('stack traces', () => {
}) })
// This is always going to return good stack traces // This is always going to return good stack traces
it('should always return good stack traces for rejected offline commands', (done) => { it('should always return good stack traces for rejected offline commands', () => {
const client = redis.createClient({ const client = redis.createClient({
enableOfflineQueue: false enableOfflineQueue: false
}) })
client.set('foo', (err, res) => { return client.set('foo').then(helper.fail).catch((err) => {
assert(/good_traces.spec.js/.test(err.stack)) assert(/good_traces.spec.js/.test(err.stack))
client.quit(done) return client.quit()
}) })
}) })
}) })

View File

@@ -8,6 +8,14 @@ const StunnelProcess = require('./lib/stunnel-process')
let rp let rp
let stunnelProcess let stunnelProcess
process.on('unhandledRejection', (err, promise) => {
console.log(err)
rp.stop(() => {
console.log('PROCESS ENDING DUE TO AN UNHANDLED REJECTION')
process.exit(1)
})
})
function startRedis (conf, done, port) { function startRedis (conf, done, port) {
RedisProcess.start((err, _rp) => { RedisProcess.start((err, _rp) => {
rp = _rp rp = _rp
@@ -45,6 +53,10 @@ function toString (res) {
if (Array.isArray(res)) { if (Array.isArray(res)) {
return res.map(toString) return res.map(toString)
} }
// Stringify all values as well
if (typeof res === 'object' && res !== null) {
Object.keys(res).map((key) => (res[key] = toString(res[key])))
}
return res return res
} }
@@ -69,92 +81,58 @@ module.exports = {
return done(err) return done(err)
}, path.resolve(__dirname, './conf')) }, path.resolve(__dirname, './conf'))
}, },
isNumber (expected, done) { isNumber (expected) {
return function (err, results) { return function (results) {
assert.strictEqual(err, null, `expected ${expected}, got error: ${err}`)
results = arrayHelper(results) results = arrayHelper(results)
assert.strictEqual(results, expected, `${expected } !== ${results}`) assert.strictEqual(results, expected, `${expected} !== ${results}`)
assert.strictEqual(typeof results, 'number', `expected a number, got ${typeof results}`) assert.strictEqual(typeof results, 'number', `expected a number, got ${typeof results}`)
if (done) done()
} }
}, },
isString (str, done) { isString (str) {
str = `${str}` // Make sure it's a string str = `${str}` // Make sure it's a string
return function (err, results) { return function (results) {
assert.strictEqual(err, null, `expected string '${str}', got error: ${err}`)
results = arrayHelper(results) results = arrayHelper(results)
results = toString(results) results = toString(results)
assert.strictEqual(results, str, `${str } does not match ${results}`) assert.strictEqual(results, str, `${str} does not match ${results}`)
if (done) done()
} }
}, },
isNull (done) { isNull () {
return function (err, results) { return function (results) {
assert.strictEqual(err, null, `expected null, got error: ${err}`)
results = arrayHelper(results) results = arrayHelper(results)
assert.strictEqual(results, null, `${results } is not null`) assert.strictEqual(results, null, `${results} is not null`)
if (done) done()
} }
}, },
isUndefined (done) { isUndefined () {
return function (err, results) { return function (results) {
assert.strictEqual(err, null, `expected null, got error: ${err}`)
results = arrayHelper(results) results = arrayHelper(results)
assert.strictEqual(results, undefined, `${results } is not undefined`) assert.strictEqual(results, undefined, `${results} is not undefined`)
if (done) done()
} }
}, },
isError (done) { isError (regex) {
return function (err, results) {
assert(err instanceof Error, 'err is not instance of \'Error\', but an error is expected here.')
if (done) done()
}
},
isNotError (done) {
return function (err, results) {
assert.strictEqual(err, null, `expected success, got an error: ${err}`)
if (done) done()
}
},
isDeepEqual (args, done) {
return function (err, res) { return function (err, res) {
assert.strictEqual(err, null, `expected null, got error: ${err}`) assert.strictEqual(res, undefined, 'There should be an error, no result!')
assert(err instanceof Error, 'err is not instance of \'Error\', but an error is expected here.')
if (regex) assert(regex.test(err.message))
}
},
isDeepEqual (args) {
return function (res) {
res = toString(res) res = toString(res)
assert.deepStrictEqual(res, args) assert.deepStrictEqual(res, args)
if (done) done()
} }
}, },
isType: { match (pattern) {
number (done) { return function (results) {
return function (err, results) {
assert.strictEqual(err, null, `expected any number, got error: ${err}`)
assert.strictEqual(typeof results, 'number', `${results } is not a number`)
if (done) done()
}
},
string (done) {
return function (err, results) {
assert.strictEqual(err, null, `expected any string, got error: ${err}`)
assert.strictEqual(typeof results, 'string', `${results } is not a string`)
if (done) done()
}
},
positiveNumber (done) {
return function (err, results) {
assert.strictEqual(err, null, `expected positive number, got error: ${err}`)
assert(results > 0, `${results } is not a positive number`)
if (done) done()
}
}
},
match (pattern, done) {
return function (err, results) {
assert.strictEqual(err, null, `expected ${pattern.toString()}, got error: ${err}`)
results = arrayHelper(results) results = arrayHelper(results)
assert(pattern.test(results), `expected string '${results}' to match ${pattern.toString()}`) assert(pattern.test(results), `expected string '${results}' to match ${pattern.toString()}`)
if (done) done()
} }
}, },
fail (err) {
err = err instanceof Error
? err
: new Error('This should not be reachable')
throw err
},
serverVersionAtLeast (connection, desiredVersion) { serverVersionAtLeast (connection, desiredVersion) {
// Wait until a connection has established (otherwise a timeout is going to be triggered at some point) // Wait until a connection has established (otherwise a timeout is going to be triggered at some point)
if (Object.keys(connection.serverInfo).length === 0) { if (Object.keys(connection.serverInfo).length === 0) {
@@ -212,10 +190,7 @@ module.exports = {
}, },
callFuncAfter (func, max) { callFuncAfter (func, max) {
let i = 0 let i = 0
return function (err) { return function () {
if (err) {
throw err
}
i++ i++
if (i >= max) { if (i >= max) {
func() func()

View File

@@ -3,11 +3,6 @@
// helpers for configuring a redis client in // helpers for configuring a redis client in
// its various modes, ipV6, ipV4, socket. // its various modes, ipV6, ipV4, socket.
const redis = require('../../index') const redis = require('../../index')
const bluebird = require('bluebird')
// Promisify everything
bluebird.promisifyAll(redis.RedisClient.prototype)
bluebird.promisifyAll(redis.Multi.prototype)
const config = { const config = {
redis, redis,

View File

@@ -6,9 +6,9 @@ const redis = require('../../index')
const client = redis.createClient() const client = redis.createClient()
// Both error cases would normally return bad stack traces // Both error cases would normally return bad stack traces
client.set('foo', (err, res) => { client.set('foo').catch((err) => {
assert(/good-traces.js:9:8/.test(err.stack)) assert(/good-traces.js:9:8/.test(err.stack))
client.set('foo', 'bar', (err, res) => { client.set('foo', 'bar').catch((err) => {
assert(/good-traces.js:11:10/.test(err.stack)) assert(/good-traces.js:11:10/.test(err.stack))
client.quit(() => { client.quit(() => {
process.exit(0) process.exit(0)

View File

@@ -6,7 +6,6 @@ const fs = require('fs')
const path = require('path') const path = require('path')
const spawn = require('win-spawn') const spawn = require('win-spawn')
const tcpPortUsed = require('tcp-port-used') const tcpPortUsed = require('tcp-port-used')
const bluebird = require('bluebird')
// wait for redis to be listening in // wait for redis to be listening in
// all three modes (ipv4, ipv6, socket). // all three modes (ipv4, ipv6, socket).
@@ -24,26 +23,28 @@ function waitForRedis (available, cb, port) {
const id = setInterval(() => { const id = setInterval(() => {
if (running) return if (running) return
running = true running = true
bluebird.join( Promise.all([
tcpPortUsed.check(port, '127.0.0.1'), tcpPortUsed.check(port, '127.0.0.1'),
tcpPortUsed.check(port, '::1'), tcpPortUsed.check(port, '::1')
(ipV4, ipV6) => { ]).then((ip) => {
if (ipV6 === available && ipV4 === available) { const ipV4 = ip[0]
if (fs.existsSync(socket) === available) { const ipV6 = ip[1]
clearInterval(id) if (ipV6 === available && ipV4 === available) {
return cb() if (fs.existsSync(socket) === available) {
} clearInterval(id)
// The same message applies for can't stop but we ignore that case return cb()
throw new Error(`Port ${port} is already in use. Tests can't start.\n`)
} }
if (Date.now() - time > 6000) { // The same message applies for can't stop but we ignore that case
throw new Error(`Redis could not start on port ${port || config.PORT}\n`) throw new Error(`Port ${port} is already in use. Tests can't start.\n`)
} }
running = false if (Date.now() - time > 6000) {
}).catch((err) => { throw new Error(`Redis could not start on port ${port || config.PORT}\n`)
console.error(`\x1b[31m${err.stack}\x1b[0m\n`) }
process.exit(1) running = false
}) }).catch((err) => {
console.error(`\x1b[31m${err.stack}\x1b[0m\n`)
process.exit(1)
})
}, 100) }, 100)
} }

View File

@@ -73,13 +73,7 @@ describe('The \'multi\' method', () => {
for (i = 0; i < 100; i++) { for (i = 0; i < 100; i++) {
multi.hset('SOME_KEY', `SOME_FIELD${i}`, buffer) multi.hset('SOME_KEY', `SOME_FIELD${i}`, buffer)
} }
multi.exec((err, res) => { multi.exec().then(run)
if (err) {
done(err)
return
}
run()
})
}) })
} }
run() run()
@@ -87,7 +81,7 @@ describe('The \'multi\' method', () => {
}) })
describe('pipeline limit', () => { describe('pipeline limit', () => {
it('do not exceed maximum string size', function (done) { it('do not exceed maximum string size', function () {
this.timeout(process.platform !== 'win32' ? 10000 : 35000) // Windows tests are horribly slow this.timeout(process.platform !== 'win32' ? 10000 : 35000) // Windows tests are horribly slow
// Triggers a RangeError: Invalid string length if not handled properly // Triggers a RangeError: Invalid string length if not handled properly
client = redis.createClient() client = redis.createClient()
@@ -97,40 +91,24 @@ describe('The \'multi\' method', () => {
i -= 10230 i -= 10230
multi.set(`foo${i}`, `bar${new Array(1024).join('1234567890')}`) multi.set(`foo${i}`, `bar${new Array(1024).join('1234567890')}`)
} }
client.on('ready', () => { multi.exec().then((res) => {
multi.exec((err, res) => { assert.strictEqual(res.length, 26241)
assert.strictEqual(err, null)
assert.strictEqual(res.length, 26241)
})
client.flushdb(done)
}) })
return client.flushdb()
}) })
}) })
helper.allTests((ip, args) => { helper.allTests((ip, args) => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
describe('when not connected', () => { describe('when not connected', () => {
beforeEach((done) => { beforeEach(() => {
const end = helper.callFuncAfter(done, 2)
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.quit()
client.quit(end)
})
client.once('end', end)
}) })
it('reports an error', (done) => { it('reports an error', () => {
const multi = client.multi() const multi = client.multi()
multi.exec((err, res) => { return multi.exec().then(helper.fail, helper.isError(/The connection is already closed/))
assert(err.message.match(/The connection is already closed/))
done()
})
})
it('reports an error if promisified', () => {
return client.multi().execAsync().catch((err) => {
assert(err.message.match(/The connection is already closed/))
})
}) })
}) })
@@ -140,26 +118,23 @@ describe('The \'multi\' method', () => {
}) })
describe('monitor and transactions do not work together', () => { describe('monitor and transactions do not work together', () => {
it('results in a execabort', (done) => { it('results in a execabort', () => {
// Check that transactions in combination with monitor result in an error // Check that transactions in combination with monitor result in an error
client.monitor((e) => { return client.monitor().then(() => {
client.on('error', (err) => {
assert.strictEqual(err.code, 'EXECABORT')
client.end(false)
done()
})
const multi = client.multi() const multi = client.multi()
multi.set('hello', 'world') multi.set('hello', 'world')
multi.exec() return multi.exec().then(assert, (err) => {
assert.strictEqual(err.code, 'EXECABORT')
client.end(false)
})
}) })
}) })
it('results in a execabort #2', (done) => { it('results in a execabort #2', () => {
// Check that using monitor with a transactions results in an error // Check that using monitor with a transactions results in an error
client.multi().set('foo', 'bar').monitor().exec((err, res) => { return client.multi().set('foo', 'bar').monitor().exec().then(assert, (err) => {
assert.strictEqual(err.code, 'EXECABORT') assert.strictEqual(err.code, 'EXECABORT')
client.end(false) client.end(false)
done()
}) })
}) })
@@ -176,24 +151,24 @@ describe('The \'multi\' method', () => {
client.sendCommand('multi') client.sendCommand('multi')
client.sendCommand('set', ['foo', 'bar']) client.sendCommand('set', ['foo', 'bar'])
client.sendCommand('get', ['foo']) client.sendCommand('get', ['foo'])
client.sendCommand('exec', (err, res) => { client.sendCommand('exec').then((res) => {
assert.strictEqual(err, null)
// res[0] is going to be the monitor result of set // res[0] is going to be the monitor result of set
// res[1] is going to be the result of the set command // res[1] is going to be the result of the set command
assert(utils.monitorRegex.test(res[0])) assert(utils.monitorRegex.test(res[0]))
assert.strictEqual(res[1], 'OK') assert.strictEqual(res[1].toString(), 'OK')
assert.strictEqual(res.length, 2) assert.strictEqual(res.length, 2)
client.end(false) client.end(false)
}) })
}) })
}) })
it('executes a pipelined multi properly in combination with the offline queue', (done) => { it('executes a pipelined multi properly in combination with the offline queue', () => {
const multi1 = client.multi() const multi1 = client.multi()
multi1.set('m1', '123') multi1.set('m1', '123')
multi1.get('m1') multi1.get('m1')
multi1.exec(done) const promise = multi1.exec()
assert.strictEqual(client.offlineQueue.length, 4) assert.strictEqual(client.offlineQueue.length, 4)
return promise
}) })
it('executes a pipelined multi properly after a reconnect in combination with the offline queue', (done) => { it('executes a pipelined multi properly after a reconnect in combination with the offline queue', (done) => {
@@ -203,17 +178,13 @@ describe('The \'multi\' method', () => {
const multi1 = client.multi() const multi1 = client.multi()
multi1.set('m1', '123') multi1.set('m1', '123')
multi1.get('m1') multi1.get('m1')
multi1.exec((err, res) => { multi1.exec().then(() => (called = true))
assert(!err)
called = true
})
client.once('ready', () => { client.once('ready', () => {
const multi1 = client.multi() const multi1 = client.multi()
multi1.set('m2', '456') multi1.set('m2', '456')
multi1.get('m2') multi1.get('m2')
multi1.exec((err, res) => { multi1.exec().then((res) => {
assert(called) assert(called)
assert(!err)
assert.strictEqual(res[1], '456') assert.strictEqual(res[1], '456')
done() done()
}) })
@@ -223,7 +194,7 @@ describe('The \'multi\' method', () => {
}) })
describe('when connection is broken', () => { describe('when connection is broken', () => {
it.skip('return an error even if connection is in broken mode if callback is present', (done) => { it.skip('return an error even if connection is in broken mode', (done) => {
client = redis.createClient({ client = redis.createClient({
host: 'somewhere', host: 'somewhere',
port: 6379, port: 6379,
@@ -236,59 +207,32 @@ describe('The \'multi\' method', () => {
} }
}) })
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec((err, res) => { client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec().catch((err) => {
// assert(/Redis connection in broken state/.test(err.message)); // assert(/Redis connection in broken state/.test(err.message));
assert.strictEqual(err.errors.length, 2) assert.strictEqual(err.errors.length, 2)
assert.strictEqual(err.errors[0].args.length, 2) assert.strictEqual(err.errors[0].args.length, 2)
}) })
}) })
it.skip('does not emit an error twice if connection is in broken mode with no callback', (done) => {
client = redis.createClient({
host: 'somewhere',
port: 6379,
retryStrategy () {}
})
client.on('error', (err) => {
// Results in multiple done calls if test fails
if (/Redis connection in broken state/.test(err.message)) {
done()
}
})
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec()
})
}) })
describe('when ready', () => { describe('when ready', () => {
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('ready', () => { return client.flushdb()
client.flushdb((err) => {
return done(err)
})
})
}) })
it('returns an empty result array', (done) => { it('returns an empty result array', () => {
const multi = client.multi() const multi = client.multi()
multi.exec((err, res) => { return multi.exec().then(helper.isDeepEqual([]))
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0)
done()
})
}) })
it('runs normal calls in-between multis', (done) => { it('runs normal calls in-between multis', () => {
const multi1 = client.multi() const multi1 = client.multi()
multi1.set('m1', '123') multi1.set('m1', '123')
client.set('m2', '456', done) return client.set('m2', '456')
}) })
it('runs simultaneous multis with the same client', (done) => { it('runs simultaneous multis with the same client', () => {
const end = helper.callFuncAfter(done, 2)
const multi1 = client.multi() const multi1 = client.multi()
multi1.set('m1', '123') multi1.set('m1', '123')
multi1.get('m1') multi1.get('m1')
@@ -297,12 +241,13 @@ describe('The \'multi\' method', () => {
multi2.set('m2', '456') multi2.set('m2', '456')
multi2.get('m2') multi2.get('m2')
multi1.exec(end) return Promise.all([
multi2.exec(helper.isDeepEqual(['OK', '456'], end)) multi1.exec(),
multi2.exec().then(helper.isDeepEqual(['OK', '456']))
])
}) })
it('runs simultaneous multis with the same client version 2', (done) => { it('runs simultaneous multis with the same client version 2', () => {
const end = helper.callFuncAfter(done, 2)
const multi2 = client.multi() const multi2 = client.multi()
const multi1 = client.multi() const multi1 = client.multi()
@@ -312,46 +257,44 @@ describe('The \'multi\' method', () => {
multi2.get('m1') multi2.get('m1')
multi2.ping() multi2.ping()
multi1.exec(end) return Promise.all([
multi2.exec(helper.isDeepEqual(['OK', '123', 'PONG'], end)) multi1.exec(),
multi2.exec().then(helper.isDeepEqual(['OK', '123', 'PONG']))
])
}) })
it('roles back a transaction when one command in a sequence of commands fails', (done) => { it('roles back a transaction when one command in a sequence of commands fails', () => {
// Provoke an error at queue time // Provoke an error at queue time
const multi1 = client.multi() const multi1 = client.multi()
multi1.mset('multifoo', '10', 'multibar', '20', helper.isString('OK')) multi1.mset('multifoo', '10', 'multibar', '20')
multi1.set('foo2', helper.isError()) multi1.set('foo2')
multi1.incr('multifoo') multi1.incr('multifoo')
multi1.incr('multibar') multi1.incr('multibar')
multi1.exec(() => { return multi1.exec().then(helper.fail, () => {
// Redis 2.6.5+ will abort transactions with errors // Redis 2.6.5+ will abort transactions with errors
// see: http://redis.io/topics/transactions // see: http://redis.io/topics/transactions
const multibarExpected = 1
const multifooExpected = 1
// Confirm that the previous command, while containing an error, still worked. // Confirm that the previous command, while containing an error, still worked.
const multi2 = client.multi() const multi2 = client.multi()
multi2.incr('multibar', helper.isNumber(multibarExpected)) multi2.incr('multibar')
multi2.incr('multifoo', helper.isNumber(multifooExpected)) multi2.incr('multifoo')
multi2.exec(helper.isDeepEqual([multibarExpected, multibarExpected], done)) return multi2.exec().then(helper.isDeepEqual([1, 1]))
}) })
}) })
it('roles back a transaction when one command in an array of commands fails', (done) => { it('roles back a transaction when one command in an array of commands fails', () => {
// test nested multi-bulk replies // test nested multi-bulk replies
client.multi([ return client.multi([
['mget', 'multifoo', 'multibar', helper.isDeepEqual([null, null])], ['mget', 'multifoo', 'multibar'],
['set', 'foo2', helper.isError()], ['set', 'foo2'],
['incr', 'multifoo'], ['incr', 'multifoo'],
['incr', 'multibar'] ['incr', 'multibar']
]).exec((err, replies) => { ]).exec().then(assert, (err) => {
assert.notEqual(err, null) assert.notEqual(err, null)
assert.strictEqual(replies, undefined)
return done()
}) })
}) })
it('handles multiple operations being applied to a set', (done) => { it('handles multiple operations being applied to a set', () => {
client.sadd('some set', 'mem 1') client.sadd('some set', 'mem 1')
client.sadd(['some set', 'mem 2']) client.sadd(['some set', 'mem 2'])
client.sadd('some set', 'mem 3') client.sadd('some set', 'mem 3')
@@ -359,60 +302,50 @@ describe('The \'multi\' method', () => {
// make sure empty mb reply works // make sure empty mb reply works
client.del('some missing set') client.del('some missing set')
client.smembers('some missing set', helper.isDeepEqual([])) client.smembers('some missing set').then(helper.isDeepEqual([]))
// test nested multi-bulk replies with empty mb elements. // test nested multi-bulk replies with empty mb elements.
client.multi([ return client.multi([
['smembers', ['some set']], ['smembers', ['some set']],
['del', 'some set'], ['del', 'some set'],
['smembers', 'some set'] ['smembers', 'some set']
]) ])
.scard('some set') .scard('some set')
.exec((err, res) => { .exec().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res[0].length, 4) assert.strictEqual(res[0].length, 4)
assert.strictEqual(res[1], 1) assert.strictEqual(res[1], 1)
assert.deepStrictEqual(res[2], []) assert.deepStrictEqual(res[2], [])
assert.strictEqual(res[3], 0) assert.strictEqual(res[3], 0)
done()
}) })
}) })
it('allows multiple operations to be performed using constructor with all kinds of syntax', (done) => { it('allows multiple operations to be performed using constructor with all kinds of syntax', () => {
const now = Date.now() const now = Date.now()
const arr = ['multihmset', 'multibar', 'multibaz'] const arr = ['multihmset', 'multibar', 'multibaz']
const arr2 = ['some manner of key', 'otherTypes'] const arr2 = ['some manner of key', 'otherTypes']
const arr3 = [5768, 'multibarx', 'multifoox'] const arr3 = [5768, 'multibarx', 'multifoox']
const arr4 = ['mset', [578, 'multibar'], helper.isString('OK')] const arr4 = ['mset', [578, 'multibar']]
let called = false return client.multi([
client.multi([
arr4, arr4,
[['mset', 'multifoo2', 'multibar2', 'multifoo3', 'multibar3'], helper.isString('OK')], [['mset', 'multifoo2', 'multibar2', 'multifoo3', 'multibar3']],
['hmset', arr], ['hmset', arr],
[['hmset', 'multihmset2', 'multibar2', 'multifoo3', 'multibar3', 'test'], helper.isString('OK')], [['hmset', 'multihmset2', 'multibar2', 'multifoo3', 'multibar3', 'test']],
['hmset', ['multihmset', 'multibar', 'multifoo'], helper.isString('OK')], ['hmset', ['multihmset', 'multibar', 'multifoo']],
['hmset', arr3, helper.isString('OK')], ['hmset', arr3],
['hmset', now, {123456789: 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}], ['hmset', now, {123456789: 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 555}],
['hmset', 'key2', {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 999}, helper.isString('OK')], ['hmset', 'key2', {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 999}],
['hmset', 'multihmset', ['multibar', 'multibaz'], undefined], // undefined is used as a explicit not set callback variable ['hmset', 'multihmset', ['multibar', 'multibaz']],
['hmset', 'multihmset', ['multibar', 'multibaz'], helper.isString('OK')] ['hmset', 'multihmset', ['multibar', 'multibaz']]
]) ])
.hmget(now, 123456789, 'otherTypes') .hmget(now, 123456789, 'otherTypes')
.hmget('key2', arr2, () => {}) .hmget('key2', arr2)
.hmget(['multihmset2', 'some manner of key', 'multibar3']) .hmget(['multihmset2', 'some manner of key', 'multibar3'])
.mget('multifoo2', ['multifoo3', 'multifoo'], (err, res) => { .mget('multifoo2', ['multifoo3', 'multifoo'])
assert.strictEqual(err, null) .exec().then((replies) => {
assert(res[0], 'multifoo3')
assert(res[1], 'multifoo')
called = true
})
.exec((err, replies) => {
assert(called)
assert.strictEqual(arr.length, 3) assert.strictEqual(arr.length, 3)
assert.strictEqual(arr2.length, 2) assert.strictEqual(arr2.length, 2)
assert.strictEqual(arr3.length, 3) assert.strictEqual(arr3.length, 3)
assert.strictEqual(arr4.length, 3) assert.strictEqual(arr4.length, 2)
assert.strictEqual(null, err)
assert.strictEqual(replies[10][1], '555') assert.strictEqual(replies[10][1], '555')
assert.strictEqual(replies[11][0], 'a type of value') assert.strictEqual(replies[11][0], 'a type of value')
assert.strictEqual(replies[12][0], null) assert.strictEqual(replies[12][0], null)
@@ -420,166 +353,81 @@ describe('The \'multi\' method', () => {
assert.strictEqual(replies[13][0], 'multibar2') assert.strictEqual(replies[13][0], 'multibar2')
assert.strictEqual(replies[13].length, 3) assert.strictEqual(replies[13].length, 3)
assert.strictEqual(replies.length, 14) assert.strictEqual(replies.length, 14)
return done()
}) })
}) })
it('converts a non string key to a string', (done) => { it('converts a non string key to a string', () => {
// TODO: Converting the key might change soon again. // TODO: Converting the key might change soon again.
client.multi().hmset(true, { return client.multi().hmset(true, {
test: 123, test: 123,
bar: 'baz' bar: 'baz'
}).exec(done) }).exec()
}) })
it('runs a multi without any further commands', (done) => { it('runs a multi without any further commands', () => {
client.multi().exec((err, res) => { return client.multi().exec().then(helper.isDeepEqual([]))
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0)
done()
})
}) })
it('allows multiple operations to be performed using a chaining API', (done) => { it('allows multiple operations to be performed using a chaining API', () => {
client.multi() return client.multi()
.mset('some', '10', 'keys', '20') .mset('some', '10', 'keys', '20')
.incr('some') .incr('some')
.incr('keys') .incr('keys')
.mget('some', ['keys']) .mget('some', ['keys'])
.exec(helper.isDeepEqual(['OK', 11, 21, ['11', '21']], done)) .exec().then(helper.isDeepEqual(['OK', 11, 21, ['11', '21']]))
}) })
it('allows multiple commands to work the same as normal to be performed using a chaining API', (done) => { it('allows an array to be provided indicating multiple operations to perform', () => {
client.multi()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.mget('some', 'keys')
.exec(helper.isDeepEqual(['OK', 11, 21, ['11', '21']], done))
})
it('allows multiple commands to work the same as normal to be performed using a chaining API promisified', () => {
return client.multi()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.mget('some', 'keys')
.execAsync()
.then((replies) => {
assert.strictEqual('OK', replies[0])
assert.strictEqual(11, replies[1])
assert.strictEqual(21, replies[2])
assert.strictEqual('11', replies[3][0].toString())
assert.strictEqual('21', replies[3][1].toString())
})
})
it('allows an array to be provided indicating multiple operations to perform', (done) => {
// test nested multi-bulk replies with nulls. // test nested multi-bulk replies with nulls.
client.multi([ return client.multi([
['mget', ['multifoo', 'some', 'random value', 'keys']], ['mget', ['multifoo', 'some', 'random value', 'keys']],
['incr', 'multifoo'] ['incr', 'multifoo']
]).exec(helper.isDeepEqual([[null, null, null, null], 1], done)) ]).exec().then(helper.isDeepEqual([[null, null, null, null], 1]))
}) })
it('allows multiple operations to be performed on a hash', (done) => { it('allows multiple operations to be performed on a hash', () => {
client.multi() return client.multi()
.hmset('multihash', 'a', 'foo', 'b', 1) .hmset('multihash', 'a', 'foo', 'b', 1)
.hmset('multihash', { .hmset('multihash', {
extra: 'fancy', extra: 'fancy',
things: 'here' things: 'here'
}) })
.hgetall('multihash') .hgetall('multihash')
.exec((err, replies) => { .exec().then((replies) => {
assert.strictEqual(null, err)
assert.strictEqual('OK', replies[0]) assert.strictEqual('OK', replies[0])
assert.strictEqual(Object.keys(replies[2]).length, 4) assert.strictEqual(Object.keys(replies[2]).length, 4)
assert.strictEqual('foo', replies[2].a) assert.strictEqual('foo', replies[2].a)
assert.strictEqual('1', replies[2].b) assert.strictEqual('1', replies[2].b)
assert.strictEqual('fancy', replies[2].extra) assert.strictEqual('fancy', replies[2].extra)
assert.strictEqual('here', replies[2].things) assert.strictEqual('here', replies[2].things)
return done()
}) })
}) })
it('reports EXECABORT exceptions when they occur (while queueing)', (done) => { it('reports EXECABORT exceptions when they occur (while queueing)', () => {
client.multi().config('bar').set('foo').set('bar').exec((err, reply) => { return client.multi().config('bar').set('foo').set('bar').exec().then(assert, (err) => {
assert.strictEqual(err.code, 'EXECABORT') assert.strictEqual(err.code, 'EXECABORT')
assert.strictEqual(reply, undefined, 'The reply should have been discarded')
assert(err.message.match(/^EXECABORT/), 'Error message should begin with EXECABORT') assert(err.message.match(/^EXECABORT/), 'Error message should begin with EXECABORT')
assert.strictEqual(err.errors.length, 2, 'err.errors should have 2 items') assert.strictEqual(err.errors.length, 2, 'err.errors should have 2 items')
assert.strictEqual(err.errors[0].command, 'SET') assert.strictEqual(err.errors[0].command, 'SET')
assert.strictEqual(err.errors[0].code, 'ERR') assert.strictEqual(err.errors[0].code, 'ERR')
assert.strictEqual(err.errors[0].position, 1) assert.strictEqual(err.errors[0].position, 1)
assert(/^ERR/.test(err.errors[0].message), 'Actuall error message should begin with ERR') assert(/^ERR/.test(err.errors[0].message), 'Actual error message should begin with ERR')
return done()
}) })
}) })
it('reports multiple exceptions when they occur (while EXEC is running)', (done) => { it('reports multiple exceptions when they occur (while EXEC is running)', () => {
client.multi().config('bar').debug('foo').eval('return {err=\'this is an error\'}', 0).exec((err, reply) => { return client.multi().config('bar').debug('foo').eval('return {err=\'this is an error\'}', 0).exec().then(assert, (err) => {
assert.strictEqual(err, null) assert.strictEqual(err.replies.length, 3)
assert.strictEqual(reply.length, 3) assert.strictEqual(err.replies[0].code, 'ERR')
assert.strictEqual(reply[0].code, 'ERR') assert.strictEqual(err.replies[0].command, 'CONFIG')
assert.strictEqual(reply[0].command, 'CONFIG') assert.strictEqual(err.replies[2].code, undefined)
assert.strictEqual(reply[2].code, undefined) assert.strictEqual(err.replies[2].command, 'EVAL')
assert.strictEqual(reply[2].command, 'EVAL') assert(/^this is an error/.test(err.replies[2].message))
assert(/^this is an error/.test(reply[2].message)) assert(/^ERR/.test(err.replies[0].message), 'Error message should begin with ERR')
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR') assert(/^ERR/.test(err.replies[1].message), 'Error message should begin with ERR')
assert(/^ERR/.test(reply[1].message), 'Error message should begin with ERR')
return done()
}) })
}) })
it('reports multiple exceptions when they occur (while EXEC is running) promisified', () => {
return client.multi().config('bar').debug('foo').eval('return {err=\'this is an error\'}', 0).execAsync().then((reply) => {
assert.strictEqual(reply.length, 3)
assert.strictEqual(reply[0].code, 'ERR')
assert.strictEqual(reply[0].command, 'CONFIG')
assert.strictEqual(reply[2].code, undefined)
assert.strictEqual(reply[2].command, 'EVAL')
assert(/^this is an error/.test(reply[2].message))
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR')
assert(/^ERR/.test(reply[1].message), 'Error message should begin with ERR')
})
})
it('reports multiple exceptions when they occur (while EXEC is running) and calls cb', (done) => {
const multi = client.multi()
multi.config('bar', helper.isError())
multi.set('foo', 'bar', helper.isString('OK'))
multi.debug('foo').exec((err, reply) => {
assert.strictEqual(err, null)
assert.strictEqual(reply.length, 3)
assert.strictEqual(reply[0].code, 'ERR')
assert(/^ERR/.test(reply[0].message), 'Error message should begin with ERR')
assert(/^ERR/.test(reply[2].message), 'Error message should begin with ERR')
assert.strictEqual(reply[1], 'OK')
client.get('foo', helper.isString('bar', done))
})
})
it('emits an error if no callback has been provided and execabort error occured', (done) => {
const multi = client.multi()
multi.config('bar')
multi.set('foo')
multi.exec()
client.on('error', (err) => {
assert.strictEqual(err.code, 'EXECABORT')
done()
})
})
it('should work without any callback', (done) => {
const multi = client.multi()
multi.set('baz', 'binary')
multi.set('foo', 'bar')
multi.exec()
client.get('foo', helper.isString('bar', done))
})
it('should not use a transaction with execAtomic if no command is used', () => { it('should not use a transaction with execAtomic if no command is used', () => {
const multi = client.multi() const multi = client.multi()
let test = false let test = false
@@ -601,7 +449,7 @@ describe('The \'multi\' method', () => {
assert(test) assert(test)
}) })
it('should use transaction with execAtomic and more than one command used', (done) => { it('should use transaction with execAtomic and more than one command used', () => {
const multi = client.multi() const multi = client.multi()
let test = false let test = false
multi.execBatch = function () { multi.execBatch = function () {
@@ -609,18 +457,17 @@ describe('The \'multi\' method', () => {
} }
multi.set('baz', 'binary') multi.set('baz', 'binary')
multi.get('baz') multi.get('baz')
multi.execAtomic(done) const promise = multi.execAtomic()
assert(!test) assert(!test)
return promise
}) })
it('do not mutate arguments in the multi constructor', (done) => { it('do not mutate arguments in the multi constructor', () => {
const input = [['set', 'foo', 'bar'], ['get', 'foo']] const input = [['set', 'foo', 'bar'], ['get', 'foo']]
client.multi(input).exec((err, res) => { return client.multi(input).exec().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(input.length, 2) assert.strictEqual(input.length, 2)
assert.strictEqual(input[0].length, 3) assert.strictEqual(input[0].length, 3)
assert.strictEqual(input[1].length, 2) assert.strictEqual(input[1].length, 2)
done()
}) })
}) })
@@ -630,29 +477,14 @@ describe('The \'multi\' method', () => {
assert.strictEqual(err.code, 'ECONNREFUSED') assert.strictEqual(err.code, 'ECONNREFUSED')
}) })
client.on('ready', () => { client.on('ready', () => {
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec((err, res) => { client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec().then((res) => {
assert(!err)
assert.strictEqual(res[1], 'bar') assert.strictEqual(res[1], 'bar')
done() done()
}) })
}) })
}) })
it('emits error once if reconnecting after multi has been executed but not yet returned without callback', (done) => { it('indivdual commands work properly with multi', () => {
// NOTE: If uncork is called async by postponing it to the next tick, this behavior is going to change.
// The command won't be processed anymore two errors are returned instead of one
client.on('error', (err) => {
assert.strictEqual(err.code, 'UNCERTAIN_STATE')
client.get('foo', helper.isString('bar', done))
})
// The commands should still be fired, no matter that the socket is destroyed on the same tick
client.multi().set('foo', 'bar').get('foo').exec()
// Abort connection before the value returned
client.stream.destroy()
})
it('indivdual commands work properly with multi', (done) => {
// Neither of the following work properly in a transactions: // Neither of the following work properly in a transactions:
// (This is due to Redis not returning the reply as expected / resulting in undefined behavior) // (This is due to Redis not returning the reply as expected / resulting in undefined behavior)
// (Likely there are more commands that do not work with a transaction) // (Likely there are more commands that do not work with a transaction)
@@ -666,31 +498,23 @@ describe('The \'multi\' method', () => {
// //
// Make sure sendCommand is not called // Make sure sendCommand is not called
client.sendCommand = function () { client.sendCommand = () => {
throw new Error('failed') throw new Error('failed')
} }
assert.strictEqual(client.selectedDb, undefined) assert.strictEqual(client.selectedDb, undefined)
const multi = client.multi() const multi = client.multi()
multi.select(5, (err, res) => { multi.select(5)
assert.strictEqual(err, null) // multi.client('reply', 'on') // Redis v.3.2
assert.strictEqual(client.selectedDb, 5) multi.set('foo', 'bar')
assert.strictEqual(res, 'OK') multi.info()
assert.notDeepEqual(client.serverInfo.db5, { avg_ttl: 0, expires: 0, keys: 1 }) multi.get('foo')
}) return multi.exec().then((res) => {
// multi.client('reply', 'on', helper.isString('OK')); // Redis v.3.2
multi.set('foo', 'bar', helper.isString('OK'))
multi.info((err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.indexOf('# Server\r\nredis_version:'), 0)
assert.deepEqual(client.serverInfo.db5, { avg_ttl: 0, expires: 0, keys: 1 })
})
multi.get('foo', helper.isString('bar'))
multi.exec((err, res) => {
assert.strictEqual(err, null)
res[2] = res[2].substr(0, 10) res[2] = res[2].substr(0, 10)
assert.deepEqual(res, ['OK', 'OK', '# Server\r\n', 'bar']) assert.strictEqual(client.selectedDb, 5)
client.flushdb(done) assert.deepStrictEqual(client.serverInfo.db5, { avg_ttl: 0, expires: 0, keys: 1 })
assert.deepStrictEqual(res, ['OK', 'OK', '# Server\r\n', 'bar'])
return client.flushdb()
}) })
}) })
}) })

View File

@@ -30,13 +30,13 @@ describe('The nodeRedis client', () => {
}) })
}) })
it('convert minus to underscore in Redis function names', (done) => { it('convert minus to underscore in Redis function names', () => {
const names = Object.keys(redis.RedisClient.prototype) const names = Object.keys(redis.RedisClient.prototype)
client = redis.createClient() client = redis.createClient()
for (let i = 0; i < names.length; i++) { for (let i = 0; i < names.length; i++) {
assert(/^([a-zA-Z_][a-zA-Z_0-9]*)?$/.test(client[names[i]].name)) assert(/^([a-zA-Z_][a-zA-Z_0-9]*)?$/.test(client[names[i]].name))
} }
client.quit(done) return client.quit()
}) })
it('reset the parser while reconnecting (See #1190)', (done) => { it('reset the parser while reconnecting (See #1190)', (done) => {
@@ -64,11 +64,9 @@ describe('The nodeRedis client', () => {
}) })
describe('when connected', () => { describe('when connected', () => {
beforeEach((done) => { beforeEach(() => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
client.once('connect', () => { return client.flushdb()
client.flushdb(done)
})
}) })
describe('duplicate', () => { describe('duplicate', () => {
@@ -122,7 +120,7 @@ describe('The nodeRedis client', () => {
client.duplicate((err, client) => { client.duplicate((err, client) => {
assert(!err) assert(!err)
assert.strictEqual(client.ready, true) assert.strictEqual(client.ready, true)
client.quit(done) client.quit().then(() => done())
}) })
}) })
@@ -134,35 +132,20 @@ describe('The nodeRedis client', () => {
done(client) done(client)
}) })
}) })
it('works with a promises', () => {
return client.duplicateAsync().then((client) => {
assert.strictEqual(client.ready, true)
return client.quitAsync()
})
})
it('works with a promises and errors', () => {
return client.duplicateAsync({
port: 9999
}).catch((err) => {
assert.strictEqual(err.code, 'ECONNREFUSED')
})
})
}) })
describe('big data', () => { describe('big data', () => {
// Check if the fast mode for big strings is working correct // Check if the fast mode for big strings is working correct
it('safe strings that are bigger than 30000 characters', (done) => { it('safe strings that are bigger than 30000 characters', () => {
let str = 'foo ಠ_ಠ bar ' let str = 'foo ಠ_ಠ bar '
while (str.length < 111111) { while (str.length < 111111) {
str += str str += str
} }
client.set('foo', str) client.set('foo', str)
client.get('foo', helper.isString(str, done)) return client.get('foo').then(helper.isString(str))
}) })
it('safe strings that are bigger than 30000 characters with multi', (done) => { it('safe strings that are bigger than 30000 characters with multi', () => {
let str = 'foo ಠ_ಠ bar ' let str = 'foo ಠ_ಠ bar '
while (str.length < 111111) { while (str.length < 111111) {
str += str str += str
@@ -176,102 +159,68 @@ describe('The nodeRedis client', () => {
assert(!client.fireStrings) assert(!client.fireStrings)
temp(data) temp(data)
} }
client.multi().set('foo', str).get('foo', helper.isString(str)).exec((err, res) => { const promise = client.multi().set('foo', str).get('foo').exec().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(called, true) assert.strictEqual(called, true)
assert.strictEqual(res[1], str) assert.strictEqual(res[1], str)
done()
}) })
assert(client.fireStrings) assert(client.fireStrings)
return promise
}) })
}) })
describe('sendCommand', () => { describe('sendCommand', () => {
it('omitting args should be fine', (done) => { it('omitting args should be fine', () => {
client.serverInfo = {} client.serverInfo = {}
client.sendCommand('info') client.sendCommand('info')
client.sendCommand('ping', (err, res) => { return client.sendCommand('ping').then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(res, 'PONG') assert.strictEqual(res, 'PONG')
// Check if the previous info command used the internal individual info command // Check if the previous info command used the internal individual info command
assert.notDeepEqual(client.serverInfo, {}) assert.notDeepEqual(client.serverInfo, {})
client.serverInfo = {} client.serverInfo = {}
}) client.sendCommand('ping', null).then(helper.isString('PONG'))
client.sendCommand('info', null, undefined) return client.sendCommand('info').then((res) => {
client.sendCommand('ping', null, (err, res) => { assert(/redis_version/.test(res))
assert.strictEqual(err, null) // The individual info command should also be called by using sendCommand
assert.strictEqual(res, 'PONG') assert.notDeepEqual(client.serverInfo, {})
// Check if the previous info command used the internal individual info command })
assert.notDeepEqual(client.serverInfo, {})
client.serverInfo = {}
})
client.sendCommand('info', undefined, undefined)
client.sendCommand('ping', (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res, 'PONG')
// Check if the previous info command used the internal individual info command
assert.notDeepEqual(client.serverInfo, {})
client.serverInfo = {}
})
client.sendCommand('info', undefined, (err, res) => {
assert.strictEqual(err, null)
assert(/redis_version/.test(res))
// The individual info command should also be called by using sendCommand
assert.notDeepEqual(client.serverInfo, {})
done()
}) })
}) })
it('using multi with sendCommand should work as individual command instead of using the internal multi', (done) => { 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 multis as you want in nodeRedis // This is necessary to keep backwards compatibility and it is the only way to handle multis as you want in nodeRedis
client.sendCommand('multi') client.sendCommand('multi')
client.sendCommand('set', ['foo', 'bar'], helper.isString('QUEUED')) client.sendCommand('set', ['foo', 'bar']).then(helper.isString('QUEUED'))
client.get('foo') client.get('foo')
// exec is not manipulated if not fired by the individual multi command // 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 // As the multi command is handled individually by the user he also has to handle the return value
client.exec(helper.isDeepEqual(['OK', 'bar'], done)) return client.exec().then(helper.isDeepEqual(['OK', 'bar']))
}) })
it('multi should be handled special', (done) => { it('multi should be handled special', () => {
client.sendCommand('multi', undefined, helper.isString('OK')) client.sendCommand('multi', undefined).then(helper.isString('OK'))
const args = ['test', 'bla'] const args = ['test', 'bla']
client.sendCommand('set', args, helper.isString('QUEUED')) client.sendCommand('set', args).then(helper.isString('QUEUED'))
assert.deepEqual(args, ['test', 'bla']) // Check args manipulation assert.deepStrictEqual(args, ['test', 'bla']) // Check args manipulation
client.get('test', helper.isString('QUEUED')) 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 // As the multi command is handled individually by the user he also has to handle the return value
client.exec(helper.isDeepEqual(['OK', 'bla'], done)) return client.exec().then(helper.isDeepEqual(['OK', 'bla']))
})
it('using another type as cb should throw', () => {
try {
client.sendCommand('set', ['test', 'bla'], [true])
throw new Error('failed')
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Array" for callback function')
}
try {
client.sendCommand('set', ['test', 'bla'], null)
throw new Error('failed')
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "null" for callback function')
}
}) })
it('command argument has to be of type string', () => { it('command argument has to be of type string', () => {
try { try {
client.sendCommand(true, ['test', 'bla'], () => {}) client.sendCommand(true, ['test', 'bla'])
throw new Error('failed') throw new Error('failed')
} catch (err) { } catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Boolean" for command name') assert.strictEqual(err.message, 'Wrong input type "Boolean" for command name')
} }
try { try {
client.sendCommand(undefined, ['test', 'bla'], () => {}) client.sendCommand(undefined, ['test', 'bla'])
throw new Error('failed') throw new Error('failed')
} catch (err) { } catch (err) {
assert.strictEqual(err.message, 'Wrong input type "undefined" for command name') assert.strictEqual(err.message, 'Wrong input type "undefined" for command name')
} }
try { try {
client.sendCommand(null, ['test', 'bla'], () => {}) client.sendCommand(null, ['test', 'bla'])
throw new Error('failed') throw new Error('failed')
} catch (err) { } catch (err) {
assert.strictEqual(err.message, 'Wrong input type "null" for command name') assert.strictEqual(err.message, 'Wrong input type "null" for command name')
@@ -287,56 +236,44 @@ describe('The nodeRedis client', () => {
} }
}) })
it('passing a callback as args and as callback should throw', () => { it('multi should be handled special', () => {
try { client.sendCommand('multi', undefined).then(helper.isString('OK'))
client.sendCommand('info', () => {}, () => {})
throw new Error('failed')
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Function" for args')
}
})
it('multi should be handled special', (done) => {
client.sendCommand('multi', undefined, helper.isString('OK'))
const args = ['test', 'bla'] const args = ['test', 'bla']
client.sendCommand('set', args, helper.isString('QUEUED')) client.sendCommand('set', args).then(helper.isString('QUEUED'))
assert.deepEqual(args, ['test', 'bla']) // Check args manipulation assert.deepStrictEqual(args, ['test', 'bla']) // Check args manipulation
client.get('test', helper.isString('QUEUED')) 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 // As the multi command is handled individually by the user he also has to handle the return value
client.exec(helper.isDeepEqual(['OK', 'bla'], done)) return client.exec().then(helper.isDeepEqual(['OK', 'bla']))
}) })
it('the args array may contain a arbitrary number of arguments', (done) => { it('the args array may contain a arbitrary number of arguments', () => {
client.sendCommand('mset', ['foo', 1, 'bar', 2, 'baz', 3], helper.isString('OK')) 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 // As the multi command is handled individually by the user he also has to handle the return value
client.mget(['foo', 'bar', 'baz'], helper.isDeepEqual(['1', '2', '3'], done)) return client.mget(['foo', 'bar', 'baz']).then(helper.isDeepEqual(['1', '2', '3']))
}) })
it('sendCommand with callback as args', (done) => { it('sendCommand with callback as args', () => {
client.sendCommand('abcdef', (err, res) => { return client.sendCommand('abcdef').then(assert, helper.isError(/ERR unknown command 'abcdef'/))
assert.strictEqual(err.message, 'ERR unknown command \'abcdef\'')
done()
})
}) })
}) })
describe('retryUnfulfilledCommands', () => { describe('retryUnfulfilledCommands', () => {
it('should retry all commands instead of returning an error if a command did not yet return after a connection loss', (done) => { 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({ const bclient = redis.createClient({
retryUnfulfilledCommands: true retryUnfulfilledCommands: true
}) })
bclient.blpop('blocking list 2', 5, (err, value) => { const promise = bclient.blpop('blocking list 2', 5).then((value) => {
assert.strictEqual(value[0], 'blocking list 2') assert.strictEqual(value[0], 'blocking list 2')
assert.strictEqual(value[1], 'initial value') assert.strictEqual(value[1], 'initial value')
bclient.end(true) bclient.end(true)
done(err)
}) })
bclient.once('ready', () => { bclient.once('ready', () => {
setTimeout(() => { setTimeout(() => {
bclient.stream.destroy() bclient.stream.destroy()
client.rpush('blocking list 2', 'initial value', helper.isNumber(1)) client.rpush('blocking list 2', 'initial value').then(helper.isNumber(1))
}, 100) }, 100)
}) })
return promise
}) })
it('should retry all commands even if the offline queue is disabled', (done) => { it('should retry all commands even if the offline queue is disabled', (done) => {
@@ -345,15 +282,15 @@ describe('The nodeRedis client', () => {
retryUnfulfilledCommands: true retryUnfulfilledCommands: true
}) })
bclient.once('ready', () => { bclient.once('ready', () => {
bclient.blpop('blocking list 2', 5, (err, value) => { bclient.blpop('blocking list 2', 5).then((value) => {
assert.strictEqual(value[0], 'blocking list 2') assert.strictEqual(value[0], 'blocking list 2')
assert.strictEqual(value[1], 'initial value') assert.strictEqual(value[1], 'initial value')
bclient.end(true) bclient.end(true)
done(err) done()
}) })
setTimeout(() => { setTimeout(() => {
bclient.stream.destroy() bclient.stream.destroy()
client.rpush('blocking list 2', 'initial value', helper.isNumber(1)) client.rpush('blocking list 2', 'initial value').then(helper.isNumber(1))
}, 100) }, 100)
}) })
}) })
@@ -367,7 +304,7 @@ describe('The nodeRedis client', () => {
done(new Error('failed')) done(new Error('failed'))
} }
}, 20) }, 20)
const cb = function (err, res) { const cb = function (err) {
assert(/Connection forcefully ended|The connection is already closed./.test(err.message)) assert(/Connection forcefully ended|The connection is already closed./.test(err.message))
assert.strictEqual(err.code, 'NR_CLOSED') assert.strictEqual(err.code, 'NR_CLOSED')
end() end()
@@ -376,7 +313,7 @@ describe('The nodeRedis client', () => {
if (i === 10) { if (i === 10) {
client.end() client.end()
} }
client.set('foo', 'bar', cb) client.set('foo', 'bar').then(assert, cb)
} }
client.on('warning', () => {}) // Ignore deprecation message client.on('warning', () => {}) // Ignore deprecation message
setTimeout(() => { setTimeout(() => {
@@ -386,10 +323,8 @@ describe('The nodeRedis client', () => {
}) })
it('used with flush set to true', (done) => { it('used with flush set to true', (done) => {
const end = helper.callFuncAfter(() => { const end = helper.callFuncAfter(done, 20)
done() const cb = function (err) {
}, 20)
const cb = function (err, res) {
assert(/Connection forcefully ended|The connection is already closed./.test(err.message)) assert(/Connection forcefully ended|The connection is already closed./.test(err.message))
end() end()
} }
@@ -398,106 +333,21 @@ describe('The nodeRedis client', () => {
client.end(true) client.end(true)
client.stream.write('foo') // Trigger an error on the closed stream that we ignore client.stream.write('foo') // Trigger an error on the closed stream that we ignore
} }
client.set('foo', 'bar', cb) client.set('foo', 'bar').then(assert, cb)
} }
}) })
it('emits an aggregate error if no callback was present for multiple commands in debugMode', (done) => {
redis.debugMode = true
const unhookIntercept = intercept((data) => {
return '' // Don't print the debug messages
})
client.set('foo', 'bar')
client.set('baz', 'hello world')
client.on('error', (err) => {
assert(err instanceof Error)
assert(err instanceof redis.AbortError)
assert(err instanceof redis.AggregateError)
assert.strictEqual(err.name, 'AggregateError')
assert.strictEqual(err.errors.length, 2)
assert.strictEqual(err.message, 'Connection forcefully ended and commands aborted.')
assert.strictEqual(err.code, 'NR_CLOSED')
assert.strictEqual(err.errors[0].message, 'Connection forcefully ended and command aborted. It might have been processed.')
assert.strictEqual(err.errors[0].command, 'SET')
assert.strictEqual(err.errors[0].code, 'NR_CLOSED')
assert.deepEqual(err.errors[0].args, ['foo', 'bar'])
done()
})
client.end(true)
unhookIntercept()
redis.debugMode = false
})
it('emits an abort error if no callback was present for a single commands', (done) => {
redis.debugMode = true
const unhookIntercept = intercept((data) => {
return '' // Don't print the debug messages
})
client.set('foo', 'bar')
client.on('error', (err) => {
assert(err instanceof Error)
assert(err instanceof redis.AbortError)
assert(!(err instanceof redis.AggregateError))
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted. It might have been processed.')
assert.strictEqual(err.command, 'SET')
assert.strictEqual(err.code, 'NR_CLOSED')
assert.deepEqual(err.args, ['foo', 'bar'])
done()
})
client.end(true)
unhookIntercept()
redis.debugMode = false
})
it('does not emit abort errors if no callback was present while not being in debugMode ', (done) => {
client.set('foo', 'bar')
client.end(true)
setTimeout(done, 100)
})
}) })
describe('commands after using .quit should fail', () => { describe('commands after using .quit should fail', () => {
it('return an error in the callback', function (done) { it('return an error in the callback version two', function () {
if (helper.redisProcess().spawnFailed()) this.skip()
// TODO: Investigate why this test is failing hard and killing mocha if using '/tmp/redis.sock'.
// Seems like something is wrong with nyc while passing a socket connection to create client!
client = redis.createClient()
client.quit(() => {
client.get('foo', (err, res) => {
assert.strictEqual(err.message, 'Stream connection ended and command aborted. It might have been processed.')
assert.strictEqual(client.offlineQueue.length, 0)
done()
})
})
})
it('return an error in the callback version two', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client.quit() client.quit()
setTimeout(() => { return client.get('foo').then(assert, (err) => {
client.get('foo', (err, res) => { assert.strictEqual(err.message, 'GET can\'t be processed. The connection is already closed.')
assert.strictEqual(err.message, 'GET can\'t be processed. The connection is already closed.') assert.strictEqual(err.command, 'GET')
assert.strictEqual(err.command, 'GET')
assert.strictEqual(client.offlineQueue.length, 0)
done()
})
}, 50)
})
it('emit an error', function (done) {
if (helper.redisProcess().spawnFailed()) this.skip()
client.quit()
client.on('error', (err) => {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection is already closed.')
assert.strictEqual(err.command, 'SET')
assert.strictEqual(client.offlineQueue.length, 0) assert.strictEqual(client.offlineQueue.length, 0)
done()
}) })
setTimeout(() => {
client.set('foo', 'bar')
}, 50)
}) })
}) })
@@ -512,16 +362,15 @@ describe('The nodeRedis client', () => {
assert.strictEqual(Object.keys(client.serverInfo.db0).length, 3) assert.strictEqual(Object.keys(client.serverInfo.db0).length, 3)
done() done()
}, 4) }, 4)
client.get('recon 1', helper.isString('one', end)) client.get('recon 1').then(helper.isString('one')).then(end)
client.get('recon 1', helper.isString('one', end)) client.get('recon 1').then(helper.isString('one')).then(end)
client.get('recon 2', helper.isString('two', end)) client.get('recon 2').then(helper.isString('two')).then(end)
client.get('recon 2', helper.isString('two', end)) client.get('recon 2').then(helper.isString('two')).then(end)
}) })
}) })
client.set('recon 1', 'one') client.set('recon 1', 'one')
client.set('recon 2', 'two', (err, res) => { client.set('recon 2', 'two').then((res) => {
assert.strictEqual(err, null)
// Do not do this in normal programs. This is to simulate the server closing on us. // 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() // For orderly shutdown in normal programs, do client.quit()
client.stream.destroy() client.stream.destroy()
@@ -540,11 +389,9 @@ describe('The nodeRedis client', () => {
assert.strictEqual(client.monitoring, false, 'monitoring off at start') assert.strictEqual(client.monitoring, false, 'monitoring off at start')
client.set('recon 1', 'one') client.set('recon 1', 'one')
client.monitor((err, res) => { client.monitor().then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(client.monitoring, true, 'monitoring on after monitor()') assert.strictEqual(client.monitoring, true, 'monitoring on after monitor()')
client.set('recon 2', 'two', (err, res) => { client.set('recon 2', 'two').then((res) => {
assert.strictEqual(err, null)
// Do not do this in normal programs. This is to simulate the server closing on us. // 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() // For orderly shutdown in normal programs, do client.quit()
client.stream.destroy() client.stream.destroy()
@@ -556,19 +403,18 @@ describe('The nodeRedis client', () => {
// "Connection in subscriber mode, only subscriber commands may be used" // "Connection in subscriber mode, only subscriber commands may be used"
it('reconnects, unsubscribes, and can retrieve the pre-existing data', (done) => { it('reconnects, unsubscribes, and can retrieve the pre-existing data', (done) => {
client.on('ready', () => { client.on('ready', () => {
client.unsubscribe(helper.isNotError()) client.unsubscribe()
client.on('unsubscribe', (channel, count) => { client.on('unsubscribe', (channel, count) => {
// we should now be out of subscriber mode. // we should now be out of subscriber mode.
assert.strictEqual(channel, 'recon channel') assert.strictEqual(channel, 'recon channel')
assert.strictEqual(count, 0) assert.strictEqual(count, 0)
client.set('foo', 'bar', helper.isString('OK', done)) client.set('foo', 'bar').then(helper.isString('OK')).then(done)
}) })
}) })
client.set('recon 1', 'one') client.set('recon 1', 'one')
client.subscribe('recon channel', (err, res) => { client.subscribe('recon channel').then((res) => {
assert.strictEqual(err, null)
// Do not do this in normal programs. This is to simulate the server closing on us. // 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() // For orderly shutdown in normal programs, do client.quit()
client.stream.destroy() client.stream.destroy()
@@ -577,89 +423,31 @@ describe('The nodeRedis client', () => {
it('reconnects, unsubscribes, and can retrieve the pre-existing data of a explicit channel', (done) => { it('reconnects, unsubscribes, and can retrieve the pre-existing data of a explicit channel', (done) => {
client.on('ready', () => { client.on('ready', () => {
client.unsubscribe('recon channel', helper.isNotError()) client.unsubscribe('recon channel').then(helper.isDeepEqual([0, ['recon channel']]))
client.on('unsubscribe', (channel, count) => { client.on('unsubscribe', (channel, count) => {
// we should now be out of subscriber mode. // we should now be out of subscriber mode.
assert.strictEqual(channel, 'recon channel') assert.strictEqual(channel, 'recon channel')
assert.strictEqual(count, 0) assert.strictEqual(count, 0)
client.set('foo', 'bar', helper.isString('OK', done)) client.set('foo', 'bar').then(helper.isString('OK')).then(done)
}) })
}) })
client.set('recon 1', 'one') client.set('recon 1', 'one')
client.subscribe('recon channel', (err, res) => { client.subscribe('recon channel').then((res) => {
assert.strictEqual(err, null)
// Do not do this in normal programs. This is to simulate the server closing on us. // 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() // For orderly shutdown in normal programs, do client.quit()
client.stream.destroy() client.stream.destroy()
}) })
}) })
}) })
describe('domain', () => {
it('allows client to be executed from within domain', (done) => {
// eslint-disable-next-line
var domain = require('domain').create()
domain.run(() => {
client.set('domain', 'value', (err, res) => {
assert.strictEqual(err, null)
assert.ok(process.domain)
throw new Error('ohhhh noooo')
})
})
// this is the expected and desired behavior
domain.on('error', (err) => {
assert.strictEqual(err.message, 'ohhhh noooo')
domain.exit()
done()
})
})
it('keeps the same domain by using the offline queue', (done) => {
client.end(true)
client = redis.createClient()
// eslint-disable-next-line
var testDomain = require('domain').create()
testDomain.run(() => {
client.set('FOOBAR', 'def', () => {
assert.strictEqual(process.domain, testDomain)
done()
})
})
// eslint-disable-next-line
require('domain').create()
})
it('catches all errors from within the domain', (done) => {
// eslint-disable-next-line
var domain = require('domain').create()
domain.run(() => {
// Trigger an error within the domain
client.end(true)
client.set('domain', 'value')
})
domain.on('error', (err) => {
assert.strictEqual(err.message, 'SET can\'t be processed. The connection is already closed.')
domain.exit()
done()
})
})
})
}) })
describe('utf8', () => { describe('utf8', () => {
it('handles utf-8 keys', (done) => { it('handles utf-8 keys', () => {
const utf8Sample = 'ಠ_ಠ' const utf8Sample = 'ಠ_ಠ'
client.set(['utf8test', utf8Sample], helper.isString('OK')) client.set(['utf8test', utf8Sample]).then(helper.isString('OK'))
client.get(['utf8test'], (err, obj) => { return client.get(['utf8test']).then(helper.isString(utf8Sample))
assert.strictEqual(utf8Sample, obj)
done(err)
})
}) })
}) })
}) })
@@ -687,13 +475,11 @@ describe('The nodeRedis client', () => {
it('keep execution order for commands that may fire while redis is still loading', (done) => { it('keep execution order for commands that may fire while redis is still loading', (done) => {
client = redis.createClient.apply(null, args) client = redis.createClient.apply(null, args)
let fired = false let fired = false
client.set('foo', 'bar', (err, res) => { client.set('foo', 'bar').then((res) => {
assert.strictEqual(err, null)
assert.strictEqual(fired, false) assert.strictEqual(fired, false)
done() done()
}) })
client.info((err, res) => { client.info().then(() => {
assert.strictEqual(err, null)
fired = true fired = true
}) })
}) })
@@ -730,10 +516,10 @@ describe('The nodeRedis client', () => {
assert.strictEqual(err, error) assert.strictEqual(err, error)
assert(err instanceof redis.ParserError) assert(err instanceof redis.ParserError)
// After the hard failure work properly again. The set should have been processed properly too // After the hard failure work properly again. The set should have been processed properly too
client.get('foo', helper.isString('bar', done)) client.get('foo').then(helper.isString('bar')).then(done)
}) })
client.once('ready', () => { client.once('ready', () => {
client.set('foo', 'bar', (err, res) => { client.set('foo', 'bar').then(assert, (err) => {
assert.strictEqual(err.message, 'Fatal error encountered. Command aborted. It might have been processed.') assert.strictEqual(err.message, 'Fatal error encountered. Command aborted. It might have been processed.')
assert.strictEqual(err.code, 'NR_FATAL') assert.strictEqual(err.code, 'NR_FATAL')
assert(err instanceof redis.AbortError) assert(err instanceof redis.AbortError)
@@ -759,7 +545,7 @@ describe('The nodeRedis client', () => {
}) })
setTimeout(() => { setTimeout(() => {
client.set('foo', 'bar', (err, result) => { client.set('foo', 'bar').then(helper.fail, (err) => {
if (!finished) done(err) if (!finished) done(err)
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted.') assert.strictEqual(err.message, 'Connection forcefully ended and command aborted.')
}) })
@@ -772,17 +558,19 @@ describe('The nodeRedis client', () => {
}, 50) }, 50)
}) })
// TODO: Fix this by adding the CONNECTION_BROKEN back in
it.skip('enqueues operation and keep the queue while trying to reconnect', (done) => { it.skip('enqueues operation and keep the queue while trying to reconnect', (done) => {
client = redis.createClient(9999, null, { client = redis.createClient(9999, null, {
retryStrategy (options) { retryStrategy (options) {
if (options.attempt < 4) { if (options.attempt < 4) {
return 200 return 50
} }
} }
}) })
let i = 0 let i = 0
client.on('error', (err) => { client.on('error', (err) => {
console.log(err)
if (err.code === 'CONNECTION_BROKEN') { if (err.code === 'CONNECTION_BROKEN') {
assert(i, 3) assert(i, 3)
assert.strictEqual(client.offlineQueue.length, 0) assert.strictEqual(client.offlineQueue.length, 0)
@@ -805,12 +593,10 @@ describe('The nodeRedis client', () => {
assert.strictEqual(params.timesConnected, 0) assert.strictEqual(params.timesConnected, 0)
assert(params.error instanceof Error) assert(params.error instanceof Error)
assert(typeof params.totalRetryTime === 'number') assert(typeof params.totalRetryTime === 'number')
assert.strictEqual(client.offlineQueue.length, 2) assert.strictEqual(client.offlineQueue.length, 1)
}) })
// Should work with either a callback or without client.set('foo', 'bar').then(assert, (err) => {
client.set('baz', 13)
client.set('foo', 'bar', (err, result) => {
assert(i, 3) assert(i, 3)
assert(err) assert(err)
assert.strictEqual(client.offlineQueue.length, 0) assert.strictEqual(client.offlineQueue.length, 0)
@@ -823,39 +609,28 @@ describe('The nodeRedis client', () => {
client.once('ready', () => { client.once('ready', () => {
const multi = client.multi() const multi = client.multi()
multi.config('bar') multi.config('bar')
const cb = function (err, reply) {
assert.strictEqual(err.code, 'UNCERTAIN_STATE')
}
for (let i = 0; i < 12; i += 3) { for (let i = 0; i < 12; i += 3) {
client.set(`foo${i}`, `bar${i}`) client.set(`foo${i}`, `bar${i}`).then(helper.fail, helper.isError)
multi.set(`foo${i + 1}`, `bar${i + 1}`, cb) multi.set(`foo${i + 1}`, `bar${i + 1}`)
multi.set(`foo${i + 2}`, `bar${i + 2}`) multi.set(`foo${i + 2}`, `bar${i + 2}`)
} }
multi.exec() 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) assert.strictEqual(client.commandQueue.length, 15)
helper.killConnection(client) helper.killConnection(client)
}) })
const end = helper.callFuncAfter(done, 3) const end = helper.callFuncAfter(done, 2)
client.on('error', (err) => { client.on('error', (err) => {
if (err.command === 'EXEC') { assert.strictEqual(err.code, 'ECONNREFUSED')
assert.strictEqual(client.commandQueue.length, 0) assert.strictEqual(err.errno, 'ECONNREFUSED')
assert.strictEqual(err.errors.length, 9) assert.strictEqual(err.syscall, 'connect')
assert.strictEqual(err.errors[1].command, 'SET') end()
assert.deepEqual(err.errors[1].args, ['foo1', 'bar1'])
end()
} else if (err.code === 'UNCERTAIN_STATE') {
assert.strictEqual(client.commandQueue.length, 0)
assert.strictEqual(err.errors.length, 4)
assert.strictEqual(err.errors[0].command, 'SET')
assert.deepEqual(err.errors[0].args, ['foo0', 'bar0'])
end()
} else {
assert.strictEqual(err.code, 'ECONNREFUSED')
assert.strictEqual(err.errno, 'ECONNREFUSED')
assert.strictEqual(err.syscall, 'connect')
end()
}
}) })
}) })
}) })
@@ -867,7 +642,7 @@ describe('The nodeRedis client', () => {
}) })
client.on('ready', () => { client.on('ready', () => {
client.stream.destroy() client.stream.destroy()
client.set('foo', 'bar', (err, res) => { client.set('foo', 'bar').then(assert, (err) => {
assert.strictEqual(err.message, 'SET can\'t be processed. Stream not writeable.') assert.strictEqual(err.message, 'SET can\'t be processed. Stream not writeable.')
done() done()
}) })
@@ -881,67 +656,25 @@ describe('The nodeRedis client', () => {
const end = helper.callFuncAfter(done, 3) const end = helper.callFuncAfter(done, 3)
client.on('error', (err) => { client.on('error', (err) => {
assert(/offline queue is deactivated|ECONNREFUSED/.test(err.message)) assert(/ECONNREFUSED/.test(err.message))
assert.strictEqual(client.commandQueue.length, 0) assert.strictEqual(client.commandQueue.length, 0)
end() end()
}) })
client.set('foo', 'bar') 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(() => { assert.doesNotThrow(() => {
client.set('foo', 'bar', (err) => { client.set('foo', 'bar').then(assert, (err) => {
// should callback with an error // should callback with an error
assert.ok(err) assert.ok(err)
setTimeout(end, 50) setTimeout(end, 50)
}) })
}) })
}) })
it('flushes the command queue if connection is lost', (done) => {
client = redis.createClient({
enableOfflineQueue: false
})
redis.debugMode = true
const unhookIntercept = intercept(() => {
return ''
})
client.once('ready', () => {
const multi = client.multi()
multi.config('bar')
const cb = function (err, reply) {
assert.strictEqual(err.code, 'UNCERTAIN_STATE')
}
for (let i = 0; i < 12; i += 3) {
client.set(`foo${i}`, `bar${i}`)
multi.set(`foo${i + 1}`, `bar${i + 1}`, cb)
multi.set(`foo${i + 2}`, `bar${i + 2}`)
}
multi.exec()
assert.strictEqual(client.commandQueue.length, 15)
helper.killConnection(client)
})
const end = helper.callFuncAfter(done, 3)
client.on('error', (err) => {
assert.strictEqual(client.commandQueue.length, 0)
if (err.command === 'EXEC') {
assert.strictEqual(err.errors.length, 9)
end()
} else if (err.code === 'UNCERTAIN_STATE') {
assert.strictEqual(err.errors.length, 4)
end()
} else {
assert.strictEqual(err.code, 'ECONNREFUSED')
assert.strictEqual(err.errno, 'ECONNREFUSED')
assert.strictEqual(err.syscall, 'connect')
redis.debugMode = false
client.end(true)
unhookIntercept()
end()
}
})
})
}) })
}) })
}) })

View File

@@ -10,84 +10,75 @@ describe('prefix key names', () => {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client = null let client = null
beforeEach((done) => { beforeEach(() => {
client = redis.createClient({ client = redis.createClient({
prefix: 'test:prefix:' prefix: 'test:prefix:'
}) })
client.on('ready', () => { return client.flushdb()
client.flushdb((err) => {
done(err)
})
})
}) })
afterEach(() => { afterEach(() => {
client.end(true) client.end(true)
}) })
it('auto prefix set / get', (done) => { it('auto prefix set / get', () => {
client.set('key', 'value', helper.isString('OK')) return Promise.all([
client.get('key', helper.isString('value')) client.set('key', 'value').then(helper.isString('OK')),
client.getrange('key', 1, -1, (err, reply) => { client.get('key').then(helper.isString('value')),
assert.strictEqual(reply, 'alue') client.getrange('key', 1, -1).then((reply) => {
assert.strictEqual(err, null) assert.strictEqual(reply, 'alue')
}) }),
client.exists('key', helper.isNumber(1)) client.exists('key').then(helper.isNumber(1)),
// The key will be prefixed itself // The key will be prefixed itself
client.exists('test:prefix:key', helper.isNumber(0)) client.exists('test:prefix:key').then(helper.isNumber(0)),
client.mset('key2', 'value2', 'key3', 'value3') client.mset('key2', 'value2', 'key3', 'value3'),
client.keys('*', (err, res) => { client.keys('*').then((res) => {
assert.strictEqual(err, null) assert.strictEqual(res.length, 3)
assert.strictEqual(res.length, 3) assert(res.includes('test:prefix:key'))
assert(res.indexOf('test:prefix:key') !== -1) assert(res.includes('test:prefix:key2'))
assert(res.indexOf('test:prefix:key2') !== -1) assert(res.includes('test:prefix:key3'))
assert(res.indexOf('test:prefix:key3') !== -1) })
done() ])
})
}) })
it('auto prefix set / get with .batch', (done) => { it('auto prefix set / get with .batch', () => {
const batch = client.batch() const batch = client.batch()
batch.set('key', 'value', helper.isString('OK')) batch.set('key', 'value')
batch.get('key', helper.isString('value')) batch.get('key')
batch.getrange('key', 1, -1, (err, reply) => { batch.getrange('key', 1, -1)
assert.strictEqual(reply, 'alue') batch.exists('key')
assert.strictEqual(err, null)
})
batch.exists('key', helper.isNumber(1))
// The key will be prefixed itself // The key will be prefixed itself
batch.exists('test:prefix:key', helper.isNumber(0)) batch.exists('test:prefix:key')
batch.mset('key2', 'value2', 'key3', 'value3') batch.mset('key2', 'value2', 'key3', 'value3')
batch.keys('*', (err, res) => { batch.keys('*')
assert.strictEqual(err, null) return batch.exec().then((res) => {
assert.strictEqual(res.length, 3) const prefixes = res.pop()
assert(res.indexOf('test:prefix:key') !== -1) assert.deepStrictEqual(res, ['OK', 'value', 'alue', 1, 0, 'OK'])
assert(res.indexOf('test:prefix:key2') !== -1) assert.strictEqual(prefixes.length, 3)
assert(res.indexOf('test:prefix:key3') !== -1) assert(prefixes.includes('test:prefix:key'))
assert(prefixes.includes('test:prefix:key2'))
assert(prefixes.includes('test:prefix:key3'))
}) })
batch.exec(done)
}) })
it('auto prefix set / get with .multi', (done) => { it('auto prefix set / get with .multi', () => {
const multi = client.multi() const multi = client.multi()
multi.set('key', 'value', helper.isString('OK')) multi.set('key', 'value')
multi.get('key', helper.isString('value')) multi.get('key')
multi.getrange('key', 1, -1, (err, reply) => { multi.getrange('key', 1, -1)
assert.strictEqual(reply, 'alue') multi.exists('key')
assert.strictEqual(err, null)
})
multi.exists('key', helper.isNumber(1))
// The key will be prefixed itself // The key will be prefixed itself
multi.exists('test:prefix:key', helper.isNumber(0)) multi.exists('test:prefix:key')
multi.mset('key2', 'value2', 'key3', 'value3') multi.mset('key2', 'value2', 'key3', 'value3')
multi.keys('*', (err, res) => { multi.keys('*')
assert.strictEqual(err, null) return multi.exec().then((res) => {
assert.strictEqual(res.length, 3) const prefixes = res.pop()
assert(res.indexOf('test:prefix:key') !== -1) assert.deepStrictEqual(res, ['OK', 'value', 'alue', 1, 0, 'OK'])
assert(res.indexOf('test:prefix:key2') !== -1) assert.strictEqual(prefixes.length, 3)
assert(res.indexOf('test:prefix:key3') !== -1) assert(prefixes.includes('test:prefix:key'))
assert(prefixes.includes('test:prefix:key2'))
assert(prefixes.includes('test:prefix:key3'))
}) })
multi.exec(done)
}) })
}) })
}) })

View File

@@ -20,14 +20,8 @@ describe('publish/subscribe', () => {
pub = redis.createClient.apply(null, args) pub = redis.createClient.apply(null, args)
sub = redis.createClient.apply(null, args) sub = redis.createClient.apply(null, args)
pub.once('connect', () => { pub.flushdb().then(() => end())
pub.flushdb(() => { sub.once('connect', end)
end()
})
})
sub.once('connect', () => {
end()
})
}) })
describe('disable resubscribe', () => { describe('disable resubscribe', () => {
@@ -36,9 +30,7 @@ describe('publish/subscribe', () => {
sub = redis.createClient({ sub = redis.createClient({
disableResubscribing: true disableResubscribing: true
}) })
sub.once('connect', () => { sub.once('connect', done)
done()
})
}) })
it('does not fire subscribe events after reconnecting', (done) => { it('does not fire subscribe events after reconnecting', (done) => {
@@ -71,9 +63,7 @@ describe('publish/subscribe', () => {
sub = redis.createClient({ sub = redis.createClient({
stringNumbers: true stringNumbers: true
}) })
sub.once('connect', () => { sub.once('connect', done)
done()
})
}) })
it('does not fire subscribe events after reconnecting', (done) => { it('does not fire subscribe events after reconnecting', (done) => {
@@ -86,16 +76,15 @@ describe('publish/subscribe', () => {
sub.on('unsubscribe', (chnl, count) => { sub.on('unsubscribe', (chnl, count) => {
assert.strictEqual(typeof count, 'number') assert.strictEqual(typeof count, 'number')
assert.strictEqual(--i, count) assert.strictEqual(--i, count)
if (count === 0) {
assert.deepStrictEqual(sub.subscriptionSet, {})
end()
}
}) })
sub.subscribe(channel, channel2) sub.subscribe(channel, channel2)
sub.unsubscribe((err, res) => { // Do not pass a channel here! sub.unsubscribe()
if (err) throw err sub.set('foo', 'bar').then(helper.isString('OK'))
assert.strictEqual(sub.pubSubMode, 2) sub.subscribe(channel2).then(end)
assert.deepEqual(sub.subscriptionSet, {})
end()
})
sub.set('foo', 'bar', helper.isString('OK'))
sub.subscribe(channel2, end)
}) })
}) })
@@ -143,8 +132,8 @@ describe('publish/subscribe', () => {
it('receives messages on subscribed channel', (done) => { it('receives messages on subscribed channel', (done) => {
const end = helper.callFuncAfter(done, 2) const end = helper.callFuncAfter(done, 2)
sub.on('subscribe', (chnl, count) => { sub.on('subscribe', (chnl, count) => {
pub.publish(channel, message, (err, res) => { pub.publish(channel, message).then((res) => {
helper.isNumber(1)(err, res) helper.isNumber(1)(res)
end() end()
}) })
}) })
@@ -161,8 +150,8 @@ describe('publish/subscribe', () => {
it('receives messages if subscribe is called after unsubscribe', (done) => { it('receives messages if subscribe is called after unsubscribe', (done) => {
const end = helper.callFuncAfter(done, 2) const end = helper.callFuncAfter(done, 2)
sub.once('subscribe', (chnl, count) => { sub.once('subscribe', (chnl, count) => {
pub.publish(channel, message, (err, res) => { pub.publish(channel, message).then((res) => {
helper.isNumber(1)(err, res) helper.isNumber(1)(res)
end() end()
}) })
}) })
@@ -178,20 +167,24 @@ describe('publish/subscribe', () => {
sub.subscribe(channel) sub.subscribe(channel)
}) })
it('handles SUB UNSUB MSG SUB', (done) => { it('handles SUB UNSUB MSG SUB', () => {
sub.subscribe('chan8') return Promise.all([
sub.subscribe('chan9') sub.subscribe('chan8'),
sub.unsubscribe('chan9') sub.subscribe('chan9'),
pub.publish('chan8', 'something') sub.unsubscribe('chan9'),
sub.subscribe('chan9', done) pub.publish('chan8', 'something'),
sub.subscribe('chan9')
])
}) })
it('handles SUB UNSUB MSG SUB 2', (done) => { it('handles SUB UNSUB MSG SUB 2', () => {
sub.psubscribe('abc*', helper.isDeepEqual([1, ['abc*']])) return Promise.all([
sub.subscribe('xyz') sub.psubscribe('abc*').then(helper.isDeepEqual([1, ['abc*']])),
sub.unsubscribe('xyz') sub.subscribe('xyz'),
pub.publish('abcd', 'something') sub.unsubscribe('xyz'),
sub.subscribe('xyz', done) pub.publish('abcd', 'something'),
sub.subscribe('xyz')
])
}) })
it('emits end event if quit is called from within subscribe', (done) => { it('emits end event if quit is called from within subscribe', (done) => {
@@ -218,8 +211,7 @@ describe('publish/subscribe', () => {
sub.select(3) sub.select(3)
sub.subscribe(channels) sub.subscribe(channels)
sub.on('ready', (err, results) => { sub.on('ready', () => {
if (err) throw err
pub.publish(channels[count], msg[count]) pub.publish(channels[count], msg[count])
count++ count++
}) })
@@ -233,10 +225,9 @@ describe('publish/subscribe', () => {
const end = helper.callFuncAfter(done, 2) const end = helper.callFuncAfter(done, 2)
sub.select(3) sub.select(3)
sub.set('foo', 'bar') sub.set('foo', 'bar')
sub.set('failure', helper.isError()) // Triggering a warning while subscribing should work sub.set('failure').then(helper.fail, helper.isError()) // Triggering a warning while subscribing should work
sub.mget('foo', 'bar', 'baz', 'hello', 'world', helper.isDeepEqual(['bar', null, null, null, null])) sub.mget('foo', 'bar', 'baz', 'hello', 'world').then(helper.isDeepEqual(['bar', null, null, null, null]))
sub.subscribe('somechannel', 'another channel', (err, res) => { sub.subscribe('somechannel', 'another channel').then((res) => {
if (err) throw err
end() end()
sub.stream.destroy() sub.stream.destroy()
}) })
@@ -244,56 +235,59 @@ describe('publish/subscribe', () => {
sub.on('ready', () => { sub.on('ready', () => {
sub.unsubscribe() sub.unsubscribe()
sub.del('foo') sub.del('foo')
sub.info(end) sub.info().then(end)
}) })
}) })
it('should not go into pubsub mode with unsubscribe commands', (done) => { it('should not go into pubsub mode with unsubscribe commands', () => {
sub.on('unsubscribe', (msg) => { sub.on('unsubscribe', (msg) => {
// The unsubscribe should not be triggered, as there was no corresponding channel // The unsubscribe should not be triggered, as there was no corresponding channel
throw new Error('Test failed') throw new Error('Test failed')
}) })
sub.set('foo', 'bar') return Promise.all([
sub.unsubscribe(helper.isDeepEqual([0, []])) sub.set('foo', 'bar'),
sub.del('foo', done) sub.unsubscribe().then(helper.isDeepEqual([0, []])),
sub.del('foo')
])
}) })
it('handles multiple channels with the same channel name properly, even with buffers', (done) => { it('handles multiple channels with the same channel name properly, even with buffers', () => {
const channels = ['a', 'b', 'a', Buffer.from('a'), 'c', 'b'] const channels = ['a', 'b', 'a', Buffer.from('a'), 'c', 'b']
const subscribedChannels = [1, 2, 2, 2, 3, 3] const subscribedChannels = [1, 2, 2, 2, 3, 3]
let i = 0
sub.subscribe(channels) sub.subscribe(channels)
sub.on('subscribe', (channel, count) => { sub.on('subscribe', (channel, count) => {
const compareChannel = channels.shift()
if (Buffer.isBuffer(channel)) { if (Buffer.isBuffer(channel)) {
assert.strictEqual(channel.inspect(), Buffer.from(channels[i]).inspect()) assert.strictEqual(channel.inspect(), Buffer.from(compareChannel).inspect())
} else { } else {
assert.strictEqual(channel, channels[i].toString()) assert.strictEqual(channel, compareChannel.toString())
} }
assert.strictEqual(count, subscribedChannels[i]) assert.strictEqual(count, subscribedChannels.shift())
i++
}) })
sub.unsubscribe('a', 'c', 'b') sub.unsubscribe('a', 'c', 'b')
sub.get('foo', done) return sub.get('foo')
}) })
it('should only resubscribe to channels not unsubscribed earlier on a reconnect', (done) => { it('should only resubscribe to channels not unsubscribed earlier on a reconnect', (done) => {
sub.subscribe('/foo', '/bar') sub.subscribe('/foo', '/bar')
sub.batch().unsubscribe(['/bar'], () => { sub.batch().unsubscribe(['/bar']).exec().then(() => {
pub.pubsub('channels', helper.isDeepEqual(['/foo'], () => { pub.pubsub('channels').then((res) => {
helper.isDeepEqual(['/foo'])(res)
sub.stream.destroy() sub.stream.destroy()
sub.once('ready', () => { sub.once('ready', () => {
pub.pubsub('channels', helper.isDeepEqual(['/foo'], () => { pub.pubsub('channels').then((res) => {
sub.unsubscribe('/foo', done) helper.isDeepEqual(['/foo'])(res)
})) sub.unsubscribe('/foo').then(() => done())
})
}) })
})) })
}).exec() })
}) })
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Withouth callbacks', (done) => { it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed', (done) => {
function subscribe (channels) { function subscribe (channels) {
sub.unsubscribe(helper.isNull) sub.unsubscribe().then(helper.isNull)
sub.subscribe(channels, helper.isNull) sub.subscribe(channels).then(helper.isNull)
} }
let all = false let all = false
const subscribeMsg = ['1', '3', '2', '5', 'test', 'bla'] const subscribeMsg = ['1', '3', '2', '5', 'test', 'bla']
@@ -318,35 +312,7 @@ describe('publish/subscribe', () => {
subscribe(['5', 'test', 'bla']) subscribe(['5', 'test', 'bla'])
}) })
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without callbacks', (done) => { it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without concrete channels', (done) => {
function subscribe (channels) {
sub.unsubscribe()
sub.subscribe(channels)
}
let all = false
const subscribeMsg = ['1', '3', '2', '5', 'test', 'bla']
sub.on('subscribe', (msg, count) => {
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1)
if (subscribeMsg.length === 0 && all) {
assert.strictEqual(count, 3)
done()
}
})
const unsubscribeMsg = ['1', '3', '2']
sub.on('unsubscribe', (msg, count) => {
unsubscribeMsg.splice(unsubscribeMsg.indexOf(msg), 1)
if (unsubscribeMsg.length === 0) {
assert.strictEqual(count, 0)
all = true
}
})
subscribe(['1', '3'])
subscribe(['2'])
subscribe(['5', 'test', 'bla'])
})
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without callback and concret channels', (done) => {
function subscribe (channels) { function subscribe (channels) {
sub.unsubscribe(channels) sub.unsubscribe(channels)
sub.unsubscribe(channels) sub.unsubscribe(channels)
@@ -377,11 +343,8 @@ describe('publish/subscribe', () => {
it('unsubscribes, subscribes, unsubscribes... with pattern matching', (done) => { it('unsubscribes, subscribes, unsubscribes... with pattern matching', (done) => {
function subscribe (channels, callback) { function subscribe (channels, callback) {
sub.punsubscribe('prefix:*', helper.isNull) sub.punsubscribe('prefix:*').then(helper.isNull)
sub.psubscribe(channels, (err, res) => { sub.psubscribe(channels).then(callback)
helper.isNull(err)
if (callback) callback(err, res)
})
} }
let all = false let all = false
const end = helper.callFuncAfter(done, 8) const end = helper.callFuncAfter(done, 8)
@@ -414,19 +377,16 @@ describe('publish/subscribe', () => {
}) })
subscribe(['prefix:*', 'prefix:3'], () => { subscribe(['prefix:*', 'prefix:3'], () => {
pub.publish('prefix:1', Buffer.from('test'), () => { pub.publish('prefix:1', Buffer.from('test')).then(() => {
subscribe(['prefix:2']) subscribe(['prefix:2'])
subscribe(['5', 'test:a', 'bla'], () => { subscribe(['5', 'test:a', 'bla'], () => assert(all))
assert(all) sub.punsubscribe().then((res) => {
})
sub.punsubscribe((err, res) => {
assert(!err)
assert.deepStrictEqual(res, [0, ['prefix:3', 'prefix:2', '5', 'test:a', 'bla']]) assert.deepStrictEqual(res, [0, ['prefix:3', 'prefix:2', '5', 'test:a', 'bla']])
assert(all) assert(all)
all = false // Make sure the callback is actually after the emit all = false // Make sure the callback is actually after the emit
end() end()
}) })
sub.pubsub('channels', helper.isDeepEqual([], end)) sub.pubsub('channels').then(helper.isDeepEqual([])).then(end)
}) })
}) })
}) })
@@ -443,7 +403,7 @@ describe('publish/subscribe', () => {
sub.on('unsubscribe', (chnl, count) => { sub.on('unsubscribe', (chnl, count) => {
assert.strictEqual(chnl, channel) assert.strictEqual(chnl, channel)
assert.strictEqual(count, 0) assert.strictEqual(count, 0)
return done() done()
}) })
}) })
@@ -455,17 +415,19 @@ describe('publish/subscribe', () => {
sub.subscribe(channel) sub.subscribe(channel)
sub.on('unsubscribe', (chnl, count) => { sub.on('unsubscribe', (chnl, count) => {
pub.incr('foo', helper.isNumber(1, done)) pub.incr('foo').then(helper.isNumber(1)).then(done)
}) })
}) })
it('sub executes callback when unsubscribe is called and there are no subscriptions', (done) => { it('sub executes when unsubscribe is called and there are no subscriptions', () => {
sub.unsubscribe(helper.isDeepEqual([0, []], done)) return sub.unsubscribe().then(helper.isDeepEqual([0, []]))
}) })
it('pub executes callback when unsubscribe is called and there are no subscriptions', (done) => { it('pub executes when unsubscribe is called and there are no subscriptions', () => {
pub.unsubscribe(helper.isDeepEqual([0, []])) return Promise.all([
pub.get('foo', done) pub.unsubscribe().then(helper.isDeepEqual([0, []])),
pub.get('foo')
])
}) })
}) })
@@ -474,28 +436,28 @@ describe('publish/subscribe', () => {
const sub2 = redis.createClient({ const sub2 = redis.createClient({
returnBuffers: true returnBuffers: true
}) })
sub.subscribe('/foo', () => { sub.subscribe('/foo').then(() => {
sub2.on('ready', () => { sub2.on('ready', () => {
sub2.batch().psubscribe('*', helper.isDeepEqual([1, ['*']])).exec() sub2.batch().psubscribe('*').exec().then(helper.isDeepEqual([[1, ['*']]]))
sub2.subscribe('/foo', () => { sub2.subscribe('/foo').then(() => {
pub.pubsub('numsub', '/foo', helper.isDeepEqual(['/foo', 2])) pub.pubsub('numsub', '/foo').then(helper.isDeepEqual(['/foo', 2]))
// sub2 is counted twice as it subscribed with psubscribe and subscribe // sub2 is counted twice as it subscribed with psubscribe and subscribe
pub.publish('/foo', 'hello world', helper.isNumber(3)) pub.publish('/foo', 'hello world').then(helper.isNumber(3))
}) })
sub2.on('pmessage', (pattern, channel, message) => { sub2.on('pmessage', (pattern, channel, message) => {
assert.strictEqual(pattern.inspect(), Buffer.from('*').inspect()) assert.strictEqual(pattern.inspect(), Buffer.from('*').inspect())
assert.strictEqual(channel.inspect(), Buffer.from('/foo').inspect()) assert.strictEqual(channel.inspect(), Buffer.from('/foo').inspect())
assert.strictEqual(message.inspect(), Buffer.from('hello world').inspect()) assert.strictEqual(message.inspect(), Buffer.from('hello world').inspect())
sub2.quit(done) sub2.quit().then(() => done())
}) })
}) })
}) })
}) })
it('allows to listen to pmessageBuffer and pmessage', (done) => { it('allows to listen to pmessageBuffer and pmessage', (done) => {
const end = helper.callFuncAfter(done, 6) const end = helper.callFuncAfter(done, 3)
const data = Array(10000).join('äüs^öéÉÉ`e') const data = Array(10000).join('äüs^öéÉÉ`e')
sub.set('foo', data, () => { sub.set('foo', data).then(() => {
sub.get('foo') sub.get('foo')
sub.stream.once('data', () => { sub.stream.once('data', () => {
assert.strictEqual(sub.messageBuffers, false) assert.strictEqual(sub.messageBuffers, false)
@@ -503,7 +465,7 @@ describe('publish/subscribe', () => {
sub.on('pmessageBuffer', (pattern, channel, message) => { sub.on('pmessageBuffer', (pattern, channel, message) => {
assert.strictEqual(pattern.inspect(), Buffer.from('*').inspect()) assert.strictEqual(pattern.inspect(), Buffer.from('*').inspect())
assert.strictEqual(channel.inspect(), Buffer.from('/foo').inspect()) assert.strictEqual(channel.inspect(), Buffer.from('/foo').inspect())
sub.quit(end) sub.quit().then(end)
}) })
assert.notStrictEqual(sub.messageBuffers, sub.buffers) assert.notStrictEqual(sub.messageBuffers, sub.buffers)
}) })
@@ -511,16 +473,16 @@ describe('publish/subscribe', () => {
batch.psubscribe('*') batch.psubscribe('*')
batch.subscribe('/foo') batch.subscribe('/foo')
batch.unsubscribe('/foo') batch.unsubscribe('/foo')
batch.unsubscribe(helper.isDeepEqual([1, []])) batch.unsubscribe()
batch.subscribe(['/foo'], helper.isDeepEqual([2, ['/foo']])) batch.subscribe(['/foo'])
batch.exec(() => { batch.exec().then(() => {
// There's one subscriber to this channel // There's one subscriber to this channel
pub.pubsub('numsub', '/foo', helper.isDeepEqual(['/foo', 1], end)) pub.pubsub('numsub', '/foo').then(helper.isDeepEqual(['/foo', 1]))
// There's exactly one channel that is listened too // There's exactly one channel that is listened too
pub.pubsub('channels', helper.isDeepEqual(['/foo'], end)) pub.pubsub('channels').then(helper.isDeepEqual(['/foo']))
// One pattern is active // One pattern is active
pub.pubsub('numpat', helper.isNumber(1, end)) pub.pubsub('numpat').then(helper.isNumber(1))
pub.publish('/foo', 'hello world', helper.isNumber(2)) pub.publish('/foo', 'hello world').then(helper.isNumber(2))
}) })
// Either messageBuffers or buffers has to be true, but not both at the same time // Either messageBuffers or buffers has to be true, but not both at the same time
sub.on('pmessage', (pattern, channel, message) => { sub.on('pmessage', (pattern, channel, message) => {
@@ -540,39 +502,28 @@ describe('publish/subscribe', () => {
describe('punsubscribe', () => { describe('punsubscribe', () => {
it('does not complain when punsubscribe is called and there are no subscriptions', () => { it('does not complain when punsubscribe is called and there are no subscriptions', () => {
sub.punsubscribe() return sub.punsubscribe()
}) })
it('executes callback when punsubscribe is called and there are no subscriptions', (done) => { it('executes when punsubscribe is called and there are no subscriptions', () => {
pub.batch().punsubscribe(helper.isDeepEqual([0, []])).exec(done) return pub.batch().punsubscribe(helper.isDeepEqual([0, []])).exec()
}) })
}) })
describe('fail for other commands while in pub sub mode', () => { describe('fail for other commands while in pub sub mode', () => {
it('return error if only pub sub commands are allowed', (done) => { it('return error if only pub sub commands are allowed', () => {
sub.subscribe('channel') return Promise.all([
// Ping is allowed even if not listed as such! sub.subscribe('channel'),
sub.ping((err, res) => { // Ping is allowed even if not listed as such!
assert.strictEqual(err, null) sub.ping().then(helper.isDeepEqual(['pong', ''])),
assert.strictEqual(res[0], 'pong') // Get is forbidden
}) sub.get('foo').then(helper.fail).catch((err) => {
// Get is forbidden assert(/^ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message))
sub.get('foo', (err, res) => { assert.strictEqual(err.command, 'GET')
assert(/^ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message)) }),
assert.strictEqual(err.command, 'GET') // Quit is allowed
}) sub.quit()
// Quit is allowed ])
sub.quit(done)
})
it('emit error if only pub sub commands are allowed without callback', (done) => {
sub.subscribe('channel')
sub.on('error', (err) => {
assert(/^ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message))
assert.strictEqual(err.command, 'GET')
done()
})
sub.get('foo')
}) })
}) })
@@ -613,31 +564,28 @@ describe('publish/subscribe', () => {
pub.set('foo', 'message') pub.set('foo', 'message')
pub.set('bar', 'hello') pub.set('bar', 'hello')
pub.mget('foo', 'bar') pub.mget('foo', 'bar')
pub.subscribe('channel', () => { pub.subscribe('channel').then(() => setTimeout(done, 50))
setTimeout(done, 50)
})
pub.on('message', (msg) => { pub.on('message', (msg) => {
done(new Error(`This message should not have been published: ${msg}`)) done(new Error(`This message should not have been published: ${msg}`))
}) })
}) })
it('arguments variants', (done) => { it('arguments variants', () => {
sub.batch() return sub.batch()
.info(['stats']) .info(['stats'])
.info() .info()
.client('KILL', ['type', 'pubsub']) .client('KILL', ['type', 'pubsub'])
.client('KILL', ['type', 'pubsub'], () => {}) .client('KILL', ['type', 'pubsub'])
.unsubscribe() .unsubscribe()
.psubscribe(['pattern:*']) .psubscribe(['pattern:*'])
.punsubscribe('unknown*') .punsubscribe('unknown*')
.punsubscribe(['pattern:*']) .punsubscribe(['pattern:*'])
.exec((err, res) => { .exec().then(() => Promise.all([
if (err) throw err sub.client('kill', ['type', 'pubsub']),
sub.client('kill', ['type', 'pubsub']) sub.psubscribe('*'),
sub.psubscribe('*') sub.punsubscribe('pa*'),
sub.punsubscribe('pa*') sub.punsubscribe(['a', '*'])
sub.punsubscribe(['a', '*'], done) ]))
})
}) })
afterEach(() => { afterEach(() => {

View File

@@ -18,8 +18,8 @@ if (process.platform !== 'win32') {
describe(`using ${ip}`, () => { describe(`using ${ip}`, () => {
let client = null let client = null
beforeEach((done) => { beforeEach(() => {
if (helper.redisProcess().spawnFailed()) return done() if (helper.redisProcess().spawnFailed()) return
client = redis.createClient({ client = redis.createClient({
renameCommands: { renameCommands: {
set: '807081f5afa96845a02816a28b7258c3', set: '807081f5afa96845a02816a28b7258c3',
@@ -27,9 +27,7 @@ if (process.platform !== 'win32') {
} }
}) })
client.on('ready', () => { return client.flushdb()
client.flushdb(done)
})
}) })
afterEach(() => { afterEach(() => {
@@ -37,75 +35,56 @@ if (process.platform !== 'win32') {
client.end(true) client.end(true)
}) })
it('allows to use renamed functions', function (done) { it('allows to use renamed functions', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client.set('key', 'value', helper.isString('OK')) client.set('key', 'value').then(helper.isString('OK'))
client.get('key', (err, reply) => { client.get('key').then(helper.fail).catch((err) => {
assert.strictEqual(err.message, 'ERR unknown command \'get\'') assert.strictEqual(err.message, 'ERR unknown command \'get\'')
assert.strictEqual(err.command, 'GET') assert.strictEqual(err.command, 'GET')
assert.strictEqual(reply, undefined)
}) })
client.getrange('key', 1, -1, (err, reply) => { return client.getrange('key', 1, -1).then(helper.isString('alue'))
assert.strictEqual(reply, 'alue')
assert.strictEqual(err, null)
done()
})
}) })
it('should also work with batch', function (done) { it('should also work with batch', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client.batch([['set', 'key', 'value']]).exec(helper.isDeepEqual(['OK'])) client.batch([['set', 'key', 'value']]).exec(helper.isDeepEqual(['OK']))
const batch = client.batch() const batch = client.batch()
batch.getrange('key', 1, -1) batch.getrange('key', 1, -1)
batch.exec((err, res) => { return batch.exec().then(helper.isDeepEqual(['alue']))
assert(!err)
assert.strictEqual(res.length, 1)
assert.strictEqual(res[0], 'alue')
done()
})
}) })
it('should also work with multi', function (done) { it('should also work with multi', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client.multi([['set', 'key', 'value']]).exec(helper.isDeepEqual(['OK'])) client.multi([['set', 'key', 'value']]).exec(helper.isDeepEqual(['OK']))
const multi = client.multi() const multi = client.multi()
multi.getrange('key', 1, -1) multi.getrange('key', 1, -1)
multi.exec((err, res) => { return multi.exec().then(helper.isDeepEqual(['alue']))
assert(!err)
assert.strictEqual(res.length, 1)
assert.strictEqual(res[0], 'alue')
done()
})
}) })
it('should also work with multi and abort transaction', function (done) { it('should also work with multi and abort transaction', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
const multi = client.multi() const multi = client.multi()
multi.get('key') multi.get('key')
multi.getrange('key', 1, -1, (err, reply) => { multi.getrange('key', 1, -1)
assert.strictEqual(reply, 'alue') return multi.exec().then(helper.fail).catch((err) => {
assert.strictEqual(err, null)
})
multi.exec((err, res) => {
assert(err) assert(err)
assert.strictEqual(err.message, 'EXECABORT Transaction discarded because of previous errors.') assert.strictEqual(err.message, 'EXECABORT Transaction discarded because of previous errors.')
assert.strictEqual(err.errors[0].message, 'ERR unknown command \'get\'') assert.strictEqual(err.errors[0].message, 'ERR unknown command \'get\'')
assert.strictEqual(err.errors[0].command, 'GET') assert.strictEqual(err.errors[0].command, 'GET')
assert.strictEqual(err.code, 'EXECABORT') assert.strictEqual(err.code, 'EXECABORT')
assert.strictEqual(err.errors[0].code, 'ERR') assert.strictEqual(err.errors[0].code, 'ERR')
done()
}) })
}) })
it('should also work prefixed commands', function (done) { it('should also work prefixed commands', function () {
if (helper.redisProcess().spawnFailed()) this.skip() if (helper.redisProcess().spawnFailed()) this.skip()
client.end(true) client.end(true)
@@ -116,11 +95,7 @@ if (process.platform !== 'win32') {
prefix: 'baz' prefix: 'baz'
}) })
client.set('foo', 'bar') client.set('foo', 'bar')
client.keys('*', (err, reply) => { return client.keys('*').then(helper.isDeepEqual(['bazfoo']))
assert.strictEqual(reply[0], 'bazfoo')
assert.strictEqual(err, null)
done()
})
}) })
}) })
}) })

View File

@@ -29,46 +29,42 @@ describe('returnBuffers', () => {
assert.strictEqual(msg, 'WARNING: You activated returnBuffers and detectBuffers at the same time. The return value is always going to be a buffer.') assert.strictEqual(msg, 'WARNING: You activated returnBuffers and detectBuffers at the same time. The return value is always going to be a buffer.')
end() end()
}) })
client.once('error', done) client.flushdb()
client.once('connect', () => { client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2')
client.flushdb((err) => { client.set('string key 1', 'string value').then(end)
client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2') })
client.set('string key 1', 'string value')
end(err) afterEach(() => {
}) client.end(true)
})
}) })
describe('get', () => { describe('get', () => {
describe('first argument is a string', () => { describe('first argument is a string', () => {
it('returns a buffer', (done) => { it('returns a buffer', () => {
client.get('string key 1', (err, reply) => { return client.get('string key 1').then((reply) => {
assert.strictEqual(true, Buffer.isBuffer(reply)) assert.strictEqual(true, Buffer.isBuffer(reply))
assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply.inspect()) assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply.inspect())
return done(err)
}) })
}) })
it('returns a bufffer when executed as part of transaction', (done) => { it('returns a buffer when executed as part of transaction', () => {
client.multi().get('string key 1').exec((err, reply) => { return client.multi().get('string key 1').exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(true, Buffer.isBuffer(reply[0])) assert.strictEqual(true, Buffer.isBuffer(reply[0]))
assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply[0].inspect()) assert.strictEqual('<Buffer 73 74 72 69 6e 67 20 76 61 6c 75 65>', reply[0].inspect())
return done(err)
}) })
}) })
}) })
}) })
describe('multi.hget', () => { describe('multi.hget', () => {
it('returns buffers', (done) => { it('returns buffers', () => {
client.multi() return client.multi()
.hget('hash key 2', 'key 1') .hget('hash key 2', 'key 1')
.hget(Buffer.from('hash key 2'), 'key 1') .hget(Buffer.from('hash key 2'), 'key 1')
.hget('hash key 2', Buffer.from('key 2')) .hget('hash key 2', Buffer.from('key 2'))
.hget('hash key 2', 'key 2') .hget('hash key 2', 'key 2')
.exec((err, reply) => { .exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(4, reply.length) assert.strictEqual(4, reply.length)
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect())
assert.strictEqual(true, Buffer.isBuffer(reply[1])) assert.strictEqual(true, Buffer.isBuffer(reply[1]))
@@ -77,20 +73,18 @@ describe('returnBuffers', () => {
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect())
assert.strictEqual(true, Buffer.isBuffer(reply[3])) assert.strictEqual(true, Buffer.isBuffer(reply[3]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[3].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[3].inspect())
return done(err)
}) })
}) })
}) })
describe('batch.hget', () => { describe('batch.hget', () => {
it('returns buffers', (done) => { it('returns buffers', () => {
client.batch() return client.batch()
.hget('hash key 2', 'key 1') .hget('hash key 2', 'key 1')
.hget(Buffer.from('hash key 2'), 'key 1') .hget(Buffer.from('hash key 2'), 'key 1')
.hget('hash key 2', Buffer.from('key 2')) .hget('hash key 2', Buffer.from('key 2'))
.hget('hash key 2', 'key 2') .hget('hash key 2', 'key 2')
.exec((err, reply) => { .exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(4, reply.length) assert.strictEqual(4, reply.length)
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect())
assert.strictEqual(true, Buffer.isBuffer(reply[1])) assert.strictEqual(true, Buffer.isBuffer(reply[1]))
@@ -99,117 +93,103 @@ describe('returnBuffers', () => {
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect())
assert.strictEqual(true, Buffer.isBuffer(reply[3])) assert.strictEqual(true, Buffer.isBuffer(reply[3]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[3].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[3].inspect())
return done(err)
}) })
}) })
}) })
describe('hmget', () => { describe('hmget', () => {
describe('first argument is a string', () => { describe('first argument is a string', () => {
it('handles array of strings with undefined values in transaction (repro #344)', (done) => { it('handles array of strings with undefined values in transaction (repro #344)', () => {
client.multi().hmget('hash key 2', 'key 3', 'key 4').exec((err, reply) => { return client.multi().hmget('hash key 2', 'key 3', 'key 4').exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length) assert.strictEqual(2, reply[0].length)
assert.strictEqual(null, reply[0][0]) assert.strictEqual(null, reply[0][0])
assert.strictEqual(null, reply[0][1]) assert.strictEqual(null, reply[0][1])
return done(err)
}) })
}) })
}) })
describe('first argument is a buffer', () => { describe('first argument is a buffer', () => {
it('returns buffers for keys requested', (done) => { it('returns buffers for keys requested', () => {
client.hmget(Buffer.from('hash key 2'), 'key 1', 'key 2', (err, reply) => { return client.hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(2, reply.length) assert.strictEqual(2, reply.length)
assert.strictEqual(true, Buffer.isBuffer(reply[0])) assert.strictEqual(true, Buffer.isBuffer(reply[0]))
assert.strictEqual(true, Buffer.isBuffer(reply[1])) assert.strictEqual(true, Buffer.isBuffer(reply[1]))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[1].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[1].inspect())
return done(err)
}) })
}) })
it('returns buffers for keys requested in transaction', (done) => { it('returns buffers for keys requested in transaction', () => {
client.multi().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => { return client.multi().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length) assert.strictEqual(2, reply[0].length)
assert.strictEqual(true, Buffer.isBuffer(reply[0][0])) assert.strictEqual(true, Buffer.isBuffer(reply[0][0]))
assert.strictEqual(true, Buffer.isBuffer(reply[0][1])) assert.strictEqual(true, Buffer.isBuffer(reply[0][1]))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect())
return done(err)
}) })
}) })
it('returns buffers for keys requested in .batch', (done) => { it('returns buffers for keys requested in .batch', () => {
client.batch().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => { return client.batch().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length) assert.strictEqual(2, reply[0].length)
assert.strictEqual(true, Buffer.isBuffer(reply[0][0])) assert.strictEqual(true, Buffer.isBuffer(reply[0][0]))
assert.strictEqual(true, Buffer.isBuffer(reply[0][1])) assert.strictEqual(true, Buffer.isBuffer(reply[0][1]))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0][0].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0][1].inspect())
return done(err)
}) })
}) })
}) })
}) })
describe('hgetall', (done) => { describe('hgetall', () => {
describe('first argument is a string', () => { describe('first argument is a string', () => {
it('returns buffer values', (done) => { it('returns buffer values', () => {
client.hgetall('hash key 2', (err, reply) => { return client.hgetall('hash key 2').then((reply) => {
assert.strictEqual('object', typeof reply) assert.strictEqual('object', typeof reply)
assert.strictEqual(2, Object.keys(reply).length) assert.strictEqual(2, Object.keys(reply).length)
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply['key 2'].inspect())
return done(err)
}) })
}) })
it('returns buffer values when executed in transaction', (done) => { it('returns buffer values when executed in transaction', () => {
client.multi().hgetall('hash key 2').exec((err, reply) => { return client.multi().hgetall('hash key 2').exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0]) assert.strictEqual('object', typeof reply[0])
assert.strictEqual(2, Object.keys(reply[0]).length) assert.strictEqual(2, Object.keys(reply[0]).length)
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect())
return done(err)
}) })
}) })
it('returns buffer values when executed in .batch', (done) => { it('returns buffer values when executed in .batch', () => {
client.batch().hgetall('hash key 2').exec((err, reply) => { return client.batch().hgetall('hash key 2').exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0]) assert.strictEqual('object', typeof reply[0])
assert.strictEqual(2, Object.keys(reply[0]).length) assert.strictEqual(2, Object.keys(reply[0]).length)
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect())
return done(err)
}) })
}) })
}) })
describe('first argument is a buffer', () => { describe('first argument is a buffer', () => {
it('returns buffer values', (done) => { it('returns buffer values', () => {
client.hgetall(Buffer.from('hash key 2'), (err, reply) => { return client.hgetall(Buffer.from('hash key 2')).then((reply) => {
assert.strictEqual(null, err)
assert.strictEqual('object', typeof reply) assert.strictEqual('object', typeof reply)
assert.strictEqual(2, Object.keys(reply).length) assert.strictEqual(2, Object.keys(reply).length)
assert.strictEqual(true, Buffer.isBuffer(reply['key 1'])) assert.strictEqual(true, Buffer.isBuffer(reply['key 1']))
assert.strictEqual(true, Buffer.isBuffer(reply['key 2'])) assert.strictEqual(true, Buffer.isBuffer(reply['key 2']))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply['key 2'].inspect())
return done(err)
}) })
}) })
it('returns buffer values when executed in transaction', (done) => { it('returns buffer values when executed in transaction', () => {
client.multi().hgetall(Buffer.from('hash key 2')).exec((err, reply) => { return client.multi().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0]) assert.strictEqual('object', typeof reply[0])
assert.strictEqual(2, Object.keys(reply[0]).length) assert.strictEqual(2, Object.keys(reply[0]).length)
@@ -217,12 +197,11 @@ describe('returnBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2'])) assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect())
return done(err)
}) })
}) })
it('returns buffer values when executed in .batch', (done) => { it('returns buffer values when executed in .batch', () => {
client.batch().hgetall(Buffer.from('hash key 2')).exec((err, reply) => { return client.batch().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length) assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0]) assert.strictEqual('object', typeof reply[0])
assert.strictEqual(2, Object.keys(reply[0]).length) assert.strictEqual(2, Object.keys(reply[0]).length)
@@ -230,15 +209,15 @@ describe('returnBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2'])) assert.strictEqual(true, Buffer.isBuffer(reply[0]['key 2']))
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].inspect())
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect()) assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[0]['key 2'].inspect())
return done(err)
}) })
}) })
}) })
}) })
describe('publish/subscribe', (done) => { describe('publish/subscribe', () => {
let pub let pub
let sub let sub
const channel = 'test channel' const channel = 'test channel'
const message = Buffer.from('test message') const message = Buffer.from('test message')
@@ -247,25 +226,12 @@ describe('returnBuffers', () => {
}) })
beforeEach((done) => { beforeEach((done) => {
let pubConnected const end = helper.callFuncAfter(done, 2)
let subConnected
pub = redis.createClient.apply(redis.createClient, basicArgs) pub = redis.createClient.apply(redis.createClient, basicArgs)
sub = redis.createClient.apply(null, args) sub = redis.createClient.apply(null, args)
pub.once('connect', () => { pub.flushdb().then(end)
pub.flushdb(() => { sub.once('connect', end)
pubConnected = true
if (subConnected) {
done()
}
})
})
sub.once('connect', () => {
subConnected = true
if (pubConnected) {
done()
}
})
}) })
it('receives buffer messages', (done) => { it('receives buffer messages', (done) => {
@@ -276,7 +242,7 @@ describe('returnBuffers', () => {
sub.on('message', (chnl, msg) => { sub.on('message', (chnl, msg) => {
assert.strictEqual(true, Buffer.isBuffer(msg)) assert.strictEqual(true, Buffer.isBuffer(msg))
assert.strictEqual('<Buffer 74 65 73 74 20 6d 65 73 73 61 67 65>', msg.inspect()) assert.strictEqual('<Buffer 74 65 73 74 20 6d 65 73 73 61 67 65>', msg.inspect())
return done() done()
}) })
sub.subscribe(channel) sub.subscribe(channel)

View File

@@ -86,7 +86,7 @@ describe.skip('TLS connection tests', () => {
}) })
describe('when not connected', () => { describe('when not connected', () => {
it('connect with host and port provided in the tls object', function (done) { it('connect with host and port provided in the tls object', function () {
if (skip) this.skip() if (skip) this.skip()
const tls = utils.clone(tlsOptions) const tls = utils.clone(tlsOptions)
tls.port = tlsPort tls.port = tlsPort
@@ -103,10 +103,10 @@ describe.skip('TLS connection tests', () => {
assert(client.stream.encrypted) assert(client.stream.encrypted)
client.set('foo', 'bar') client.set('foo', 'bar')
client.get('foo', helper.isString('bar', done)) return client.get('foo').then(helper.isString('bar'))
}) })
it('fails to connect because the cert is not correct', function (done) { it('fails to connect because the cert is not correct', function () {
if (skip) this.skip() if (skip) this.skip()
const faultyCert = utils.clone(tlsOptions) const faultyCert = utils.clone(tlsOptions)
faultyCert.ca = [ String(fs.readFileSync(path.resolve(__dirname, './conf/faulty.cert'))) ] faultyCert.ca = [ String(fs.readFileSync(path.resolve(__dirname, './conf/faulty.cert'))) ]
@@ -121,7 +121,7 @@ describe.skip('TLS connection tests', () => {
assert(/DEPTH_ZERO_SELF_SIGNED_CERT/.test(err.code || err.message), err) assert(/DEPTH_ZERO_SELF_SIGNED_CERT/.test(err.code || err.message), err)
client.end(true) client.end(true)
}) })
client.set('foo', 'bar', helper.isError(done)) return client.set('foo', 'bar').then(helper.isError())
}) })
}) })
}) })

View File

@@ -15,7 +15,7 @@ describe('utils.js', () => {
fn: function noop () {} fn: function noop () {}
} }
const clone = utils.clone(obj) const clone = utils.clone(obj)
assert.deepEqual(clone, obj) assert.deepStrictEqual(clone, obj)
assert.strictEqual(obj.fn, clone.fn) assert.strictEqual(obj.fn, clone.fn)
assert(typeof clone.fn === 'function') assert(typeof clone.fn === 'function')
}) })
@@ -42,9 +42,7 @@ describe('utils.js', () => {
describe('replyInOrder', () => { describe('replyInOrder', () => {
let errCount = 0 let errCount = 0
let resCount = 0 let resCount = 0
let emitted = false
const clientMock = { const clientMock = {
emit () { emitted = true },
offlineQueue: new Queue(), offlineQueue: new Queue(),
commandQueue: new Queue() commandQueue: new Queue()
} }
@@ -62,7 +60,6 @@ describe('utils.js', () => {
clientMock.commandQueue.clear() clientMock.commandQueue.clear()
errCount = 0 errCount = 0
resCount = 0 resCount = 0
emitted = false
}) })
it('no elements in either queue. Reply in the next tick with callback', (done) => { it('no elements in either queue. Reply in the next tick with callback', (done) => {
@@ -74,16 +71,6 @@ describe('utils.js', () => {
assert(!called) assert(!called)
}) })
it('no elements in either queue. Reply in the next tick without callback', (done) => {
assert(!emitted)
utils.replyInOrder(clientMock, null, new Error('tada'))
assert(!emitted)
setTimeout(() => {
assert(emitted)
done()
}, 1)
})
it('elements in the offline queue. Reply after the offline queue is empty and respect the commandObj callback', (done) => { it('elements in the offline queue. Reply after the offline queue is empty and respect the commandObj callback', (done) => {
clientMock.offlineQueue.push(createCommandObj(), createCommandObj()) clientMock.offlineQueue.push(createCommandObj(), createCommandObj())
utils.replyInOrder(clientMock, () => { utils.replyInOrder(clientMock, () => {
@@ -95,11 +82,10 @@ describe('utils.js', () => {
}) })
it('elements in the offline queue. Reply after the offline queue is empty and respect the commandObj error emit', (done) => { it('elements in the offline queue. Reply after the offline queue is empty and respect the commandObj error emit', (done) => {
clientMock.commandQueue.push({}, createCommandObj(), {}) clientMock.commandQueue.push(createCommandObj(), createCommandObj(), createCommandObj())
utils.replyInOrder(clientMock, () => { utils.replyInOrder(clientMock, () => {
assert.strictEqual(clientMock.commandQueue.length, 0) assert.strictEqual(clientMock.commandQueue.length, 0)
assert(emitted) assert.strictEqual(errCount, 3)
assert.strictEqual(errCount, 1)
assert.strictEqual(resCount, 0) assert.strictEqual(resCount, 0)
done() done()
}, null, null) }, null, null)
@@ -113,13 +99,12 @@ describe('utils.js', () => {
it('elements in the offline queue and the commandQueue. Reply all other commands got handled respect the commandObj', (done) => { it('elements in the offline queue and the commandQueue. Reply all other commands got handled respect the commandObj', (done) => {
clientMock.commandQueue.push(createCommandObj(), createCommandObj()) clientMock.commandQueue.push(createCommandObj(), createCommandObj())
clientMock.offlineQueue.push(createCommandObj(), {}) clientMock.offlineQueue.push(createCommandObj(), createCommandObj())
utils.replyInOrder(clientMock, (err, res) => { utils.replyInOrder(clientMock, (err, res) => {
if (err) throw err if (err) throw err
assert.strictEqual(clientMock.commandQueue.length, 0) assert.strictEqual(clientMock.commandQueue.length, 0)
assert.strictEqual(clientMock.offlineQueue.length, 0) assert.strictEqual(clientMock.offlineQueue.length, 0)
assert(!emitted) assert.strictEqual(resCount, 4)
assert.strictEqual(resCount, 3)
done() done()
}, null, null) }, null, null)
while (clientMock.offlineQueue.length) { while (clientMock.offlineQueue.length) {