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 () {
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.fillPipeline()
return this.fillPipeline()
}
Test.prototype.fillPipeline = function () {
@@ -131,19 +131,19 @@ Test.prototype.fillPipeline = function () {
}
this.ended = true
this.printStats()
this.stopClients()
return
return this.stopClients()
}
if (this.batchPipeline) {
this.batch()
} else {
return this.batch()
}
const promises = []
while (pipeline < this.maxPipeline) {
this.commandsSent++
pipeline++
this.sendNext()
}
promises.push(this.sendNext())
}
return Promise.all(promises)
}
Test.prototype.batch = function () {
@@ -158,29 +158,24 @@ Test.prototype.batch = function () {
batch[this.args.command](this.args.args)
}
batch.exec((err, res) => {
if (err) {
throw err
}
batch.exec().then((res) => {
self.commandsCompleted += res.length
self.commandLatency.update(process.hrtime(start)[1])
self.fillPipeline()
return self.fillPipeline()
})
}
Test.prototype.stopClients = function () {
const self = this
this.clients.forEach((client, pos) => {
return Promise.all(this.clients.map((client, pos) => {
if (pos === self.clients.length - 1) {
client.quit((err, res) => {
if (err) throw err
return client.quit().then((res) => {
self.callback()
})
} else {
client.quit()
}
})
return client.quit()
}))
}
Test.prototype.sendNext = function () {
@@ -188,13 +183,10 @@ Test.prototype.sendNext = function () {
const curClient = this.commandsSent % this.clients.length
const start = process.hrtime()
this.clients[curClient][this.args.command](this.args.args, (err, res) => {
if (err) {
throw err
}
this.clients[curClient][this.args.command](this.args.args).then((res) => {
self.commandsCompleted++
self.commandLatency.update(process.hrtime(start)[1])
self.fillPipeline()
return self.fillPipeline()
})
}

View File

@@ -2,10 +2,24 @@
## 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
- Dropped support for `UPPER_CASE` commands
- Dropped support for `snake_case`
- Dropped support for domains
- Dropped support for Redis 2.4
- Dropped support for Node.js < 4
- Removed `drain` event
- Removed `idle` event
@@ -13,6 +27,7 @@ Breaking Changes
- Removed `retryMaxDelay` (max_delay) option
- Removed `maxAttempts` (max_attempts) option
- Removed `socketNoDelay` (socket_no_delay) option
- Removed `authPass` (auth_pass) option. Please use `password` instead
- Removed `Redis.print` helper function
- Removed backpressure indicator from function return value
- Changed return value of `(p)(un)subscribe`
@@ -20,6 +35,9 @@ Breaking Changes
- Changed `connectTimeout` (connect_timeout) option
- 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
- 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
## v.2.7.2 - 14 Mar, 2017

163
index.js
View File

@@ -1,5 +1,6 @@
'use strict'
// TODO: Replace all for in loops!
const Buffer = require('safe-buffer').Buffer
const net = require('net')
const tls = require('tls')
@@ -113,6 +114,14 @@ function RedisClient (options, stream) {
this.buffers = options.returnBuffers || options.detectBuffers
this.options = options
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 = []
// Init parser
this.replyParser = createParser(this)
@@ -150,8 +159,8 @@ function createParser (self) {
error: err,
queues: ['commandQueue']
})
self.emit('error', err)
self.createStream()
setImmediate(() => self.emit('error', err))
},
returnBuffers: self.buffers || self.messageBuffers,
stringNumbers: self.options.stringNumbers || false
@@ -195,17 +204,17 @@ RedisClient.prototype.createStream = function () {
}
if (this.options.connectTimeout) {
// TODO: Investigate why this is not properly triggered
this.stream.setTimeout(this.connectTimeout, () => {
// Note: This is only tested if a internet connection is established
self.retryTotaltime = self.connectTimeout
self.connectionGone('timeout')
})
}
/* 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'
this.stream.once(connectEvent, function () {
this.removeAllListeners('timeout')
this.stream.once(connectEvent, () => {
this.stream.removeAllListeners('timeout')
self.timesConnected++
self.onConnect()
})
@@ -234,10 +243,18 @@ RedisClient.prototype.createStream = function () {
self.connectionGone('end')
})
this.stream.setNoDelay()
// Fire the command before redis is connected to be sure it's the first fired command
if (this.authPass !== undefined) {
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
}
}
@@ -255,8 +272,7 @@ RedisClient.prototype.uncork = noop
RedisClient.prototype.initializeRetryVars = function () {
this.retryTimer = null
this.retryTotaltime = 0
this.retryDelay = 200
this.retryBackoff = 1.7
this.retryDelay = 100
this.attempts = 1
}
@@ -276,7 +292,6 @@ RedisClient.prototype.warn = function (msg) {
// Flush provided queues, erroring any items with a callback first
RedisClient.prototype.flushAndError = function (errorAttributes, options) {
options = options || {}
const aggregatedErrors = []
const queueNames = options.queues || ['commandQueue', 'offlineQueue'] // Flush the commandQueue first to keep the order intact
for (let i = 0; i < queueNames.length; i++) {
// 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) {
err.origin = options.error
}
if (typeof commandObj.callback === 'function') {
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) {
@@ -330,7 +329,7 @@ RedisClient.prototype.onError = function (err) {
this.ready = false
// Only emit the error if the retryStrategy option is not set
if (!this.options.retryStrategy) {
if (this.retryStrategyProvided === false) {
this.emit('error', err)
}
// 'error' events get turned into exceptions if they aren't listened for. If the user handled this error
@@ -381,26 +380,52 @@ RedisClient.prototype.onReady = function () {
// Restore modal commands from previous connection. The order of the commands is important
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
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
// 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) {
debug('Sending pub/sub onReady commands')
for (const key in this.subscriptionSet) {
const command = key.slice(0, key.indexOf('_'))
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.emit('ready')
}
RedisClient.prototype.onInfoCmd = function (err, res) {
if (err) {
RedisClient.prototype.onInfoFail = function (err) {
if (this.closing) {
return
}
if (err.message === "ERR unknown command 'info'") {
this.onReady()
return
@@ -408,8 +433,9 @@ RedisClient.prototype.onInfoCmd = function (err, res) {
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 */
if (!res) {
debug('The info command returned without any data.')
@@ -434,19 +460,18 @@ RedisClient.prototype.onInfoCmd = function (err, res) {
retryTime = 1000
}
debug(`Redis server still loading, trying again in ${retryTime}`)
setTimeout((self) => {
self.readyCheck()
}, retryTime, this)
return new Promise((resolve) => {
setTimeout((self) => resolve(self.readyCheck()), retryTime, this)
})
}
RedisClient.prototype.readyCheck = function () {
const self = this
debug('Checking server ready state...')
// Always fire this info command as first command even if other commands are already queued up
this.ready = true
this.info((err, res) => {
self.onInfoCmd(err, res)
})
this.info()
.then((res) => this.onInfoCmd(res))
.catch((err) => this.onInfoFail(err))
this.ready = false
}
@@ -471,7 +496,6 @@ const retryConnection = function (self, error) {
self.retryTotaltime += self.retryDelay
self.attempts += 1
self.retryDelay = Math.round(self.retryDelay * self.retryBackoff)
self.createStream()
self.retryTimer = null
}
@@ -498,6 +522,20 @@ RedisClient.prototype.connectionGone = function (why, error) {
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.closing) {
debug('Connection ended by quit / end command, not retrying.')
@@ -510,14 +548,12 @@ RedisClient.prototype.connectionGone = function (why, error) {
return
}
if (typeof this.options.retryStrategy === 'function') {
const retryParams = {
this.retryDelay = this.retryStrategy({
attempt: this.attempts,
error,
totalRetryTime: this.retryTotaltime,
timesConnected: this.timesConnected
}
this.retryDelay = this.options.retryStrategy(retryParams)
})
if (typeof this.retryDelay !== 'number') {
// Pass individual error through
if (this.retryDelay instanceof Error) {
@@ -529,10 +565,13 @@ RedisClient.prototype.connectionGone = function (why, error) {
}, {
error
})
// TODO: Check if this is so smart
if (error) {
this.emit('error', error)
}
this.end(false)
return
}
}
// Retry commands after a reconnect instead of throwing an error. Use this with caution
if (this.options.retryUnfulfilledCommands) {
@@ -574,19 +613,15 @@ RedisClient.prototype.returnError = function (err) {
err.code = match[1]
}
utils.callbackOrEmit(this, commandObj.callback, err)
commandObj.callback(err)
}
function normalReply (self, reply) {
const commandObj = self.commandQueue.shift()
if (typeof commandObj.callback === 'function') {
if (commandObj.command !== 'exec') {
reply = self.handleReply(reply, commandObj.command, commandObj.bufferArgs)
}
commandObj.callback(null, reply)
} else {
debug('No callback for reply')
}
}
function subscribeUnsubscribe (self, reply, type) {
@@ -600,13 +635,13 @@ function subscribeUnsubscribe (self, reply, type) {
// Emit first, then return the callback
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') {
self.subscriptionSet[`${type}_${channel}`] = channel
} else {
type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe' // Make types consistent
delete self.subscriptionSet[`${type}_${channel}`]
const innerType = type === 'unsubscribe' ? 'subscribe' : 'psubscribe' // Make types consistent
delete self.subscriptionSet[`${innerType}_${channel}`]
}
self.emit(type, channel, count)
self.subscribeChannels.push(channel)
}
@@ -625,9 +660,7 @@ function subscribeUnsubscribe (self, reply, type) {
}
}
self.commandQueue.shift()
if (typeof commandObj.callback === 'function') {
commandObj.callback(null, [count, self.subscribeChannels])
}
self.subscribeChannels = []
self.subCommandsLeft = 0
} else {
@@ -738,16 +771,14 @@ RedisClient.prototype.internalSendCommand = function (commandObj) {
let bigData = false
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) {
// Handle offline commands right away
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) {
if (typeof args[i] === 'string') {
// 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.')
err.command = command.toUpperCase()
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])) {
argsCopy[i] = args[i]
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.')
err.command = command.toUpperCase()
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') {
const err = new TypeError('The command contains a "undefined" argument but NodeRedis can only handle strings, numbers and buffers.')
err.command = command.toUpperCase()
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 {
// Seems like numbers are converted fast using string concatenation
argsCopy[i] = `${args[i]}`
@@ -834,16 +868,14 @@ RedisClient.prototype.internalSendCommand = function (commandObj) {
} else {
// Do not expect a reply
// Does this work in combination with the pub sub mode?
if (commandObj.callback) {
utils.replyInOrder(this, commandObj.callback, null, undefined, this.commandQueue)
}
if (this.reply === 'SKIP') {
this.reply = 'SKIP_ONE_MORE'
} else if (this.reply === 'SKIP_ONE_MORE') {
this.reply = 'ON'
}
}
return
return commandObj.promise
}
RedisClient.prototype.writeStrings = function () {
@@ -884,7 +916,6 @@ exports.AbortError = errorClasses.AbortError
exports.RedisError = Parser.RedisError
exports.ParserError = Parser.ParserError
exports.ReplyError = Parser.ReplyError
exports.AggregateError = errorClasses.AggregateError
// Add all redis commands / nodeRedis api to the client
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)
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.args = args
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.callOnWrite = callOnWrite
if (betterStackTraces) {

View File

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

View File

@@ -5,7 +5,7 @@ const URL = require('url')
module.exports = function createClient (portArg, hostArg, options) {
if (typeof portArg === 'number' || (typeof portArg === 'string' && /^\d+$/.test(portArg))) {
let host
var host
if (typeof hostArg === 'string') {
host = hostArg
} else {
@@ -40,7 +40,7 @@ module.exports = function createClient (portArg, hostArg, options) {
options.port = parsed.port
}
if (parsed.search !== '') {
let elem
var elem
for (elem in parsed.query) {
// If options are passed twice, only the parsed options will be used
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(AggregateError, AbortError)
Object.defineProperty(AbortError.prototype, 'name', {
value: 'AbortError',
configurable: true,
writable: true
})
Object.defineProperty(AggregateError.prototype, 'name', {
value: 'AggregateError',
configurable: true,
writable: true
})
module.exports = {
AbortError,
AggregateError
AbortError
}

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
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
if (typeof command !== 'string') {
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 (args === undefined || args === null) {
args = []
} else if (typeof args === 'function' && callback === undefined) {
callback = args
args = []
} else {
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
// 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
// from each other, so let's just do it do it this way for the time being
if (command === 'multi' || typeof this[command] !== 'function') {
return this.internalSendCommand(new Command(command, args, callback))
}
if (typeof callback === 'function') {
args = args.concat([callback]) // Prevent manipulating the input array
return this.internalSendCommand(new Command(command, 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) {
if (typeof options === 'function') {
callback = options

View File

@@ -1,11 +1,9 @@
'use strict'
const utils = require('./utils')
const debug = require('./debug')
const Multi = require('./multi')
const Command = require('./command')
const noPasswordIsSet = /no password is set/
const loading = /LOADING/
const RedisClient = require('../').RedisClient
/********************************************************************************************
@@ -32,26 +30,26 @@ RedisClient.prototype.batch = function batch (args) {
return new Multi(this, args)
}
function selectCallback (self, db, callback) {
function selectCallback (self, db) {
return function (err, res) {
if (err === null) {
// Store db in this.selectDb to restore it on reconnect
self.selectedDb = db
}
utils.callbackOrEmit(self, callback, err, res)
return err || res
}
}
RedisClient.prototype.select = function select (db, callback) {
return this.internalSendCommand(new Command('select', [db], selectCallback(this, db, callback)))
RedisClient.prototype.select = function select (db) {
return this.internalSendCommand(new Command('select', [db], null, selectCallback(this, db)))
}
Multi.prototype.select = function select (db, callback) {
this.queue.push(new Command('select', [db], selectCallback(this._client, db, callback)))
Multi.prototype.select = function select (db) {
this.queue.push(new Command('select', [db], null, selectCallback(this._client, db)))
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
const self = this
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.
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
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
if (this.exec !== this.execTransaction) {
const self = this
const callOnWrite = function () {
self._client.monitoring = true
}
this.queue.push(new Command('monitor', [], callback, callOnWrite))
this.queue.push(new Command('monitor', [], callOnWrite))
return this
}
// Set multi monitoring to indicate the exec that it should abort
@@ -79,30 +77,29 @@ Multi.prototype.monitor = function monitor (callback) {
return this
}
function quitCallback (self, callback) {
function quitCallback (self) {
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') {
// Pretend the quit command worked properly in this case.
// 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 stream is not writable
// or while sending the quit, the connection ended / closed
err = null
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 'OK'
}
return err || res
}
}
RedisClient.prototype.quit = function quit (callback) {
RedisClient.prototype.quit = function quit () {
// 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.
// 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
this.closing = true
this.ready = false
@@ -110,23 +107,27 @@ RedisClient.prototype.quit = function quit (callback) {
}
// Only works with batch, not in a transaction
Multi.prototype.quit = function quit (callback) {
Multi.prototype.quit = function quit () {
const self = this._client
const callOnWrite = function () {
// If called in a multi context, we expect redis is available
self.closing = true
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
}
function infoCallback (self, callback) {
function infoCallback (self) {
return function (err, res) {
if (res) {
if (err) {
self.serverInfo = {}
return err
}
const obj = {}
const lines = res.toString().split('\r\n')
let line, parts, subParts
var line, parts, subParts
for (let i = 0; i < lines.length; i++) {
parts = lines[i].split(':')
@@ -151,89 +152,70 @@ function infoCallback (self, callback) {
}
// Expose info key/values to users
self.serverInfo = obj
} else {
self.serverInfo = {}
}
utils.callbackOrEmit(self, callback, err, res)
return res
}
}
// Store info in this.serverInfo after each call
RedisClient.prototype.info = function info (section, callback) {
let args = []
if (typeof section === 'function') {
callback = section
} else if (section !== undefined) {
RedisClient.prototype.info = function info (section) {
var args = []
if (section !== undefined) {
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) {
let args = []
if (typeof section === 'function') {
callback = section
} else if (section !== undefined) {
Multi.prototype.info = function info (section) {
var args = []
if (section !== undefined) {
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
}
function authCallback (self, pass, callback) {
function authCallback (self, pass) {
return function (err, res) {
if (err) {
if (noPasswordIsSet.test(err.message)) {
self.warn('Warning: Redis server does not require a password, but a password was supplied.')
err = null
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 'OK' // TODO: Fix this
}
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}`)
// Stash auth for connect and reconnect.
this.authPass = pass
const ready = this.ready
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
return tmp
}
// 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}`)
// Stash auth for connect and reconnect.
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
}
RedisClient.prototype.client = function client () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length
arr = new Array(len + 1)
arr[0] = arguments[0]
@@ -242,18 +224,13 @@ RedisClient.prototype.client = function client () {
}
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
}
}
const self = this
let callOnWrite
var callOnWrite
// CLIENT REPLY ON|OFF|SKIP
/* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
@@ -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 () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length
arr = new Array(len + 1)
arr[0] = arguments[0]
@@ -287,18 +259,13 @@ Multi.prototype.client = function client () {
}
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
}
}
const self = this._client
let callOnWrite
var callOnWrite
// CLIENT REPLY ON|OFF|SKIP
/* istanbul ignore next: TODO: Remove this as soon as Travis runs Redis 3.2 */
if (arr.length === 2 && arr[0].toString().toUpperCase() === 'REPLY') {
@@ -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
}
RedisClient.prototype.hmset = function hmset () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length
arr = new Array(len + 1)
arr[0] = arguments[0]
for (; i < len; i += 1) {
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]]
for (const field in arguments[1]) {
arr.push(field, arguments[1][field])
}
callback = arguments[2]
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
}
}
return this.internalSendCommand(new Command('hmset', arr, callback))
return this.internalSendCommand(new Command('hmset', arr))
}
Multi.prototype.hmset = function hmset () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0]
callback = arguments[1]
} else if (Array.isArray(arguments[1])) {
if (len === 3) {
callback = arguments[2]
}
len = arguments[1].length
arr = new Array(len + 1)
arr[0] = arguments[0]
for (; i < len; i += 1) {
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]]
for (const field in arguments[1]) {
arr.push(field, arguments[1][field])
}
callback = arguments[2]
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
}
}
this.queue.push(new Command('hmset', arr, callback))
this.queue.push(new Command('hmset', arr))
return this
}
RedisClient.prototype.subscribe = function subscribe () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
@@ -416,24 +354,17 @@ RedisClient.prototype.subscribe = function subscribe () {
const callOnWrite = function () {
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 () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
@@ -443,25 +374,18 @@ Multi.prototype.subscribe = function subscribe () {
const callOnWrite = function () {
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
}
RedisClient.prototype.unsubscribe = function unsubscribe () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
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
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 () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
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
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
}
RedisClient.prototype.psubscribe = function psubscribe () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
@@ -528,24 +438,17 @@ RedisClient.prototype.psubscribe = function psubscribe () {
const callOnWrite = function () {
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 () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
arr[i] = arguments[i]
@@ -555,25 +458,18 @@ Multi.prototype.psubscribe = function psubscribe () {
const callOnWrite = function () {
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
}
RedisClient.prototype.punsubscribe = function punsubscribe () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
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
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 () {
let arr
let len = arguments.length
let callback
let i = 0
var arr
var len = arguments.length
var i = 0
if (Array.isArray(arguments[0])) {
arr = arguments[0].slice(0)
callback = arguments[1]
} else {
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)
for (; i < len; i += 1) {
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
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
}

View File

@@ -4,10 +4,12 @@ const Queue = require('double-ended-queue')
const utils = require('./utils')
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) {
this._client = client
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
for (let i = 0; i < args.length; i++) {
command = args[i][0]
@@ -25,163 +27,116 @@ function pipelineTransactionCommand (self, commandObj, index) {
// Queueing is done first, then the commands are executed
const tmp = commandObj.callback
commandObj.callback = function (err, reply) {
// Ignore the multi command. This is applied by nodeRedis and the user does not benefit by it
if (err && index !== -1) {
if (tmp) {
if (err) {
tmp(err)
}
err.position = index
self.errors.push(err)
return
}
// Keep track of who wants buffer responses:
// By the time the callback is called the commandObj got the bufferArgs attribute attached
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) {
return this.execBatch(callback)
return this.execBatch()
}
return this.exec(callback)
return this.exec()
}
function multiCallback (self, err, replies) {
let 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
}
function multiCallback (self, replies) {
var i = 0
if (replies) {
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)
// LUA script could return user errors that don't behave like all other errors!
if (match) {
replies[i].code = match[1]
}
replies[i].command = commandObj.command.toUpperCase()
if (typeof commandObj.callback === 'function') {
commandObj.callback(replies[i])
}
} else {
// If we asked for strings, even in detectBuffers mode, then return strings:
replies[i] = self._client.handleReply(replies[i], commandObj.command, self.wantsBuffers[i])
if (typeof commandObj.callback === 'function') {
commandObj.callback(null, replies[i])
}
}
i++
}
}
if (self.callback) {
self.callback(null, replies)
}
return replies
}
Multi.prototype.execTransaction = function execTransaction (callback) {
Multi.prototype.execTransaction = function execTransaction () {
if (this.monitoring || this._client.monitoring) {
const err = new RangeError(
'Using transaction with a client that is in monitor mode does not work due to faulty return values of Redis.'
)
err.command = 'EXEC'
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 = self.queue.length
self.errors = []
self.callback = callback
self._client.cork()
self.wantsBuffers = new Array(len)
pipelineTransactionCommand(self, new Command('multi', []), -1)
const len = this.queue.length
this.errors = []
this._client.cork()
this.wantsBuffers = new Array(len)
// Silently ignore this error. We'll receive the error for the exec as well
const promises = [this._client.internalSendCommand(new Command('multi', [])).catch(() => {})]
// Drain queue, callback will catch 'QUEUED' or error
for (let index = 0; index < len; index++) {
// 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) => {
multiCallback(self, err, replies)
}))
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 main = this._client.internalSendCommand(new Command('exec', []))
this._client.uncork()
const self = this
const len = self.queue.length
let index = 0
let commandObj
if (len === 0) {
utils.replyInOrder(self._client, callback, null, [])
return
return Promise.all(promises).then(() => main.then((replies) => multiCallback(self, replies)).catch((err) => {
err.errors = self.errors
return Promise.reject(err)
}))
}
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()
if (!callback) {
for (commandObj = self.queue.shift(); commandObj !== undefined; commandObj = self.queue.shift()) {
self._client.internalSendCommand(commandObj)
var error = false
this._client.cork()
const promises = []
while (this.queue.length) {
const commandObj = this.queue.shift()
promises.push(this._client.internalSendCommand(commandObj).catch((e) => {
error = true
return e
}))
}
self._client.uncork()
return
this._client.uncork()
return Promise.all(promises).then((res) => {
if (error) {
const err = new Error('bla failed')
err.code = 'foo'
err.replies = res
return Promise.reject(err)
}
const callbackWithoutOwnCb = function (err, res) {
if (err) {
self.results.push(err)
// Add the position to the error
const i = self.results.length - 1
self.results[i].position = i
} else {
self.results.push(res)
}
// Do not emit an error here. Otherwise each error would result in one emit.
// 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
return res
})
}
module.exports = Multi

View File

@@ -15,10 +15,13 @@ function replyToObject (reply) {
}
function replyToStrings (reply) {
if (reply instanceof Buffer) {
if (reply === null) {
return null
}
if (typeof reply.inspect === 'function') { // instanceof Buffer
return reply.toString()
}
if (reply instanceof Array) {
if (typeof reply.map === 'function') { // instanceof Array
const res = new Array(reply.length)
for (let i = 0; i < reply.length; i++) {
// 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)
// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
function clone (obj) {
let copy
var copy
if (Array.isArray(obj)) {
copy = new Array(obj.length)
for (let i = 0; i < obj.length; i++) {
@@ -56,18 +59,10 @@ function convenienceClone (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) {
// 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
let commandObj
var commandObj
if (queue) {
commandObj = queue.peekBack()
} else {
@@ -75,20 +70,14 @@ function replyInOrder (self, callback, err, res, queue) {
}
if (!commandObj) {
process.nextTick(() => {
callbackOrEmit(self, callback, err, res)
callback(err, res)
})
} else {
// TODO: Change this to chain promises instead
const tmp = commandObj.callback
commandObj.callback = tmp
? function (e, r) {
commandObj.callback = function (e, r) {
tmp(e, r)
callbackOrEmit(self, callback, err, res)
}
: function (e) {
if (e) {
self.emit('error', e)
}
callbackOrEmit(self, callback, err, res)
callback(err, res)
}
}
}
@@ -99,6 +88,5 @@ module.exports = {
errCode: /^([A-Z]+)\s+(.+)$/,
monitorRegex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]+ .+]( ".+?")+$/,
clone: convenienceClone,
callbackOrEmit,
replyInOrder
}

View File

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

View File

@@ -30,81 +30,42 @@ if (process.platform !== 'win32') {
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()
client = redis.createClient.apply(null, args)
client.auth(auth, (err, res) => {
assert.strictEqual(null, err)
assert.strictEqual('OK', res.toString())
return done(err)
})
return client.auth(auth).then(helper.isString('OK'))
})
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()
client = redis.createClient.apply(null, args)
const time = Date.now()
client.auth(auth, (err, res) => {
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) => {
client.on('error', helper.isError(/Ready check failed: NOAUTH Authentication required./))
return client.auth(`${auth}bad`).then(assert, (err) => {
assert.strictEqual(err.command, 'AUTH')
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()
client = redis.createClient.apply(null, args)
client.auth('', (err) => {
client.on('error', helper.isError(/Ready check failed: NOAUTH Authentication required./))
return client.auth('').then(helper.fail).catch((err) => {
assert.strictEqual(err.command, 'AUTH')
assert.ok(/ERR invalid password/.test(err.message))
done()
})
})
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()
const end = helper.callFuncAfter(done, 2)
client = redis.createClient(`redis://:${auth}@${config.HOST[ip]}:${config.PORT}`)
client.on('ready', () => {
end()
})
// The info command may be used while loading but not if not yet authenticated
client.info((err) => {
assert.strictEqual(err, null)
end(err)
})
return client.info()
})
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.authPass, auth)
client.on('ready', () => {
const promises = []
// Set a key so the used database is returned in the info command
client.set('foo', 'bar')
client.get('foo')
promises.push(client.set('foo', 'bar'))
promises.push(client.get('foo'))
assert.strictEqual(client.serverInfo.db2, undefined)
// Using the info command should update the serverInfo
client.info((err) => {
assert.strictEqual(err, null)
promises.push(client.info().then(() => {
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)
client = redis.createClient.apply(null, args)
client.auth(auth)
client.auth(auth).catch(done)
client.on('ready', done)
})
@@ -163,7 +125,7 @@ if (process.platform !== 'win32') {
if (helper.redisProcess().spawnFailed()) this.skip()
client = redis.createClient.apply(null, args)
client.auth(auth)
client.auth(auth).catch(done)
client.on('ready', function () {
if (this.timesConnected < 3) {
let interval = setInterval(() => {
@@ -173,8 +135,8 @@ if (process.platform !== 'win32') {
clearInterval(interval)
interval = null
client.stream.destroy()
client.set('foo', 'bar')
client.get('foo') // Errors would bubble
client.set('foo', 'bar').catch(done)
client.get('foo').catch(done)
assert.strictEqual(client.offlineQueue.length, 2)
}, 1)
} else {
@@ -190,11 +152,11 @@ if (process.platform !== 'win32') {
if (helper.redisProcess().spawnFailed()) this.skip()
const args = config.configureClient(ip, {
authPass: auth
password: auth
})
client = redis.createClient.apply(null, args)
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.on('ready', () => {
client.set('foo', 'bar', (err) => {
client.set('foo', 'bar').catch((err) => {
assert.strictEqual(err.message, 'NOAUTH Authentication required.')
assert.strictEqual(err.code, 'NOAUTH')
assert.strictEqual(err.command, 'SET')
@@ -245,16 +207,13 @@ if (process.platform !== 'win32') {
})
client = redis.createClient.apply(null, args)
client.set('foo', 'bar')
client.subscribe('somechannel', 'another channel', (err) => {
assert.strictEqual(err, null)
client.once('ready', () => {
client.subscribe('somechannel', 'another channel').then(() => {
assert.strictEqual(client.pubSubMode, 1)
client.get('foo', (err) => {
client.get('foo').catch((err) => {
assert(/ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message))
done()
})
})
})
client.once('ready', () => {
// Coherent behavior with all other offline commands fires commands before emitting but does not wait till they return
assert.strictEqual(client.pubSubMode, 2)
@@ -282,27 +241,17 @@ if (process.platform !== 'win32') {
})
client.batch()
.auth(auth)
.select(5, (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(client.selectedDb, 5)
assert.strictEqual(res, 'OK')
assert.notDeepEqual(client.serverInfo.db5, { avgTtl: 0, expires: 0, keys: 1 })
})
.select(5)
.monitor()
.set('foo', 'bar', helper.isString('OK'))
.info('stats', (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.indexOf('# Stats\r\n'), 0)
assert.strictEqual(client.serverInfo.sync_full, '0')
})
.get('foo', helper.isString('bar'))
.subscribe(['foo', 'bar', 'foo'], helper.isDeepEqual([2, ['foo', 'bar', 'foo']]))
.set('foo', 'bar')
.info('stats')
.get('foo')
.subscribe(['foo', 'bar', 'foo'])
.unsubscribe('foo')
.subscribe('/foo', helper.isDeepEqual([2, ['/foo']]))
.subscribe('/foo')
.psubscribe('*')
.quit(helper.isString('OK'))
.exec((err, res) => {
assert.strictEqual(err, null)
.quit()
.exec().then((res) => {
res[4] = res[4].substr(0, 9)
assert.deepStrictEqual(
res,

View File

@@ -11,36 +11,21 @@ describe('The \'batch\' method', () => {
describe('when not connected', () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('connect', () => {
client.quit()
})
client.on('end', done)
return client.quit()
})
it('returns an empty array for missing commands', (done) => {
it('returns an empty array for missing commands', () => {
const batch = client.batch()
batch.exec((err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0)
done()
})
return batch.exec().then(helper.isDeepEqual([]))
})
it('returns an error for batch with commands', (done) => {
it('returns an error for batch with commands', () => {
const batch = client.batch()
batch.set('foo', 'bar')
batch.exec((err, res) => {
assert.strictEqual(err, null)
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)
return batch.exec().then(helper.fail).catch((err) => {
assert.strictEqual(err.replies[0].code, 'NR_CLOSED')
})
})
})
@@ -48,122 +33,85 @@ describe('The \'batch\' method', () => {
describe('when connected', () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb((err) => {
return done(err)
})
})
return client.flushdb()
})
afterEach(() => {
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
client.set('foo', 'bar', () => {
client.set('foo', 'bar').then(() => {
called = true
})
const batch = client.batch()
batch.exec((err, res) => {
assert.strictEqual(err, null)
return batch.exec().then((res) => {
assert.strictEqual(res.length, 0)
assert(called)
done()
})
})
it('runs normal calls in-between batch', (done) => {
it('runs normal calls in-between batch', () => {
const batch = client.batch()
batch.set('m1', '123')
client.set('m2', '456', done)
return client.set('m2', '456')
})
it('returns an empty array if promisified', () => {
return client.batch().execAsync().then((res) => {
assert.strictEqual(res.length, 0)
})
})
it('returns an empty result array', (done) => {
it('returns an empty result array', () => {
const batch = client.batch()
let async = true
batch.exec((err, res) => {
assert.strictEqual(err, null)
const promise = batch.exec().then((res) => {
assert.strictEqual(res.length, 0)
async = false
done()
})
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()
batch1.mset('batchfoo', '10', 'batchbar', '20', helper.isString('OK'))
// 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'))
batch1.mset('batchfoo', '10', 'batchbar', '20')
// Provoke an error at queue time
batch1.set('foo2')
batch1.incr('batchfoo')
batch1.incr('batchbar')
batch1.exec((err, res) => {
// TODO: This should actually return an error!
assert.strictEqual(err, null)
assert.strictEqual(res[1].command, 'SET')
assert.strictEqual(res[1].code, 'ERR')
done()
return batch1.exec().then(helper.fail).catch(() => {
// Confirm that the previous command, while containing an error, still worked.
const batch2 = client.batch()
batch2.get('foo2')
batch2.incr('batchbar')
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
client.batch([
['mget', 'batchfoo', 'batchbar', helper.isDeepEqual([null, null])],
['set', 'foo2', helper.isError()],
return client.batch([
['mget', 'batchfoo', 'batchbar'],
['set', 'foo2'],
['incr', 'batchfoo'],
['incr', 'batchbar']
]).exec((err, replies) => {
// TODO: This should actually return an error!
assert.strictEqual(err, null)
]).exec().then(helper.fail).catch((err) => {
const replies = err.replies
assert.strictEqual(2, replies[0].length)
assert.strictEqual(null, replies[0][0])
assert.strictEqual(null, replies[0][1])
assert.strictEqual('SET', replies[1].command)
assert.strictEqual('1', replies[2].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 2'])
client.sadd('some set', 'mem 3')
@@ -171,60 +119,51 @@ describe('The \'batch\' method', () => {
// make sure empty mb reply works
client.del('some missing set')
client.smembers('some missing set', (err, reply) => {
assert.strictEqual(err, null)
client.smembers('some missing set').then((reply) => {
// make sure empty mb reply works
assert.strictEqual(0, reply.length)
})
// test nested batch-bulk replies with empty mb elements.
client.batch([
return client.batch([
['smembers', ['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')
.exec((err, replies) => {
assert.strictEqual(err, null)
.exec().then((replies) => {
assert.strictEqual(4, replies[0].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 arr = ['batchhmset', 'batchbar', 'batchbaz']
const arr2 = ['some manner of key', 'otherTypes']
const arr3 = [5768, 'batchbarx', 'batchfoox']
const arr4 = ['mset', [578, 'batchbar'], helper.isString('OK')]
client.batch([
const arr4 = ['mset', [578, 'batchbar']]
return client.batch([
arr4,
[['mset', 'batchfoo2', 'batchbar2', 'batchfoo3', 'batchbar3'], helper.isString('OK')],
[['mset', 'batchfoo2', 'batchbar2', 'batchfoo3', 'batchbar3']],
['hmset', arr],
[['hmset', 'batchhmset2', 'batchbar2', 'batchfoo3', 'batchbar3', 'test'], helper.isString('OK')],
['hmset', ['batchhmset', 'batchbar', 'batchfoo'], helper.isString('OK')],
['hmset', arr3, helper.isString('OK')],
[['hmset', 'batchhmset2', 'batchbar2', 'batchfoo3', 'batchbar3', 'test']],
['hmset', ['batchhmset', 'batchbar', 'batchfoo']],
['hmset', arr3],
['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'], helper.isString('OK')]
['hmset', 'batchhmset', ['batchbar', 'batchbaz']]
])
.hmget(now, 123456789, 'otherTypes')
.hmget('key2', arr2, () => {})
.hmget('key2', arr2)
.hmget(['batchhmset2', 'some manner of key', 'batchbar3'])
.mget('batchfoo2', ['batchfoo3', 'batchfoo'], (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res[0], 'batchbar2')
assert.strictEqual(res[1], 'batchbar3')
assert.strictEqual(res[2], null)
})
.exec((err, replies) => {
.mget('batchfoo2', ['batchfoo3', 'batchfoo'])
.exec().then((replies) => {
assert.strictEqual(arr.length, 3)
assert.strictEqual(arr2.length, 2)
assert.strictEqual(arr3.length, 3)
assert.strictEqual(arr4.length, 3)
assert.strictEqual(null, err)
assert.strictEqual(arr4.length, 2)
assert.strictEqual(replies[10][1], '555')
assert.strictEqual(replies[11][0], 'a type of value')
assert.strictEqual(replies[12][0], null)
@@ -232,93 +171,62 @@ describe('The \'batch\' method', () => {
assert.strictEqual(replies[13][0], 'batchbar2')
assert.strictEqual(replies[13].length, 3)
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.
client.batch().hmset(true, {
return client.batch().hmset(true, {
test: 123,
bar: 'baz'
}).exec(done)
}).exec()
})
it('runs a batch without any further commands', (done) => {
client.batch().exec((err, res) => {
assert.strictEqual(err, null)
it('runs a batch without any further commands', () => {
return client.batch().exec().then((res) => {
assert.strictEqual(res.length, 0)
done()
})
})
it('runs a batch without any further commands and without callback', () => {
client.batch().exec()
})
it('allows multiple operations to be performed using a chaining API', (done) => {
client.batch()
it('allows multiple operations to be performed using a chaining API', () => {
return client.batch()
.mset('some', '10', 'keys', '20')
.incr('some')
.incr('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) => {
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', () => {
it('allows multiple commands to work the same as normal to be performed using a chaining API', () => {
return client.batch()
.mset(['some', '10', 'keys', '20'])
.incr('some', helper.isNumber(11))
.incr(['keys'], helper.isNumber(21))
.incr('some')
.incr(['keys'])
.mget('some', 'keys')
.execAsync()
.then((res) => {
helper.isDeepEqual(['OK', 11, 21, ['11', '21']])(null, res)
})
.exec().then(helper.isDeepEqual(['OK', 11, 21, ['11', '21']]))
})
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.
client.batch([
return client.batch([
['mget', ['batchfoo', 'some', 'random value', 'keys']],
['incr', 'batchfoo']
])
.exec((err, replies) => {
assert.strictEqual(err, null)
.exec().then((replies) => {
assert.strictEqual(replies.length, 2)
assert.strictEqual(replies[0].length, 4)
return done()
})
})
it('allows multiple operations to be performed on a hash', (done) => {
client.batch()
it('allows multiple operations to be performed on a hash', () => {
return client.batch()
.hmset('batchhash', 'a', 'foo', 'b', 1)
.hmset('batchhash', {
extra: 'fancy',
things: 'here'
})
.hgetall('batchhash')
.exec(done)
})
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))
.exec()
})
})
})

View File

@@ -12,14 +12,12 @@ describe('The \'blpop\' method', () => {
let client
let bclient
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
it('pops value immediately if list contains values', (done) => {
it('pops value immediately if list contains values', () => {
bclient = redis.createClient.apply(null, args)
redis.debugMode = true
let text = ''
@@ -27,43 +25,42 @@ describe('The \'blpop\' method', () => {
text += data
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()
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
bclient.blpop('blocking list', 0, (err, value) => {
assert.strictEqual(value[0], 'blocking list')
assert.strictEqual(value[1], 'initial value')
return done(err)
})
return promise
.then(() => bclient.blpop(values[0], 0))
.then(helper.isDeepEqual(values))
})
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)
client.rpush(['blocking list', 'initial value'], helper.isNumber(1))
bclient.blpop(['blocking list', 0], (err, value) => {
assert.strictEqual(value[0], 'blocking list')
assert.strictEqual(value[1], 'initial value')
return done(err)
})
return client.rpush(['blocking list', 'initial value'])
.then(helper.isNumber(1))
.then(() => bclient.blpop(['blocking list', 0]))
.then(helper.isDeepEqual(['blocking list', 'initial value']))
})
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.blpop('blocking list 2', 5, (err, value) => {
assert.strictEqual(value[0], 'blocking list 2')
assert.strictEqual(value[1], 'initial value')
return done(err)
})
client.rpush('blocking list 2', 'initial value', helper.isNumber(1))
const promises = [
bclient.blpop('blocking list 2', 5).then(helper.isDeepEqual(['blocking list 2', 'initial value']))
]
promises.push(new Promise((resolve, reject) => {
setTimeout(() => {
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.blpop('blocking list', 1, (err, res) => {
assert.strictEqual(res, null)
return done(err)
})
return bclient.blpop('blocking list', 1)
.then(helper.fail)
.catch(helper.isError())
})
afterEach(() => {

View File

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

View File

@@ -19,18 +19,14 @@ describe('The \'dbsize\' method', () => {
describe('when not connected', () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.quit()
})
client.on('end', done)
return client.quit()
})
it('reports an error', (done) => {
client.dbsize([], (err, res) => {
it('reports an error', () => {
return client.dbsize([]).then(helper.fail).catch((err) => {
assert(err.message.match(/The connection is already closed/))
done()
})
})
})
@@ -38,52 +34,34 @@ describe('The \'dbsize\' method', () => {
describe('when connected', () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb((err, res) => {
helper.isString('OK')(err, res)
done()
})
})
return client.flushdb().then(helper.isString('OK'))
})
afterEach(() => {
client.end(true)
})
it('returns a zero db size', (done) => {
client.dbsize([], (err, res) => {
helper.isNotError()(err, res)
helper.isType.number()(err, res)
assert.strictEqual(res, 0, 'Initial db size should be 0')
done()
})
it('returns a zero db size', () => {
return client.dbsize([]).then(helper.isNumber(0))
})
describe('when more data is added to Redis', () => {
let oldSize
beforeEach((done) => {
client.dbsize((err, res) => {
helper.isType.number()(err, res)
assert.strictEqual(res, 0, 'Initial db size should be 0')
beforeEach(() => {
return client.dbsize().then((res) => {
helper.isNumber(0)(res)
oldSize = res
client.set(key, value, (err, res) => {
helper.isNotError()(err, res)
done()
})
return client.set(key, value).then(helper.isString('OK'))
})
})
it('returns a larger db size', (done) => {
client.dbsize([], (err, res) => {
helper.isNotError()(err, res)
helper.isType.positiveNumber()(err, res)
it('returns a larger db size', () => {
return client.dbsize([]).then((res) => {
assert.strictEqual(typeof res, 'number')
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}`, () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
it('allows a single key to be deleted', (done) => {
client.set('foo', 'bar')
client.del('foo', helper.isNumber(1))
client.get('foo', helper.isNull(done))
it('allows a single key to be deleted', () => {
return Promise.all([
client.set('foo', 'bar'),
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) => {
client.del('foo', helper.isNumber(0, done))
it('allows del to be called on a key that does not exist', () => {
return client.del('foo').then(helper.isNumber(0))
})
it('allows multiple keys to be deleted', (done) => {
client.mset('foo', 'bar', 'apple', 'banana')
client.del('foo', 'apple', helper.isNumber(2))
client.get('foo', helper.isNull())
client.get('apple', helper.isNull(done))
it('allows multiple keys to be deleted', () => {
return Promise.all([
client.mset('foo', 'bar', 'apple', 'banana'),
client.del('foo', 'apple').then(helper.isNumber(2)),
client.get('foo').then(helper.isNull()),
client.get('apple').then(helper.isNull())
])
})
it('allows multiple keys to be deleted with the array syntax', (done) => {
client.mset('foo', 'bar', 'apple', 'banana')
client.del(['foo', 'apple'], helper.isNumber(2))
client.get('foo', helper.isNull())
client.get('apple', helper.isNull(done))
})
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))
it('allows multiple keys to be deleted with the array syntax', () => {
return Promise.all([
client.mset('foo', 'bar', 'apple', 'banana'),
client.del(['foo', 'apple']).then(helper.isNumber(2)),
client.get('foo').then(helper.isNull()),
client.get('apple').then(helper.isNull())
])
})
afterEach(() => {

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,20 +9,14 @@ describe('The \'geoadd\' method', () => {
describe(`using ${ip}`, () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
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])
client.geoadd('mycity:21:0:location', '13.361389', '38.115556', 'COR', (err, res) => {
console.log(err, res)
// geoadd is still in the unstable branch. As soon as it reaches the stable one, activate this test
done()
})
return client.geoadd('mycity:21:0:location', '13.361389', '38.115556', 'COR')
})
afterEach(() => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,66 +12,51 @@ describe('The \'hset\' method', () => {
let client
const hash = 'test hash'
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
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 value = Buffer.from('abcdefghij')
client.hset(hash, field, value, helper.isNumber(1))
client.hget(hash, field, helper.isString(value.toString(), done))
client.hset(hash, field, value).then(helper.isNumber(1))
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 value = Buffer.from('')
client.hset(hash, field, value, helper.isNumber(1))
client.hget([hash, field], helper.isString('', done))
client.hset(hash, field, value).then(helper.isNumber(1))
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 value = Buffer.from('')
client.hset([hash, field, value], (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res, 1)
client.hset(hash, field, value, helper.isNumber(0, done))
})
client.hset([hash, field, value]).then(helper.isNumber(1))
return client.hset(hash, field, value).then(helper.isNumber(0))
})
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 field = 'array'
// 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
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) => {
const hash = 'test hash'
const field1 = 'buffer'
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 values on the same hash', () => {
return client.hmset('test hash', 'buffer', Buffer.from('abcdefghij'), 'data', new Date())
.then(helper.isString('OK'))
})
it('does not error when a buffer and date are set as fields on the same hash', (done) => {
const hash = 'test hash'
const value1 = 'buffer'
const field1 = Buffer.from('abcdefghij')
const value2 = 'date'
const field2 = 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', () => {
return client.hmset('test hash', Buffer.from('abcdefghij'), 'buffer', new Date(), 'date')
.then(helper.isString('OK'))
})
afterEach(() => {

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,11 +15,9 @@ describe('The \'monitor\' method', () => {
client.end(true)
})
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('connect', () => {
client.flushdb(done)
})
return client.flushdb()
})
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.flushdb()
monitorClient.monitor((err, res) => {
assert.strictEqual(err, null)
monitorClient.monitor().then((res) => {
assert.strictEqual(res, 'OK')
client.mget('some', 'keys', 'foo', 'bar')
client.set('json', JSON.stringify({
@@ -48,33 +45,28 @@ describe('The \'monitor\' method', () => {
another: false
}))
client.eval('return redis.call(\'set\', \'sha\', \'test\')', 0)
monitorClient.get('baz', (err, res) => {
monitorClient.get('baz').then((res) => {
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')
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[1], null)
end(err)
})
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)
end()
})
monitorClient.subscribe('foo', 'baz').then(() => end())
})
monitorClient.on('monitor', (time, args, rawOutput) => {
assert.strictEqual(monitorClient.monitoring, true)
assert.deepEqual(args, responses.shift())
assert.deepStrictEqual(args, responses.shift())
assert(utils.monitorRegex.test(rawOutput), rawOutput)
if (responses.length === 0) {
monitorClient.quit(end)
monitorClient.quit().then(() => end())
}
})
})
@@ -88,8 +80,7 @@ describe('The \'monitor\' method', () => {
path: '/tmp/redis.sock'
})
monitorClient.monitor((err, res) => {
assert.strictEqual(err, null)
monitorClient.monitor().then((res) => {
assert.strictEqual(monitorClient.monitoring, true)
assert.strictEqual(res.inspect(), Buffer.from('OK').inspect())
monitorClient.mget('hello', Buffer.from('world'))
@@ -98,20 +89,20 @@ describe('The \'monitor\' method', () => {
monitorClient.on('monitor', (time, args, rawOutput) => {
assert.strictEqual(typeof rawOutput, 'string')
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
monitorClient.quit(done)
monitorClient.quit().then(() => done())
})
})
it('monitors reconnects properly and works with the offline queue', (done) => {
let called = false
client.monitor(helper.isString('OK'))
client.monitor().then(helper.isString('OK'))
client.mget('hello', 'world')
client.on('monitor', (time, args, rawOutput) => {
assert.strictEqual(client.monitoring, true)
assert(utils.monitorRegex.test(rawOutput), rawOutput)
assert.deepEqual(args, ['mget', 'hello', 'world'])
assert.deepStrictEqual(args, ['mget', 'hello', 'world'])
if (called) {
// End after a reconnect
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) => {
let called = false
const multi = client.batch()
multi.monitor(helper.isString('OK'))
multi.monitor()
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) => {
assert.strictEqual(client.monitoring, true)
assert(utils.monitorRegex.test(rawOutput), rawOutput)
assert.deepEqual(args, ['mget', 'hello', 'world'])
assert.deepStrictEqual(args, ['mget', 'hello', 'world'])
if (called) {
// End after a reconnect
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) => {
client.monitor((err, res) => {
client.monitor().then(assert, (err) => {
assert.strictEqual(err.code, 'UNCERTAIN_STATE')
})
client.on('error', () => {}) // Ignore error here
@@ -154,8 +145,7 @@ describe('The \'monitor\' method', () => {
end()
})
client.on('reconnecting', () => {
client.get('foo', (err, res) => {
assert(!err)
client.get('foo').then((res) => {
assert.strictEqual(client.monitoring, true)
end()
})
@@ -174,10 +164,9 @@ describe('The \'monitor\' method', () => {
]
const pub = redis.createClient()
pub.on('ready', () => {
client.monitor((err, res) => {
assert.strictEqual(err, null)
client.monitor().then((res) => {
assert.strictEqual(res, 'OK')
pub.get('foo', helper.isNull())
pub.get('foo').then(helper.isNull())
})
client.subscribe('/foo', '/bar')
client.unsubscribe('/bar')
@@ -186,21 +175,22 @@ describe('The \'monitor\' method', () => {
client.once('ready', () => {
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.unsubscribe('baz')
}, 150)
let called = false
client.on('monitor', (time, args, rawOutput) => {
assert.deepEqual(args, responses.shift())
assert.deepStrictEqual(args, responses.shift())
assert(utils.monitorRegex.test(rawOutput), rawOutput)
if (responses.length === 0) {
// 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
process.nextTick(() => {
assert(called)
client.quit(done)
pub.end(false)
client.quit().then(() => done())
})
}
})

View File

@@ -21,19 +21,14 @@ describe('The \'mset\' method', () => {
describe('when not connected', () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.quit()
})
client.on('end', done)
return client.quit()
})
it('reports an error', (done) => {
client.mset(key, value, key2, value2, (err, res) => {
assert(err.message.match(/The connection is already closed/))
done()
})
it('reports an error', () => {
return client.mset(key, value, key2, value2)
.then(assert, helper.isError(/The connection is already closed/))
})
})
@@ -42,63 +37,18 @@ describe('The \'mset\' method', () => {
beforeEach((done) => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
done()
})
client.once('ready', done)
})
afterEach(() => {
client.end(true)
})
describe('and a callback is specified', () => {
describe('with valid parameters', () => {
it('sets the value correctly', (done) => {
client.mset(key, value, key2, value2, (err) => {
if (err) {
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()
it('sets the value correctly', () => {
return client.mset(key, value, key2, value2).then(() => {
client.get(key).then(helper.isString(value))
return client.get(key2).then(helper.isString(value2))
})
})
})

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,104 +11,60 @@ describe('The \'select\' method', () => {
describe('when not connected', () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.quit()
})
client.on('end', done)
return client.quit()
})
it('returns an error if redis is not connected', (done) => {
client.select(1, (err, res) => {
assert(err.message.match(/The connection is already closed/))
done()
})
it('returns an error if redis is not connected', () => {
return client.select(1).then(assert, helper.isError(/The connection is already closed/))
})
})
describe('when connected', () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
afterEach(() => {
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.
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(1, (err, res) => {
helper.isNotError()(err, res)
return client.select(1).then((res) => {
assert.strictEqual(client.selectedDb, 1, 'db should be 1 after select')
done()
})
})
describe('and a callback is specified', () => {
describe('with a valid db index', () => {
it('selects the appropriate database', (done) => {
it('selects the appropriate database', () => {
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(1, (err) => {
assert.strictEqual(err, null)
return client.select(1).then(() => {
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) => {
it('returns an error', () => {
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(9999, (err) => {
return client.select(9999).then(assert, (err) => {
assert.strictEqual(err.code, 'ERR')
assert.strictEqual(err.message, 'ERR invalid DB index')
done()
})
})
})
})
describe('and no callback is specified', () => {
describe('with a valid db index', () => {
it('selects the appropriate database', (done) => {
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(1)
setTimeout(() => {
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) => {
assert.strictEqual(client.selectedDb, undefined, 'default db should be undefined')
client.select(3)
client.set('foo', 'bar', () => {
client.stream.destroy()
})
client.set('foo', 'bar').then(() => client.stream.destroy())
client.once('ready', () => {
assert.strictEqual(client.selectedDb, 3)
assert(typeof client.serverInfo.db3 === 'object')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -12,37 +12,34 @@ describe('The \'watch\' method', () => {
describe(`using ${ip}`, () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
afterEach(() => {
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.incr(watched)
const multi = client.multi()
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(watched, 0)
client.watch(watched)
client.incr(watched)
client.multi().incr(watched).exec((err, replies) => {
assert.strictEqual(err, null)
return client.multi().incr(watched).exec().then((replies) => {
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}`, () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
it('reports an error', function (done) {
it('reports an error', function () {
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()
helper.serverVersionAtLeast.call(this, client, [3, 0, 2])
client.zadd('infinity', [+Infinity, 'should be inf'], helper.isNumber(1))
client.zadd('infinity', ['inf', 'should be also be inf'], helper.isNumber(1))
client.zadd('infinity', -Infinity, 'should be negative inf', helper.isNumber(1))
client.zadd('infinity', [99999999999999999999999, 'should not be inf'], helper.isNumber(1))
client.zrange('infinity', 0, -1, 'WITHSCORES', (err, res) => {
assert.strictEqual(err, null)
client.zadd('infinity', [+Infinity, 'should be inf']).then(helper.isNumber(1))
client.zadd('infinity', ['inf', 'should be also be inf']).then(helper.isNumber(1))
client.zadd('infinity', -Infinity, 'should be negative inf').then(helper.isNumber(1))
client.zadd('infinity', [99999999999999999999999, 'should not be inf']).then(helper.isNumber(1))
return client.zrange('infinity', 0, -1, 'WITHSCORES').then((res) => {
assert.strictEqual(res[5], 'inf')
assert.strictEqual(res[1], '-inf')
assert.strictEqual(res[3], '9.9999999999999992e+22')
done()
})
})

View File

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

View File

@@ -9,16 +9,14 @@ describe('The \'zscore\' method', () => {
describe(`using ${ip}`, () => {
let client
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb(done)
})
return client.flushdb()
})
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.zscore('myzset', 'one', helper.isString('1', done))
return client.zscore('myzset', 'one').then(helper.isString('1'))
})
afterEach(() => {

View File

@@ -20,8 +20,8 @@ if (process.platform !== 'win32') {
})
})
before((done) => {
if (helper.redisProcess().spawnFailed()) return done()
before(() => {
if (helper.redisProcess().spawnFailed()) return
master = redis.createClient({
password: 'porkchopsandwiches'
})
@@ -32,7 +32,7 @@ if (process.platform !== 'win32') {
// 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.exec(done)
return multi.exec()
})
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)
let i = 0
slave.info = function (err, res) {
slave.info = function () {
i++
tmp(err, res)
const promise = tmp()
if (!firstInfo || Object.keys(firstInfo).length === 0) {
firstInfo = slave.serverInfo
}
return promise
}
slave.on('connect', () => {
@@ -68,9 +69,9 @@ if (process.platform !== 'win32') {
assert.strictEqual(this.serverInfo.master_link_status, 'up')
assert.strictEqual(firstInfo.master_link_status, 'down')
assert(i > 1)
this.get('foo300', (err, res) => {
this.get('foo300').then((res) => {
assert.strictEqual(res.substr(0, 3), 'bar')
end(err)
end()
})
})
@@ -85,10 +86,10 @@ if (process.platform !== 'win32') {
const end = helper.callFuncAfter(done, 3)
rp.stop(end)
slave.end(true)
master.flushdb((err) => {
end(err)
master.flushdb().then(() => {
end()
master.end(true)
})
}).catch(done)
helper.stopRedis(() => {
helper.startRedis('./conf/redis.conf', end)
})

View File

@@ -17,7 +17,7 @@ describe('connection tests', () => {
})
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.
// Therefore this is not officially supported!
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) => {
let called = 0
client = redis.createClient({
connectTimeout: 5,
port: 9999,
retryStrategy (options) {
client.quit((err, res) => {
client.quit().then((res) => {
assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
assert.strictEqual(called++, -1)
setTimeout(done, 25)
})
}).catch(helper.fail)
assert.strictEqual(called++, 0)
return 5
}
})
client.set('foo', 'bar', (err, res) => {
client.set('foo', 'bar').catch((err) => {
assert.strictEqual(err.message, 'Stream connection ended and command aborted.')
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
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.')
called = true
})
client.quit((err, res) => {
return client.quit().then((res) => {
assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
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
client = redis.createClient(9999, {
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.')
called = true
})
client.quit((err, res) => {
return client.quit().then((res) => {
assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
assert(called)
done()
})
})
@@ -93,44 +89,43 @@ describe('connection tests', () => {
enableOfflineQueue: false
})
client.on('ready', () => {
client.set('foo', 'bar', (err, res) => {
assert.strictEqual(err, null)
client.set('foo', 'bar').then((res) => {
assert.strictEqual(res, 'OK')
called = true
})
client.quit((err, res) => {
client.quit().then((res) => {
assert.strictEqual(res, 'OK')
assert.strictEqual(err, null)
assert(called)
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.set('foo', 'bar', helper.isString('OK'))
client.quit(done)
return Promise.all([
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.set('foo', 'bar', (err, res) => {
assert.strictEqual(err, null)
return client.set('foo', 'bar').then((res) => {
assert.strictEqual(res, 'OK')
client.quit((err, res) => {
const promise = client.quit().then((res) => {
assert.strictEqual(res, 'OK')
done(err)
})
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) => {
client = redis.createClient()
client.once('ready', () => {
client.set('foo', 'bar', helper.isError())
client.quit(done)
client.set('foo', 'bar').catch(helper.isError())
client.quit().then(() => done())
process.nextTick(() => {
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) => {
client = redis.createClient({
retryStrategy (options) {
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.origin.message, 'Connection timeout')
done()
@@ -205,13 +187,14 @@ describe('connection tests', () => {
},
port: 9999
})
client.on('error', helper.isError(/Connection timeout/))
})
it('retryStrategy used to reconnect', (done) => {
client = redis.createClient({
retryStrategy (options) {
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.code, 'NR_CLOSED')
assert.strictEqual(err.origin.code, 'ECONNREFUSED')
@@ -223,6 +206,7 @@ describe('connection tests', () => {
},
port: 9999
})
client.on('error', helper.isError(/Redis connection to 127\.0\.0\.1:9999 failed/))
})
it('retryStrategy used to reconnect with defaults', (done) => {
@@ -232,7 +216,13 @@ describe('connection tests', () => {
redis.debugMode = true
client = redis.createClient({
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)
return null
}
@@ -240,13 +230,6 @@ describe('connection tests', () => {
setTimeout(() => {
client.stream.destroy()
}, 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) => {
const connectTimeout = 500 // in ms
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',
connectTimeout
})
process.nextTick(() => {
assert.strictEqual(client.stream.listeners('timeout').length, 1)
connectTimeout,
retryStrategy () {
return 5000
}
})
process.nextTick(() => assert.strictEqual(client.stream.listeners('timeout').length, 1))
assert.strictEqual(client.address, '10.255.255.1:6379')
assert.strictEqual(client.connectionOptions.family, 4)
client.on('reconnecting', (params) => {
client.on('reconnecting', () => {
throw new Error('No reconnect, since no connection was ever established')
})
const time = Date.now()
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()
}
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)
})
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') {
this.skip()
}
@@ -330,11 +315,7 @@ describe('connection tests', () => {
path: '/tmp/redis.sock',
connectTimeout: 1000
})
const end = helper.callFuncAfter(done, 2)
client.once('ready', end)
client.set('foo', 'bar', end)
return client.set('foo', 'bar')
})
it('connects correctly with args', (done) => {
@@ -343,7 +324,7 @@ describe('connection tests', () => {
client.once('ready', () => {
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.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.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.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.set('foo', 'bar')
client.get('foo', (err, res) => {
assert.strictEqual(res, 'bar')
done(err)
})
client.get('foo')
.then(helper.isString('bar'))
.then(done)
})
})
@@ -400,10 +380,9 @@ describe('connection tests', () => {
client.once('ready', () => {
client.set('foo', 'bar')
client.get('foo', (err, res) => {
assert.strictEqual(res, 'bar')
done(err)
})
client.get('foo')
.then(helper.isString('bar'))
.then(done)
})
})
@@ -416,22 +395,23 @@ describe('connection tests', () => {
client.once('ready', () => {
client.set('foo', 'bar')
client.get('foo', (err, res) => {
assert.strictEqual(res, 'bar')
done(err)
})
client.get('foo')
.then(helper.isString('bar'))
.then(done)
})
})
it('connects correctly even if the info command is not present on the redis server', (done) => {
client = redis.createClient.apply(null, args)
client.info = function (cb) {
const end = helper.callFuncAfter(done, 2)
client.info = function () {
// Mock the result
cb(new Error('ERR unknown command \'info\''))
end()
return Promise.reject(new Error('ERR unknown command \'info\''))
}
client.once('ready', () => {
assert.strictEqual(Object.keys(client.serverInfo).length, 0)
done()
end()
})
})
@@ -512,18 +492,17 @@ describe('connection tests', () => {
const end = helper.callFuncAfter(done, 3)
let delayed = false
let time
// Mock original function and pretent redis is still loading
client.info = function (cb) {
tmp((err, res) => {
// Mock original function and pretend redis is still loading
client.info = function () {
return tmp().then((res) => {
if (!delayed) {
assert(!err)
client.serverInfo.loading = 1
client.serverInfo.loading_eta_seconds = 0.5
delayed = true
time = Date.now()
}
end()
cb(err, res)
return res
})
}
client.on('ready', () => {
@@ -542,11 +521,10 @@ describe('connection tests', () => {
const end = helper.callFuncAfter(done, 3)
let delayed = false
let time
// Mock original function and pretent redis is still loading
client.info = function (cb) {
tmp((err, res) => {
// Mock original function and pretend redis is still loading
client.info = function () {
return tmp().then((res) => {
if (!delayed) {
assert(!err)
// Try reconnecting after one second even if redis tells us the time needed is above one second
client.serverInfo.loading = 1
client.serverInfo.loading_eta_seconds = 2.5
@@ -554,7 +532,7 @@ describe('connection tests', () => {
time = Date.now()
}
end()
cb(err, res)
return res
})
}
client.on('ready', () => {

View File

@@ -43,46 +43,4 @@ describe('errors', () => {
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
})
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('error', done)
client.once('connect', () => {
client.flushdb((err) => {
client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2')
return Promise.all([
client.flushdb(),
client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2'),
client.set('string key 1', 'string value')
return done(err)
})
})
])
})
afterEach(() => {
@@ -30,45 +27,41 @@ describe('detectBuffers', () => {
describe('get', () => {
describe('first argument is a string', () => {
it('returns a string', (done) => {
client.get('string key 1', helper.isString('string value', done))
it('returns a string', () => {
return client.get('string key 1').then(helper.isString('string value'))
})
it('returns a string when executed as part of transaction', (done) => {
client.multi().get('string key 1').exec((err, res) => {
helper.isString('string value', done)(err, res[0])
})
it('returns a string when executed as part of transaction', () => {
return client.multi().get('string key 1').exec().then(helper.isString('string value'))
})
})
describe('first argument is a buffer', () => {
it('returns a buffer', (done) => {
client.get(Buffer.from('string key 1'), (err, reply) => {
it('returns a buffer', () => {
return client.get(Buffer.from('string key 1')).then((reply) => {
assert.strictEqual(true, Buffer.isBuffer(reply))
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) => {
client.multi().get(Buffer.from('string key 1')).exec((err, reply) => {
it('returns a buffer when executed as part of transaction', () => {
return client.multi().get(Buffer.from('string key 1')).exec().then((reply) => {
assert.strictEqual(1, reply.length)
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())
return done(err)
})
})
})
})
describe('multi.hget', () => {
it('can interleave string and buffer results', (done) => {
client.multi()
it('can interleave string and buffer results', () => {
return client.multi()
.hget('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', 'key 2')
.exec((err, reply) => {
.exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(4, reply.length)
assert.strictEqual('val 1', reply[0])
@@ -77,19 +70,18 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[2]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect())
assert.strictEqual('val 2', reply[3])
return done(err)
})
})
})
describe('batch.hget', () => {
it('can interleave string and buffer results', (done) => {
client.batch()
it('can interleave string and buffer results', () => {
return client.batch()
.hget('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', 'key 2')
.exec((err, reply) => {
.exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(4, reply.length)
assert.strictEqual('val 1', reply[0])
@@ -98,71 +90,56 @@ describe('detectBuffers', () => {
assert.strictEqual(true, Buffer.isBuffer(reply[2]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[2].inspect())
assert.strictEqual('val 2', reply[3])
return done(err)
})
})
})
describe('hmget', () => {
describe('first argument is a string', () => {
it('returns strings for keys requested', (done) => {
client.hmget('hash key 2', 'key 1', 'key 2', (err, reply) => {
it('returns strings for keys requested', () => {
return client.hmget('hash key 2', 'key 1', 'key 2').then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(2, reply.length)
assert.strictEqual('val 1', reply[0])
assert.strictEqual('val 2', reply[1])
return done(err)
})
})
it('returns strings for keys requested in transaction', (done) => {
client.multi().hmget('hash key 2', 'key 1', 'key 2').exec((err, reply) => {
it('returns strings for keys requested in transaction', () => {
return client.multi().hmget('hash key 2', 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(true, Array.isArray(reply))
assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length)
assert.strictEqual('val 1', reply[0][0])
assert.strictEqual('val 2', reply[0][1])
return done(err)
})
})
it('handles array of strings with undefined values (repro #344)', (done) => {
client.hmget('hash key 2', 'key 3', 'key 4', (err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
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 (repro #344)', () => {
return client.hmget('hash key 2', 'key 3', 'key 4')
.then(helper.isDeepEqual([null, null]))
})
it('handles array of strings with undefined values in transaction (repro #344)', (done) => {
client.multi().hmget('hash key 2', 'key 3', 'key 4').exec((err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
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)
})
it('handles array of strings with undefined values in transaction (repro #344)', () => {
return client.multi().hmget('hash key 2', 'key 3', 'key 4').exec()
.then(helper.isDeepEqual([[null, null]]))
})
})
describe('first argument is a buffer', () => {
it('returns buffers for keys requested', (done) => {
client.hmget(Buffer.from('hash key 2'), 'key 1', 'key 2', (err, reply) => {
it('returns buffers for keys requested', () => {
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(true, Buffer.isBuffer(reply[0]))
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 32>', reply[1].inspect())
return done(err)
})
})
it('returns buffers for keys requested in transaction', (done) => {
client.multi().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => {
it('returns buffers for keys requested in transaction', () => {
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(2, reply[0].length)
@@ -170,12 +147,11 @@ describe('detectBuffers', () => {
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 32>', reply[0][1].inspect())
return done(err)
})
})
it('returns buffers for keys requested in .batch', (done) => {
client.batch().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => {
it('returns buffers for keys requested in .batch', () => {
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(2, reply[0].length)
@@ -183,63 +159,49 @@ describe('detectBuffers', () => {
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 32>', reply[0][1].inspect())
return done(err)
})
})
})
})
describe('hgetall', (done) => {
describe('hgetall', () => {
describe('first argument is a string', () => {
it('returns string values', (done) => {
client.hgetall('hash key 2', (err, reply) => {
assert.strictEqual('object', typeof reply)
assert.strictEqual(2, Object.keys(reply).length)
assert.strictEqual('val 1', reply['key 1'])
assert.strictEqual('val 2', reply['key 2'])
return done(err)
})
it('returns string values', () => {
return client.hgetall('hash key 2').then(helper.isDeepEqual({
'key 1': 'val 1',
'key 2': 'val 2'
}))
})
it('returns string values when executed in transaction', (done) => {
client.multi().hgetall('hash key 2').exec((err, reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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 transaction', () => {
return client.multi().hgetall('hash key 2').exec().then(helper.isDeepEqual([{
'key 1': 'val 1',
'key 2': 'val 2'
}]))
})
it('returns string values when executed in .batch', (done) => {
client.batch().hgetall('hash key 2').exec((err, reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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', () => {
return client.batch().hgetall('hash key 2').exec().then(helper.isDeepEqual([{
'key 1': 'val 1',
'key 2': 'val 2'
}]))
})
})
describe('first argument is a buffer', () => {
it('returns buffer values', (done) => {
client.hgetall(Buffer.from('hash key 2'), (err, reply) => {
assert.strictEqual(null, err)
it('returns buffer values', () => {
return client.hgetall(Buffer.from('hash key 2')).then((reply) => {
assert.strictEqual('object', typeof reply)
assert.strictEqual(2, Object.keys(reply).length)
assert.strictEqual(true, Buffer.isBuffer(reply['key 1']))
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 32>', reply['key 2'].inspect())
return done(err)
})
})
it('returns buffer values when executed in transaction', (done) => {
client.multi().hgetall(Buffer.from('hash key 2')).exec((err, reply) => {
it('returns buffer values when executed in transaction', () => {
return client.multi().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].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) => {
client.batch().hgetall(Buffer.from('hash key 2')).exec((err, reply) => {
it('returns buffer values when executed in .batch', () => {
return client.batch().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].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 config = require('./lib/config')
const helper = require('./helper')
const fork = require('child_process').fork
const redis = config.redis
@@ -46,13 +47,13 @@ describe('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({
enableOfflineQueue: false
})
client.set('foo', (err, res) => {
return client.set('foo').then(helper.fail).catch((err) => {
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 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) {
RedisProcess.start((err, _rp) => {
rp = _rp
@@ -45,6 +53,10 @@ function toString (res) {
if (Array.isArray(res)) {
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
}
@@ -69,92 +81,58 @@ module.exports = {
return done(err)
}, path.resolve(__dirname, './conf'))
},
isNumber (expected, done) {
return function (err, results) {
assert.strictEqual(err, null, `expected ${expected}, got error: ${err}`)
isNumber (expected) {
return function (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}`)
if (done) done()
}
},
isString (str, done) {
isString (str) {
str = `${str}` // Make sure it's a string
return function (err, results) {
assert.strictEqual(err, null, `expected string '${str}', got error: ${err}`)
return function (results) {
results = arrayHelper(results)
results = toString(results)
assert.strictEqual(results, str, `${str } does not match ${results}`)
if (done) done()
assert.strictEqual(results, str, `${str} does not match ${results}`)
}
},
isNull (done) {
return function (err, results) {
assert.strictEqual(err, null, `expected null, got error: ${err}`)
isNull () {
return function (results) {
results = arrayHelper(results)
assert.strictEqual(results, null, `${results } is not null`)
if (done) done()
assert.strictEqual(results, null, `${results} is not null`)
}
},
isUndefined (done) {
return function (err, results) {
assert.strictEqual(err, null, `expected null, got error: ${err}`)
isUndefined () {
return function (results) {
results = arrayHelper(results)
assert.strictEqual(results, undefined, `${results } is not undefined`)
if (done) done()
assert.strictEqual(results, undefined, `${results} is not undefined`)
}
},
isError (done) {
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) {
isError (regex) {
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)
assert.deepStrictEqual(res, args)
if (done) done()
}
},
isType: {
number (done) {
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}`)
match (pattern) {
return function (results) {
results = arrayHelper(results)
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) {
// Wait until a connection has established (otherwise a timeout is going to be triggered at some point)
if (Object.keys(connection.serverInfo).length === 0) {
@@ -212,10 +190,7 @@ module.exports = {
},
callFuncAfter (func, max) {
let i = 0
return function (err) {
if (err) {
throw err
}
return function () {
i++
if (i >= max) {
func()

View File

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

View File

@@ -6,9 +6,9 @@ const redis = require('../../index')
const client = redis.createClient()
// 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))
client.set('foo', 'bar', (err, res) => {
client.set('foo', 'bar').catch((err) => {
assert(/good-traces.js:11:10/.test(err.stack))
client.quit(() => {
process.exit(0)

View File

@@ -6,7 +6,6 @@ const fs = require('fs')
const path = require('path')
const spawn = require('win-spawn')
const tcpPortUsed = require('tcp-port-used')
const bluebird = require('bluebird')
// wait for redis to be listening in
// all three modes (ipv4, ipv6, socket).
@@ -24,10 +23,12 @@ function waitForRedis (available, cb, port) {
const id = setInterval(() => {
if (running) return
running = true
bluebird.join(
Promise.all([
tcpPortUsed.check(port, '127.0.0.1'),
tcpPortUsed.check(port, '::1'),
(ipV4, ipV6) => {
tcpPortUsed.check(port, '::1')
]).then((ip) => {
const ipV4 = ip[0]
const ipV6 = ip[1]
if (ipV6 === available && ipV4 === available) {
if (fs.existsSync(socket) === available) {
clearInterval(id)

View File

@@ -73,13 +73,7 @@ describe('The \'multi\' method', () => {
for (i = 0; i < 100; i++) {
multi.hset('SOME_KEY', `SOME_FIELD${i}`, buffer)
}
multi.exec((err, res) => {
if (err) {
done(err)
return
}
run()
})
multi.exec().then(run)
})
}
run()
@@ -87,7 +81,7 @@ describe('The \'multi\' method', () => {
})
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
// Triggers a RangeError: Invalid string length if not handled properly
client = redis.createClient()
@@ -97,40 +91,24 @@ describe('The \'multi\' method', () => {
i -= 10230
multi.set(`foo${i}`, `bar${new Array(1024).join('1234567890')}`)
}
client.on('ready', () => {
multi.exec((err, res) => {
assert.strictEqual(err, null)
multi.exec().then((res) => {
assert.strictEqual(res.length, 26241)
})
client.flushdb(done)
})
return client.flushdb()
})
})
helper.allTests((ip, args) => {
describe(`using ${ip}`, () => {
describe('when not connected', () => {
beforeEach((done) => {
const end = helper.callFuncAfter(done, 2)
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.quit(end)
})
client.once('end', end)
return client.quit()
})
it('reports an error', (done) => {
it('reports an error', () => {
const multi = client.multi()
multi.exec((err, res) => {
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/))
})
return multi.exec().then(helper.fail, helper.isError(/The connection is already closed/))
})
})
@@ -140,26 +118,23 @@ describe('The \'multi\' method', () => {
})
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
client.monitor((e) => {
client.on('error', (err) => {
assert.strictEqual(err.code, 'EXECABORT')
client.end(false)
done()
})
return client.monitor().then(() => {
const multi = client.multi()
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
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')
client.end(false)
done()
})
})
@@ -176,24 +151,24 @@ describe('The \'multi\' method', () => {
client.sendCommand('multi')
client.sendCommand('set', ['foo', 'bar'])
client.sendCommand('get', ['foo'])
client.sendCommand('exec', (err, res) => {
assert.strictEqual(err, null)
client.sendCommand('exec').then((res) => {
// res[0] is going to be the monitor result of set
// res[1] is going to be the result of the set command
assert(utils.monitorRegex.test(res[0]))
assert.strictEqual(res[1], 'OK')
assert.strictEqual(res[1].toString(), 'OK')
assert.strictEqual(res.length, 2)
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()
multi1.set('m1', '123')
multi1.get('m1')
multi1.exec(done)
const promise = multi1.exec()
assert.strictEqual(client.offlineQueue.length, 4)
return promise
})
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()
multi1.set('m1', '123')
multi1.get('m1')
multi1.exec((err, res) => {
assert(!err)
called = true
})
multi1.exec().then(() => (called = true))
client.once('ready', () => {
const multi1 = client.multi()
multi1.set('m2', '456')
multi1.get('m2')
multi1.exec((err, res) => {
multi1.exec().then((res) => {
assert(called)
assert(!err)
assert.strictEqual(res[1], '456')
done()
})
@@ -223,7 +194,7 @@ describe('The \'multi\' method', () => {
})
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({
host: 'somewhere',
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.strictEqual(err.errors.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', () => {
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('ready', () => {
client.flushdb((err) => {
return done(err)
})
})
return client.flushdb()
})
it('returns an empty result array', (done) => {
it('returns an empty result array', () => {
const multi = client.multi()
multi.exec((err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0)
done()
})
return multi.exec().then(helper.isDeepEqual([]))
})
it('runs normal calls in-between multis', (done) => {
it('runs normal calls in-between multis', () => {
const multi1 = client.multi()
multi1.set('m1', '123')
client.set('m2', '456', done)
return client.set('m2', '456')
})
it('runs simultaneous multis with the same client', (done) => {
const end = helper.callFuncAfter(done, 2)
it('runs simultaneous multis with the same client', () => {
const multi1 = client.multi()
multi1.set('m1', '123')
multi1.get('m1')
@@ -297,12 +241,13 @@ describe('The \'multi\' method', () => {
multi2.set('m2', '456')
multi2.get('m2')
multi1.exec(end)
multi2.exec(helper.isDeepEqual(['OK', '456'], end))
return Promise.all([
multi1.exec(),
multi2.exec().then(helper.isDeepEqual(['OK', '456']))
])
})
it('runs simultaneous multis with the same client version 2', (done) => {
const end = helper.callFuncAfter(done, 2)
it('runs simultaneous multis with the same client version 2', () => {
const multi2 = client.multi()
const multi1 = client.multi()
@@ -312,46 +257,44 @@ describe('The \'multi\' method', () => {
multi2.get('m1')
multi2.ping()
multi1.exec(end)
multi2.exec(helper.isDeepEqual(['OK', '123', 'PONG'], end))
return Promise.all([
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
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('multibar')
multi1.exec(() => {
return multi1.exec().then(helper.fail, () => {
// Redis 2.6.5+ will abort transactions with errors
// see: http://redis.io/topics/transactions
const multibarExpected = 1
const multifooExpected = 1
// Confirm that the previous command, while containing an error, still worked.
const multi2 = client.multi()
multi2.incr('multibar', helper.isNumber(multibarExpected))
multi2.incr('multifoo', helper.isNumber(multifooExpected))
multi2.exec(helper.isDeepEqual([multibarExpected, multibarExpected], done))
multi2.incr('multibar')
multi2.incr('multifoo')
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
client.multi([
['mget', 'multifoo', 'multibar', helper.isDeepEqual([null, null])],
['set', 'foo2', helper.isError()],
return client.multi([
['mget', 'multifoo', 'multibar'],
['set', 'foo2'],
['incr', 'multifoo'],
['incr', 'multibar']
]).exec((err, replies) => {
]).exec().then(assert, (err) => {
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 2'])
client.sadd('some set', 'mem 3')
@@ -359,60 +302,50 @@ describe('The \'multi\' method', () => {
// make sure empty mb reply works
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.
client.multi([
return client.multi([
['smembers', ['some set']],
['del', 'some set'],
['smembers', 'some set']
])
.scard('some set')
.exec((err, res) => {
assert.strictEqual(err, null)
.exec().then((res) => {
assert.strictEqual(res[0].length, 4)
assert.strictEqual(res[1], 1)
assert.deepStrictEqual(res[2], [])
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 arr = ['multihmset', 'multibar', 'multibaz']
const arr2 = ['some manner of key', 'otherTypes']
const arr3 = [5768, 'multibarx', 'multifoox']
const arr4 = ['mset', [578, 'multibar'], helper.isString('OK')]
let called = false
client.multi([
const arr4 = ['mset', [578, 'multibar']]
return client.multi([
arr4,
[['mset', 'multifoo2', 'multibar2', 'multifoo3', 'multibar3'], helper.isString('OK')],
[['mset', 'multifoo2', 'multibar2', 'multifoo3', 'multibar3']],
['hmset', arr],
[['hmset', 'multihmset2', 'multibar2', 'multifoo3', 'multibar3', 'test'], helper.isString('OK')],
['hmset', ['multihmset', 'multibar', 'multifoo'], helper.isString('OK')],
['hmset', arr3, helper.isString('OK')],
[['hmset', 'multihmset2', 'multibar2', 'multifoo3', 'multibar3', 'test']],
['hmset', ['multihmset', 'multibar', 'multifoo']],
['hmset', arr3],
['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', 'multihmset', ['multibar', 'multibaz'], undefined], // undefined is used as a explicit not set callback variable
['hmset', 'multihmset', ['multibar', 'multibaz'], helper.isString('OK')]
['hmset', 'key2', {'0123456789': 'abcdefghij', 'some manner of key': 'a type of value', 'otherTypes': 999}],
['hmset', 'multihmset', ['multibar', 'multibaz']],
['hmset', 'multihmset', ['multibar', 'multibaz']]
])
.hmget(now, 123456789, 'otherTypes')
.hmget('key2', arr2, () => {})
.hmget('key2', arr2)
.hmget(['multihmset2', 'some manner of key', 'multibar3'])
.mget('multifoo2', ['multifoo3', 'multifoo'], (err, res) => {
assert.strictEqual(err, null)
assert(res[0], 'multifoo3')
assert(res[1], 'multifoo')
called = true
})
.exec((err, replies) => {
assert(called)
.mget('multifoo2', ['multifoo3', 'multifoo'])
.exec().then((replies) => {
assert.strictEqual(arr.length, 3)
assert.strictEqual(arr2.length, 2)
assert.strictEqual(arr3.length, 3)
assert.strictEqual(arr4.length, 3)
assert.strictEqual(null, err)
assert.strictEqual(arr4.length, 2)
assert.strictEqual(replies[10][1], '555')
assert.strictEqual(replies[11][0], 'a type of value')
assert.strictEqual(replies[12][0], null)
@@ -420,166 +353,81 @@ describe('The \'multi\' method', () => {
assert.strictEqual(replies[13][0], 'multibar2')
assert.strictEqual(replies[13].length, 3)
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.
client.multi().hmset(true, {
return client.multi().hmset(true, {
test: 123,
bar: 'baz'
}).exec(done)
}).exec()
})
it('runs a multi without any further commands', (done) => {
client.multi().exec((err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 0)
done()
})
it('runs a multi without any further commands', () => {
return client.multi().exec().then(helper.isDeepEqual([]))
})
it('allows multiple operations to be performed using a chaining API', (done) => {
client.multi()
it('allows multiple operations to be performed using a chaining API', () => {
return client.multi()
.mset('some', '10', 'keys', '20')
.incr('some')
.incr('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) => {
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) => {
it('allows an array to be provided indicating multiple operations to perform', () => {
// test nested multi-bulk replies with nulls.
client.multi([
return client.multi([
['mget', ['multifoo', 'some', 'random value', 'keys']],
['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) => {
client.multi()
it('allows multiple operations to be performed on a hash', () => {
return client.multi()
.hmset('multihash', 'a', 'foo', 'b', 1)
.hmset('multihash', {
extra: 'fancy',
things: 'here'
})
.hgetall('multihash')
.exec((err, replies) => {
assert.strictEqual(null, err)
.exec().then((replies) => {
assert.strictEqual('OK', replies[0])
assert.strictEqual(Object.keys(replies[2]).length, 4)
assert.strictEqual('foo', replies[2].a)
assert.strictEqual('1', replies[2].b)
assert.strictEqual('fancy', replies[2].extra)
assert.strictEqual('here', replies[2].things)
return done()
})
})
it('reports EXECABORT exceptions when they occur (while queueing)', (done) => {
client.multi().config('bar').set('foo').set('bar').exec((err, reply) => {
it('reports EXECABORT exceptions when they occur (while queueing)', () => {
return client.multi().config('bar').set('foo').set('bar').exec().then(assert, (err) => {
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.strictEqual(err.errors.length, 2, 'err.errors should have 2 items')
assert.strictEqual(err.errors[0].command, 'SET')
assert.strictEqual(err.errors[0].code, 'ERR')
assert.strictEqual(err.errors[0].position, 1)
assert(/^ERR/.test(err.errors[0].message), 'Actuall error message should begin with ERR')
return done()
assert(/^ERR/.test(err.errors[0].message), 'Actual error message should begin with ERR')
})
})
it('reports multiple exceptions when they occur (while EXEC is running)', (done) => {
client.multi().config('bar').debug('foo').eval('return {err=\'this is an error\'}', 0).exec((err, reply) => {
assert.strictEqual(err, null)
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')
return done()
it('reports multiple exceptions when they occur (while EXEC is running)', () => {
return client.multi().config('bar').debug('foo').eval('return {err=\'this is an error\'}', 0).exec().then(assert, (err) => {
assert.strictEqual(err.replies.length, 3)
assert.strictEqual(err.replies[0].code, 'ERR')
assert.strictEqual(err.replies[0].command, 'CONFIG')
assert.strictEqual(err.replies[2].code, undefined)
assert.strictEqual(err.replies[2].command, 'EVAL')
assert(/^this is an error/.test(err.replies[2].message))
assert(/^ERR/.test(err.replies[0].message), 'Error message should begin with ERR')
assert(/^ERR/.test(err.replies[1].message), 'Error message should begin with ERR')
})
})
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', () => {
const multi = client.multi()
let test = false
@@ -601,7 +449,7 @@ describe('The \'multi\' method', () => {
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()
let test = false
multi.execBatch = function () {
@@ -609,18 +457,17 @@ describe('The \'multi\' method', () => {
}
multi.set('baz', 'binary')
multi.get('baz')
multi.execAtomic(done)
const promise = multi.execAtomic()
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']]
client.multi(input).exec((err, res) => {
assert.strictEqual(err, null)
return client.multi(input).exec().then((res) => {
assert.strictEqual(input.length, 2)
assert.strictEqual(input[0].length, 3)
assert.strictEqual(input[1].length, 2)
done()
})
})
@@ -630,29 +477,14 @@ describe('The \'multi\' method', () => {
assert.strictEqual(err.code, 'ECONNREFUSED')
})
client.on('ready', () => {
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec((err, res) => {
assert(!err)
client.multi([['set', 'foo', 'bar'], ['get', 'foo']]).exec().then((res) => {
assert.strictEqual(res[1], 'bar')
done()
})
})
})
it('emits error once if reconnecting after multi has been executed but not yet returned without callback', (done) => {
// 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) => {
it('indivdual commands work properly with multi', () => {
// Neither of the following work properly in a transactions:
// (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)
@@ -666,31 +498,23 @@ describe('The \'multi\' method', () => {
//
// Make sure sendCommand is not called
client.sendCommand = function () {
client.sendCommand = () => {
throw new Error('failed')
}
assert.strictEqual(client.selectedDb, undefined)
const multi = client.multi()
multi.select(5, (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(client.selectedDb, 5)
assert.strictEqual(res, 'OK')
assert.notDeepEqual(client.serverInfo.db5, { avg_ttl: 0, expires: 0, keys: 1 })
})
// 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)
multi.select(5)
// multi.client('reply', 'on') // Redis v.3.2
multi.set('foo', 'bar')
multi.info()
multi.get('foo')
return multi.exec().then((res) => {
res[2] = res[2].substr(0, 10)
assert.deepEqual(res, ['OK', 'OK', '# Server\r\n', 'bar'])
client.flushdb(done)
assert.strictEqual(client.selectedDb, 5)
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)
client = redis.createClient()
for (let i = 0; i < names.length; i++) {
assert(/^([a-zA-Z_][a-zA-Z_0-9]*)?$/.test(client[names[i]].name))
}
client.quit(done)
return client.quit()
})
it('reset the parser while reconnecting (See #1190)', (done) => {
@@ -64,11 +64,9 @@ describe('The nodeRedis client', () => {
})
describe('when connected', () => {
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient.apply(null, args)
client.once('connect', () => {
client.flushdb(done)
})
return client.flushdb()
})
describe('duplicate', () => {
@@ -122,7 +120,7 @@ describe('The nodeRedis client', () => {
client.duplicate((err, client) => {
assert(!err)
assert.strictEqual(client.ready, true)
client.quit(done)
client.quit().then(() => done())
})
})
@@ -134,35 +132,20 @@ describe('The nodeRedis 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', () => {
// 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 '
while (str.length < 111111) {
str += 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 '
while (str.length < 111111) {
str += str
@@ -176,102 +159,68 @@ describe('The nodeRedis client', () => {
assert(!client.fireStrings)
temp(data)
}
client.multi().set('foo', str).get('foo', helper.isString(str)).exec((err, res) => {
assert.strictEqual(err, null)
const promise = client.multi().set('foo', str).get('foo').exec().then((res) => {
assert.strictEqual(called, true)
assert.strictEqual(res[1], str)
done()
})
assert(client.fireStrings)
return promise
})
})
describe('sendCommand', () => {
it('omitting args should be fine', (done) => {
it('omitting args should be fine', () => {
client.serverInfo = {}
client.sendCommand('info')
client.sendCommand('ping', (err, res) => {
assert.strictEqual(err, null)
return client.sendCommand('ping').then((res) => {
assert.strictEqual(res, 'PONG')
// Check if the previous info command used the internal individual info command
assert.notDeepEqual(client.serverInfo, {})
client.serverInfo = {}
})
client.sendCommand('info', null, undefined)
client.sendCommand('ping', null, (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, 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)
client.sendCommand('ping', null).then(helper.isString('PONG'))
return client.sendCommand('info').then((res) => {
assert(/redis_version/.test(res))
// The individual info command should also be called by using sendCommand
assert.notDeepEqual(client.serverInfo, {})
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
client.sendCommand('multi')
client.sendCommand('set', ['foo', 'bar'], helper.isString('QUEUED'))
client.sendCommand('set', ['foo', 'bar']).then(helper.isString('QUEUED'))
client.get('foo')
// exec is not manipulated if not fired by the individual multi command
// As the multi command is handled individually by the user he also has to handle the return value
client.exec(helper.isDeepEqual(['OK', 'bar'], done))
return client.exec().then(helper.isDeepEqual(['OK', 'bar']))
})
it('multi should be handled special', (done) => {
client.sendCommand('multi', undefined, helper.isString('OK'))
it('multi should be handled special', () => {
client.sendCommand('multi', undefined).then(helper.isString('OK'))
const args = ['test', 'bla']
client.sendCommand('set', args, helper.isString('QUEUED'))
assert.deepEqual(args, ['test', 'bla']) // Check args manipulation
client.get('test', helper.isString('QUEUED'))
client.sendCommand('set', args).then(helper.isString('QUEUED'))
assert.deepStrictEqual(args, ['test', 'bla']) // Check args manipulation
client.get('test').then(helper.isString('QUEUED'))
// As the multi command is handled individually by the user he also has to handle the return value
client.exec(helper.isDeepEqual(['OK', 'bla'], done))
})
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')
}
return client.exec().then(helper.isDeepEqual(['OK', 'bla']))
})
it('command argument has to be of type string', () => {
try {
client.sendCommand(true, ['test', 'bla'], () => {})
client.sendCommand(true, ['test', 'bla'])
throw new Error('failed')
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "Boolean" for command name')
}
try {
client.sendCommand(undefined, ['test', 'bla'], () => {})
client.sendCommand(undefined, ['test', 'bla'])
throw new Error('failed')
} catch (err) {
assert.strictEqual(err.message, 'Wrong input type "undefined" for command name')
}
try {
client.sendCommand(null, ['test', 'bla'], () => {})
client.sendCommand(null, ['test', 'bla'])
throw new Error('failed')
} catch (err) {
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', () => {
try {
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'))
it('multi should be handled special', () => {
client.sendCommand('multi', undefined).then(helper.isString('OK'))
const args = ['test', 'bla']
client.sendCommand('set', args, helper.isString('QUEUED'))
assert.deepEqual(args, ['test', 'bla']) // Check args manipulation
client.get('test', helper.isString('QUEUED'))
client.sendCommand('set', args).then(helper.isString('QUEUED'))
assert.deepStrictEqual(args, ['test', 'bla']) // Check args manipulation
client.get('test').then(helper.isString('QUEUED'))
// As the multi command is handled individually by the user he also has to handle the return value
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) => {
client.sendCommand('mset', ['foo', 1, 'bar', 2, 'baz', 3], helper.isString('OK'))
it('the args array may contain a arbitrary number of arguments', () => {
client.sendCommand('mset', ['foo', 1, 'bar', 2, 'baz', 3]).then(helper.isString('OK'))
// As the multi command is handled individually by the user he also has to handle the return value
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) => {
client.sendCommand('abcdef', (err, res) => {
assert.strictEqual(err.message, 'ERR unknown command \'abcdef\'')
done()
})
it('sendCommand with callback as args', () => {
return client.sendCommand('abcdef').then(assert, helper.isError(/ERR unknown command 'abcdef'/))
})
})
describe('retryUnfulfilledCommands', () => {
it('should retry all commands instead of returning an error if a command did not yet return after a connection loss', (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({
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[1], 'initial value')
bclient.end(true)
done(err)
})
bclient.once('ready', () => {
setTimeout(() => {
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)
})
return promise
})
it('should retry all commands even if the offline queue is disabled', (done) => {
@@ -345,15 +282,15 @@ describe('The nodeRedis client', () => {
retryUnfulfilledCommands: true
})
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[1], 'initial value')
bclient.end(true)
done(err)
done()
})
setTimeout(() => {
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)
})
})
@@ -367,7 +304,7 @@ describe('The nodeRedis client', () => {
done(new Error('failed'))
}
}, 20)
const cb = function (err, res) {
const cb = function (err) {
assert(/Connection forcefully ended|The connection is already closed./.test(err.message))
assert.strictEqual(err.code, 'NR_CLOSED')
end()
@@ -376,7 +313,7 @@ describe('The nodeRedis client', () => {
if (i === 10) {
client.end()
}
client.set('foo', 'bar', cb)
client.set('foo', 'bar').then(assert, cb)
}
client.on('warning', () => {}) // Ignore deprecation message
setTimeout(() => {
@@ -386,10 +323,8 @@ describe('The nodeRedis client', () => {
})
it('used with flush set to true', (done) => {
const end = helper.callFuncAfter(() => {
done()
}, 20)
const cb = function (err, res) {
const end = helper.callFuncAfter(done, 20)
const cb = function (err) {
assert(/Connection forcefully ended|The connection is already closed./.test(err.message))
end()
}
@@ -398,106 +333,21 @@ describe('The nodeRedis client', () => {
client.end(true)
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', () => {
it('return an error in the callback', function (done) {
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) {
it('return an error in the callback version two', function () {
if (helper.redisProcess().spawnFailed()) this.skip()
client.quit()
setTimeout(() => {
client.get('foo', (err, res) => {
return client.get('foo').then(assert, (err) => {
assert.strictEqual(err.message, 'GET can\'t be processed. The connection is already closed.')
assert.strictEqual(err.command, 'GET')
assert.strictEqual(client.offlineQueue.length, 0)
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)
done()
})
setTimeout(() => {
client.set('foo', 'bar')
}, 50)
})
})
@@ -512,16 +362,15 @@ describe('The nodeRedis client', () => {
assert.strictEqual(Object.keys(client.serverInfo.db0).length, 3)
done()
}, 4)
client.get('recon 1', helper.isString('one', end))
client.get('recon 1', helper.isString('one', end))
client.get('recon 2', helper.isString('two', end))
client.get('recon 2', helper.isString('two', end))
client.get('recon 1').then(helper.isString('one')).then(end)
client.get('recon 1').then(helper.isString('one')).then(end)
client.get('recon 2').then(helper.isString('two')).then(end)
client.get('recon 2').then(helper.isString('two')).then(end)
})
})
client.set('recon 1', 'one')
client.set('recon 2', 'two', (err, res) => {
assert.strictEqual(err, null)
client.set('recon 2', 'two').then((res) => {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy()
@@ -540,11 +389,9 @@ describe('The nodeRedis client', () => {
assert.strictEqual(client.monitoring, false, 'monitoring off at start')
client.set('recon 1', 'one')
client.monitor((err, res) => {
assert.strictEqual(err, null)
client.monitor().then((res) => {
assert.strictEqual(client.monitoring, true, 'monitoring on after monitor()')
client.set('recon 2', 'two', (err, res) => {
assert.strictEqual(err, null)
client.set('recon 2', 'two').then((res) => {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy()
@@ -556,19 +403,18 @@ describe('The nodeRedis client', () => {
// "Connection in subscriber mode, only subscriber commands may be used"
it('reconnects, unsubscribes, and can retrieve the pre-existing data', (done) => {
client.on('ready', () => {
client.unsubscribe(helper.isNotError())
client.unsubscribe()
client.on('unsubscribe', (channel, count) => {
// we should now be out of subscriber mode.
assert.strictEqual(channel, 'recon channel')
assert.strictEqual(count, 0)
client.set('foo', 'bar', helper.isString('OK', done))
client.set('foo', 'bar').then(helper.isString('OK')).then(done)
})
})
client.set('recon 1', 'one')
client.subscribe('recon channel', (err, res) => {
assert.strictEqual(err, null)
client.subscribe('recon channel').then((res) => {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy()
@@ -577,89 +423,31 @@ describe('The nodeRedis client', () => {
it('reconnects, unsubscribes, and can retrieve the pre-existing data of a explicit channel', (done) => {
client.on('ready', () => {
client.unsubscribe('recon channel', helper.isNotError())
client.unsubscribe('recon channel').then(helper.isDeepEqual([0, ['recon channel']]))
client.on('unsubscribe', (channel, count) => {
// we should now be out of subscriber mode.
assert.strictEqual(channel, 'recon channel')
assert.strictEqual(count, 0)
client.set('foo', 'bar', helper.isString('OK', done))
client.set('foo', 'bar').then(helper.isString('OK')).then(done)
})
})
client.set('recon 1', 'one')
client.subscribe('recon channel', (err, res) => {
assert.strictEqual(err, null)
client.subscribe('recon channel').then((res) => {
// Do not do this in normal programs. This is to simulate the server closing on us.
// For orderly shutdown in normal programs, do client.quit()
client.stream.destroy()
})
})
})
describe('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', () => {
it('handles utf-8 keys', (done) => {
it('handles utf-8 keys', () => {
const utf8Sample = 'ಠ_ಠ'
client.set(['utf8test', utf8Sample], helper.isString('OK'))
client.get(['utf8test'], (err, obj) => {
assert.strictEqual(utf8Sample, obj)
done(err)
})
client.set(['utf8test', utf8Sample]).then(helper.isString('OK'))
return client.get(['utf8test']).then(helper.isString(utf8Sample))
})
})
})
@@ -687,13 +475,11 @@ describe('The nodeRedis client', () => {
it('keep execution order for commands that may fire while redis is still loading', (done) => {
client = redis.createClient.apply(null, args)
let fired = false
client.set('foo', 'bar', (err, res) => {
assert.strictEqual(err, null)
client.set('foo', 'bar').then((res) => {
assert.strictEqual(fired, false)
done()
})
client.info((err, res) => {
assert.strictEqual(err, null)
client.info().then(() => {
fired = true
})
})
@@ -730,10 +516,10 @@ describe('The nodeRedis client', () => {
assert.strictEqual(err, error)
assert(err instanceof redis.ParserError)
// After the hard failure work properly again. The set should have been processed properly too
client.get('foo', helper.isString('bar', done))
client.get('foo').then(helper.isString('bar')).then(done)
})
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.code, 'NR_FATAL')
assert(err instanceof redis.AbortError)
@@ -759,7 +545,7 @@ describe('The nodeRedis client', () => {
})
setTimeout(() => {
client.set('foo', 'bar', (err, result) => {
client.set('foo', 'bar').then(helper.fail, (err) => {
if (!finished) done(err)
assert.strictEqual(err.message, 'Connection forcefully ended and command aborted.')
})
@@ -772,17 +558,19 @@ describe('The nodeRedis client', () => {
}, 50)
})
// TODO: Fix this by adding the CONNECTION_BROKEN back in
it.skip('enqueues operation and keep the queue while trying to reconnect', (done) => {
client = redis.createClient(9999, null, {
retryStrategy (options) {
if (options.attempt < 4) {
return 200
return 50
}
}
})
let i = 0
client.on('error', (err) => {
console.log(err)
if (err.code === 'CONNECTION_BROKEN') {
assert(i, 3)
assert.strictEqual(client.offlineQueue.length, 0)
@@ -805,12 +593,10 @@ describe('The nodeRedis client', () => {
assert.strictEqual(params.timesConnected, 0)
assert(params.error instanceof Error)
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('baz', 13)
client.set('foo', 'bar', (err, result) => {
client.set('foo', 'bar').then(assert, (err) => {
assert(i, 3)
assert(err)
assert.strictEqual(client.offlineQueue.length, 0)
@@ -823,39 +609,28 @@ describe('The nodeRedis client', () => {
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)
client.set(`foo${i}`, `bar${i}`).then(helper.fail, helper.isError)
multi.set(`foo${i + 1}`, `bar${i + 1}`)
multi.set(`foo${i + 2}`, `bar${i + 2}`)
}
multi.exec()
multi.exec().then(helper.fail, (err) => {
assert.strictEqual(client.commandQueue.length, 0)
assert.strictEqual(err.errors.length, 9)
assert.strictEqual(err.errors[1].command, 'SET')
assert.deepStrictEqual(err.errors[1].args, ['foo1', 'bar1'])
end()
})
assert.strictEqual(client.commandQueue.length, 15)
helper.killConnection(client)
})
const end = helper.callFuncAfter(done, 3)
const end = helper.callFuncAfter(done, 2)
client.on('error', (err) => {
if (err.command === 'EXEC') {
assert.strictEqual(client.commandQueue.length, 0)
assert.strictEqual(err.errors.length, 9)
assert.strictEqual(err.errors[1].command, 'SET')
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.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.')
done()
})
@@ -881,67 +656,25 @@ describe('The nodeRedis client', () => {
const end = helper.callFuncAfter(done, 3)
client.on('error', (err) => {
assert(/offline queue is deactivated|ECONNREFUSED/.test(err.message))
assert(/ECONNREFUSED/.test(err.message))
assert.strictEqual(client.commandQueue.length, 0)
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(() => {
client.set('foo', 'bar', (err) => {
client.set('foo', 'bar').then(assert, (err) => {
// should callback with an error
assert.ok(err)
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}`, () => {
let client = null
beforeEach((done) => {
beforeEach(() => {
client = redis.createClient({
prefix: 'test:prefix:'
})
client.on('ready', () => {
client.flushdb((err) => {
done(err)
})
})
return client.flushdb()
})
afterEach(() => {
client.end(true)
})
it('auto prefix set / get', (done) => {
client.set('key', 'value', helper.isString('OK'))
client.get('key', helper.isString('value'))
client.getrange('key', 1, -1, (err, reply) => {
it('auto prefix set / get', () => {
return Promise.all([
client.set('key', 'value').then(helper.isString('OK')),
client.get('key').then(helper.isString('value')),
client.getrange('key', 1, -1).then((reply) => {
assert.strictEqual(reply, 'alue')
assert.strictEqual(err, null)
})
client.exists('key', helper.isNumber(1))
}),
client.exists('key').then(helper.isNumber(1)),
// The key will be prefixed itself
client.exists('test:prefix:key', helper.isNumber(0))
client.mset('key2', 'value2', 'key3', 'value3')
client.keys('*', (err, res) => {
assert.strictEqual(err, null)
client.exists('test:prefix:key').then(helper.isNumber(0)),
client.mset('key2', 'value2', 'key3', 'value3'),
client.keys('*').then((res) => {
assert.strictEqual(res.length, 3)
assert(res.indexOf('test:prefix:key') !== -1)
assert(res.indexOf('test:prefix:key2') !== -1)
assert(res.indexOf('test:prefix:key3') !== -1)
done()
assert(res.includes('test:prefix:key'))
assert(res.includes('test:prefix:key2'))
assert(res.includes('test:prefix:key3'))
})
])
})
it('auto prefix set / get with .batch', (done) => {
it('auto prefix set / get with .batch', () => {
const batch = client.batch()
batch.set('key', 'value', helper.isString('OK'))
batch.get('key', helper.isString('value'))
batch.getrange('key', 1, -1, (err, reply) => {
assert.strictEqual(reply, 'alue')
assert.strictEqual(err, null)
})
batch.exists('key', helper.isNumber(1))
batch.set('key', 'value')
batch.get('key')
batch.getrange('key', 1, -1)
batch.exists('key')
// 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.keys('*', (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 3)
assert(res.indexOf('test:prefix:key') !== -1)
assert(res.indexOf('test:prefix:key2') !== -1)
assert(res.indexOf('test:prefix:key3') !== -1)
batch.keys('*')
return batch.exec().then((res) => {
const prefixes = res.pop()
assert.deepStrictEqual(res, ['OK', 'value', 'alue', 1, 0, 'OK'])
assert.strictEqual(prefixes.length, 3)
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()
multi.set('key', 'value', helper.isString('OK'))
multi.get('key', helper.isString('value'))
multi.getrange('key', 1, -1, (err, reply) => {
assert.strictEqual(reply, 'alue')
assert.strictEqual(err, null)
})
multi.exists('key', helper.isNumber(1))
multi.set('key', 'value')
multi.get('key')
multi.getrange('key', 1, -1)
multi.exists('key')
// 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.keys('*', (err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res.length, 3)
assert(res.indexOf('test:prefix:key') !== -1)
assert(res.indexOf('test:prefix:key2') !== -1)
assert(res.indexOf('test:prefix:key3') !== -1)
multi.keys('*')
return multi.exec().then((res) => {
const prefixes = res.pop()
assert.deepStrictEqual(res, ['OK', 'value', 'alue', 1, 0, 'OK'])
assert.strictEqual(prefixes.length, 3)
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)
sub = redis.createClient.apply(null, args)
pub.once('connect', () => {
pub.flushdb(() => {
end()
})
})
sub.once('connect', () => {
end()
})
pub.flushdb().then(() => end())
sub.once('connect', end)
})
describe('disable resubscribe', () => {
@@ -36,9 +30,7 @@ describe('publish/subscribe', () => {
sub = redis.createClient({
disableResubscribing: true
})
sub.once('connect', () => {
done()
})
sub.once('connect', done)
})
it('does not fire subscribe events after reconnecting', (done) => {
@@ -71,9 +63,7 @@ describe('publish/subscribe', () => {
sub = redis.createClient({
stringNumbers: true
})
sub.once('connect', () => {
done()
})
sub.once('connect', done)
})
it('does not fire subscribe events after reconnecting', (done) => {
@@ -86,16 +76,15 @@ describe('publish/subscribe', () => {
sub.on('unsubscribe', (chnl, count) => {
assert.strictEqual(typeof count, 'number')
assert.strictEqual(--i, count)
if (count === 0) {
assert.deepStrictEqual(sub.subscriptionSet, {})
end()
}
})
sub.subscribe(channel, channel2)
sub.unsubscribe((err, res) => { // Do not pass a channel here!
if (err) throw err
assert.strictEqual(sub.pubSubMode, 2)
assert.deepEqual(sub.subscriptionSet, {})
end()
})
sub.set('foo', 'bar', helper.isString('OK'))
sub.subscribe(channel2, end)
sub.unsubscribe()
sub.set('foo', 'bar').then(helper.isString('OK'))
sub.subscribe(channel2).then(end)
})
})
@@ -143,8 +132,8 @@ describe('publish/subscribe', () => {
it('receives messages on subscribed channel', (done) => {
const end = helper.callFuncAfter(done, 2)
sub.on('subscribe', (chnl, count) => {
pub.publish(channel, message, (err, res) => {
helper.isNumber(1)(err, res)
pub.publish(channel, message).then((res) => {
helper.isNumber(1)(res)
end()
})
})
@@ -161,8 +150,8 @@ describe('publish/subscribe', () => {
it('receives messages if subscribe is called after unsubscribe', (done) => {
const end = helper.callFuncAfter(done, 2)
sub.once('subscribe', (chnl, count) => {
pub.publish(channel, message, (err, res) => {
helper.isNumber(1)(err, res)
pub.publish(channel, message).then((res) => {
helper.isNumber(1)(res)
end()
})
})
@@ -178,20 +167,24 @@ describe('publish/subscribe', () => {
sub.subscribe(channel)
})
it('handles SUB UNSUB MSG SUB', (done) => {
sub.subscribe('chan8')
it('handles SUB UNSUB MSG SUB', () => {
return Promise.all([
sub.subscribe('chan8'),
sub.subscribe('chan9'),
sub.unsubscribe('chan9'),
pub.publish('chan8', 'something'),
sub.subscribe('chan9')
sub.unsubscribe('chan9')
pub.publish('chan8', 'something')
sub.subscribe('chan9', done)
])
})
it('handles SUB UNSUB MSG SUB 2', (done) => {
sub.psubscribe('abc*', helper.isDeepEqual([1, ['abc*']]))
it('handles SUB UNSUB MSG SUB 2', () => {
return Promise.all([
sub.psubscribe('abc*').then(helper.isDeepEqual([1, ['abc*']])),
sub.subscribe('xyz'),
sub.unsubscribe('xyz'),
pub.publish('abcd', 'something'),
sub.subscribe('xyz')
sub.unsubscribe('xyz')
pub.publish('abcd', 'something')
sub.subscribe('xyz', done)
])
})
it('emits end event if quit is called from within subscribe', (done) => {
@@ -218,8 +211,7 @@ describe('publish/subscribe', () => {
sub.select(3)
sub.subscribe(channels)
sub.on('ready', (err, results) => {
if (err) throw err
sub.on('ready', () => {
pub.publish(channels[count], msg[count])
count++
})
@@ -233,10 +225,9 @@ describe('publish/subscribe', () => {
const end = helper.callFuncAfter(done, 2)
sub.select(3)
sub.set('foo', 'bar')
sub.set('failure', helper.isError()) // Triggering a warning while subscribing should work
sub.mget('foo', 'bar', 'baz', 'hello', 'world', helper.isDeepEqual(['bar', null, null, null, null]))
sub.subscribe('somechannel', 'another channel', (err, res) => {
if (err) throw err
sub.set('failure').then(helper.fail, helper.isError()) // Triggering a warning while subscribing should work
sub.mget('foo', 'bar', 'baz', 'hello', 'world').then(helper.isDeepEqual(['bar', null, null, null, null]))
sub.subscribe('somechannel', 'another channel').then((res) => {
end()
sub.stream.destroy()
})
@@ -244,56 +235,59 @@ describe('publish/subscribe', () => {
sub.on('ready', () => {
sub.unsubscribe()
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) => {
// The unsubscribe should not be triggered, as there was no corresponding channel
throw new Error('Test failed')
})
sub.set('foo', 'bar')
sub.unsubscribe(helper.isDeepEqual([0, []]))
sub.del('foo', done)
return Promise.all([
sub.set('foo', 'bar'),
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 subscribedChannels = [1, 2, 2, 2, 3, 3]
let i = 0
sub.subscribe(channels)
sub.on('subscribe', (channel, count) => {
const compareChannel = channels.shift()
if (Buffer.isBuffer(channel)) {
assert.strictEqual(channel.inspect(), Buffer.from(channels[i]).inspect())
assert.strictEqual(channel.inspect(), Buffer.from(compareChannel).inspect())
} else {
assert.strictEqual(channel, channels[i].toString())
assert.strictEqual(channel, compareChannel.toString())
}
assert.strictEqual(count, subscribedChannels[i])
i++
assert.strictEqual(count, subscribedChannels.shift())
})
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) => {
sub.subscribe('/foo', '/bar')
sub.batch().unsubscribe(['/bar'], () => {
pub.pubsub('channels', helper.isDeepEqual(['/foo'], () => {
sub.batch().unsubscribe(['/bar']).exec().then(() => {
pub.pubsub('channels').then((res) => {
helper.isDeepEqual(['/foo'])(res)
sub.stream.destroy()
sub.once('ready', () => {
pub.pubsub('channels', helper.isDeepEqual(['/foo'], () => {
sub.unsubscribe('/foo', done)
}))
pub.pubsub('channels').then((res) => {
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) {
sub.unsubscribe(helper.isNull)
sub.subscribe(channels, helper.isNull)
sub.unsubscribe().then(helper.isNull)
sub.subscribe(channels).then(helper.isNull)
}
let all = false
const subscribeMsg = ['1', '3', '2', '5', 'test', 'bla']
@@ -318,35 +312,7 @@ describe('publish/subscribe', () => {
subscribe(['5', 'test', 'bla'])
})
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without callbacks', (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) => {
it('unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without concrete channels', (done) => {
function subscribe (channels) {
sub.unsubscribe(channels)
sub.unsubscribe(channels)
@@ -377,11 +343,8 @@ describe('publish/subscribe', () => {
it('unsubscribes, subscribes, unsubscribes... with pattern matching', (done) => {
function subscribe (channels, callback) {
sub.punsubscribe('prefix:*', helper.isNull)
sub.psubscribe(channels, (err, res) => {
helper.isNull(err)
if (callback) callback(err, res)
})
sub.punsubscribe('prefix:*').then(helper.isNull)
sub.psubscribe(channels).then(callback)
}
let all = false
const end = helper.callFuncAfter(done, 8)
@@ -414,19 +377,16 @@ describe('publish/subscribe', () => {
})
subscribe(['prefix:*', 'prefix:3'], () => {
pub.publish('prefix:1', Buffer.from('test'), () => {
pub.publish('prefix:1', Buffer.from('test')).then(() => {
subscribe(['prefix:2'])
subscribe(['5', 'test:a', 'bla'], () => {
assert(all)
})
sub.punsubscribe((err, res) => {
assert(!err)
subscribe(['5', 'test:a', 'bla'], () => assert(all))
sub.punsubscribe().then((res) => {
assert.deepStrictEqual(res, [0, ['prefix:3', 'prefix:2', '5', 'test:a', 'bla']])
assert(all)
all = false // Make sure the callback is actually after the emit
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) => {
assert.strictEqual(chnl, channel)
assert.strictEqual(count, 0)
return done()
done()
})
})
@@ -455,17 +415,19 @@ describe('publish/subscribe', () => {
sub.subscribe(channel)
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) => {
sub.unsubscribe(helper.isDeepEqual([0, []], done))
it('sub executes when unsubscribe is called and there are no subscriptions', () => {
return sub.unsubscribe().then(helper.isDeepEqual([0, []]))
})
it('pub executes callback when unsubscribe is called and there are no subscriptions', (done) => {
pub.unsubscribe(helper.isDeepEqual([0, []]))
pub.get('foo', done)
it('pub executes when unsubscribe is called and there are no subscriptions', () => {
return Promise.all([
pub.unsubscribe().then(helper.isDeepEqual([0, []])),
pub.get('foo')
])
})
})
@@ -474,28 +436,28 @@ describe('publish/subscribe', () => {
const sub2 = redis.createClient({
returnBuffers: true
})
sub.subscribe('/foo', () => {
sub.subscribe('/foo').then(() => {
sub2.on('ready', () => {
sub2.batch().psubscribe('*', helper.isDeepEqual([1, ['*']])).exec()
sub2.subscribe('/foo', () => {
pub.pubsub('numsub', '/foo', helper.isDeepEqual(['/foo', 2]))
sub2.batch().psubscribe('*').exec().then(helper.isDeepEqual([[1, ['*']]]))
sub2.subscribe('/foo').then(() => {
pub.pubsub('numsub', '/foo').then(helper.isDeepEqual(['/foo', 2]))
// 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) => {
assert.strictEqual(pattern.inspect(), Buffer.from('*').inspect())
assert.strictEqual(channel.inspect(), Buffer.from('/foo').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) => {
const end = helper.callFuncAfter(done, 6)
const end = helper.callFuncAfter(done, 3)
const data = Array(10000).join('äüs^öéÉÉ`e')
sub.set('foo', data, () => {
sub.set('foo', data).then(() => {
sub.get('foo')
sub.stream.once('data', () => {
assert.strictEqual(sub.messageBuffers, false)
@@ -503,7 +465,7 @@ describe('publish/subscribe', () => {
sub.on('pmessageBuffer', (pattern, channel, message) => {
assert.strictEqual(pattern.inspect(), Buffer.from('*').inspect())
assert.strictEqual(channel.inspect(), Buffer.from('/foo').inspect())
sub.quit(end)
sub.quit().then(end)
})
assert.notStrictEqual(sub.messageBuffers, sub.buffers)
})
@@ -511,16 +473,16 @@ describe('publish/subscribe', () => {
batch.psubscribe('*')
batch.subscribe('/foo')
batch.unsubscribe('/foo')
batch.unsubscribe(helper.isDeepEqual([1, []]))
batch.subscribe(['/foo'], helper.isDeepEqual([2, ['/foo']]))
batch.exec(() => {
batch.unsubscribe()
batch.subscribe(['/foo'])
batch.exec().then(() => {
// 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
pub.pubsub('channels', helper.isDeepEqual(['/foo'], end))
pub.pubsub('channels').then(helper.isDeepEqual(['/foo']))
// One pattern is active
pub.pubsub('numpat', helper.isNumber(1, end))
pub.publish('/foo', 'hello world', helper.isNumber(2))
pub.pubsub('numpat').then(helper.isNumber(1))
pub.publish('/foo', 'hello world').then(helper.isNumber(2))
})
// Either messageBuffers or buffers has to be true, but not both at the same time
sub.on('pmessage', (pattern, channel, message) => {
@@ -540,39 +502,28 @@ describe('publish/subscribe', () => {
describe('punsubscribe', () => {
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) => {
pub.batch().punsubscribe(helper.isDeepEqual([0, []])).exec(done)
it('executes when punsubscribe is called and there are no subscriptions', () => {
return pub.batch().punsubscribe(helper.isDeepEqual([0, []])).exec()
})
})
describe('fail for other commands while in pub sub mode', () => {
it('return error if only pub sub commands are allowed', (done) => {
sub.subscribe('channel')
it('return error if only pub sub commands are allowed', () => {
return Promise.all([
sub.subscribe('channel'),
// Ping is allowed even if not listed as such!
sub.ping((err, res) => {
assert.strictEqual(err, null)
assert.strictEqual(res[0], 'pong')
})
sub.ping().then(helper.isDeepEqual(['pong', ''])),
// Get is forbidden
sub.get('foo', (err, res) => {
sub.get('foo').then(helper.fail).catch((err) => {
assert(/^ERR only \(P\)SUBSCRIBE \/ \(P\)UNSUBSCRIBE/.test(err.message))
assert.strictEqual(err.command, 'GET')
})
}),
// 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')
sub.quit()
])
})
})
@@ -613,31 +564,28 @@ describe('publish/subscribe', () => {
pub.set('foo', 'message')
pub.set('bar', 'hello')
pub.mget('foo', 'bar')
pub.subscribe('channel', () => {
setTimeout(done, 50)
})
pub.subscribe('channel').then(() => setTimeout(done, 50))
pub.on('message', (msg) => {
done(new Error(`This message should not have been published: ${msg}`))
})
})
it('arguments variants', (done) => {
sub.batch()
it('arguments variants', () => {
return sub.batch()
.info(['stats'])
.info()
.client('KILL', ['type', 'pubsub'])
.client('KILL', ['type', 'pubsub'], () => {})
.client('KILL', ['type', 'pubsub'])
.unsubscribe()
.psubscribe(['pattern:*'])
.punsubscribe('unknown*')
.punsubscribe(['pattern:*'])
.exec((err, res) => {
if (err) throw err
sub.client('kill', ['type', 'pubsub'])
sub.psubscribe('*')
sub.punsubscribe('pa*')
sub.punsubscribe(['a', '*'], done)
})
.exec().then(() => Promise.all([
sub.client('kill', ['type', 'pubsub']),
sub.psubscribe('*'),
sub.punsubscribe('pa*'),
sub.punsubscribe(['a', '*'])
]))
})
afterEach(() => {

View File

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

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.')
end()
})
client.once('error', done)
client.once('connect', () => {
client.flushdb((err) => {
client.flushdb()
client.hmset('hash key 2', 'key 1', 'val 1', 'key 2', 'val 2')
client.set('string key 1', 'string value')
end(err)
})
client.set('string key 1', 'string value').then(end)
})
afterEach(() => {
client.end(true)
})
describe('get', () => {
describe('first argument is a string', () => {
it('returns a buffer', (done) => {
client.get('string key 1', (err, reply) => {
it('returns a buffer', () => {
return client.get('string key 1').then((reply) => {
assert.strictEqual(true, Buffer.isBuffer(reply))
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) => {
client.multi().get('string key 1').exec((err, reply) => {
it('returns a buffer when executed as part of transaction', () => {
return client.multi().get('string key 1').exec().then((reply) => {
assert.strictEqual(1, reply.length)
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())
return done(err)
})
})
})
})
describe('multi.hget', () => {
it('returns buffers', (done) => {
client.multi()
it('returns buffers', () => {
return client.multi()
.hget('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', 'key 2')
.exec((err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
.exec().then((reply) => {
assert.strictEqual(4, reply.length)
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect())
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(true, Buffer.isBuffer(reply[3]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[3].inspect())
return done(err)
})
})
})
describe('batch.hget', () => {
it('returns buffers', (done) => {
client.batch()
it('returns buffers', () => {
return client.batch()
.hget('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', 'key 2')
.exec((err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
.exec().then((reply) => {
assert.strictEqual(4, reply.length)
assert.strictEqual('<Buffer 76 61 6c 20 31>', reply[0].inspect())
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(true, Buffer.isBuffer(reply[3]))
assert.strictEqual('<Buffer 76 61 6c 20 32>', reply[3].inspect())
return done(err)
})
})
})
describe('hmget', () => {
describe('first argument is a string', () => {
it('handles array of strings with undefined values in transaction (repro #344)', (done) => {
client.multi().hmget('hash key 2', 'key 3', 'key 4').exec((err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
it('handles array of strings with undefined values in transaction (repro #344)', () => {
return client.multi().hmget('hash key 2', 'key 3', 'key 4').exec().then((reply) => {
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', () => {
it('returns buffers for keys requested', (done) => {
client.hmget(Buffer.from('hash key 2'), 'key 1', 'key 2', (err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
it('returns buffers for keys requested', () => {
return client.hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').then((reply) => {
assert.strictEqual(2, reply.length)
assert.strictEqual(true, Buffer.isBuffer(reply[0]))
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 32>', reply[1].inspect())
return done(err)
})
})
it('returns buffers for keys requested in transaction', (done) => {
client.multi().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
it('returns buffers for keys requested in transaction', () => {
return client.multi().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length)
assert.strictEqual(true, Buffer.isBuffer(reply[0][0]))
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 32>', reply[0][1].inspect())
return done(err)
})
})
it('returns buffers for keys requested in .batch', (done) => {
client.batch().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec((err, reply) => {
assert.strictEqual(true, Array.isArray(reply))
it('returns buffers for keys requested in .batch', () => {
return client.batch().hmget(Buffer.from('hash key 2'), 'key 1', 'key 2').exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual(2, reply[0].length)
assert.strictEqual(true, Buffer.isBuffer(reply[0][0]))
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 32>', reply[0][1].inspect())
return done(err)
})
})
})
})
describe('hgetall', (done) => {
describe('hgetall', () => {
describe('first argument is a string', () => {
it('returns buffer values', (done) => {
client.hgetall('hash key 2', (err, reply) => {
it('returns buffer values', () => {
return client.hgetall('hash key 2').then((reply) => {
assert.strictEqual('object', typeof reply)
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 32>', reply['key 2'].inspect())
return done(err)
})
})
it('returns buffer values when executed in transaction', (done) => {
client.multi().hgetall('hash key 2').exec((err, reply) => {
it('returns buffer values when executed in transaction', () => {
return client.multi().hgetall('hash key 2').exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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 32>', reply[0]['key 2'].inspect())
return done(err)
})
})
it('returns buffer values when executed in .batch', (done) => {
client.batch().hgetall('hash key 2').exec((err, reply) => {
it('returns buffer values when executed in .batch', () => {
return client.batch().hgetall('hash key 2').exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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 32>', reply[0]['key 2'].inspect())
return done(err)
})
})
})
describe('first argument is a buffer', () => {
it('returns buffer values', (done) => {
client.hgetall(Buffer.from('hash key 2'), (err, reply) => {
assert.strictEqual(null, err)
it('returns buffer values', () => {
return client.hgetall(Buffer.from('hash key 2')).then((reply) => {
assert.strictEqual('object', typeof reply)
assert.strictEqual(2, Object.keys(reply).length)
assert.strictEqual(true, Buffer.isBuffer(reply['key 1']))
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 32>', reply['key 2'].inspect())
return done(err)
})
})
it('returns buffer values when executed in transaction', (done) => {
client.multi().hgetall(Buffer.from('hash key 2')).exec((err, reply) => {
it('returns buffer values when executed in transaction', () => {
return client.multi().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].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) => {
client.batch().hgetall(Buffer.from('hash key 2')).exec((err, reply) => {
it('returns buffer values when executed in .batch', () => {
return client.batch().hgetall(Buffer.from('hash key 2')).exec().then((reply) => {
assert.strictEqual(1, reply.length)
assert.strictEqual('object', typeof reply[0])
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('<Buffer 76 61 6c 20 31>', reply[0]['key 1'].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 sub
const channel = 'test channel'
const message = Buffer.from('test message')
@@ -247,25 +226,12 @@ describe('returnBuffers', () => {
})
beforeEach((done) => {
let pubConnected
let subConnected
const end = helper.callFuncAfter(done, 2)
pub = redis.createClient.apply(redis.createClient, basicArgs)
sub = redis.createClient.apply(null, args)
pub.once('connect', () => {
pub.flushdb(() => {
pubConnected = true
if (subConnected) {
done()
}
})
})
sub.once('connect', () => {
subConnected = true
if (pubConnected) {
done()
}
})
pub.flushdb().then(end)
sub.once('connect', end)
})
it('receives buffer messages', (done) => {
@@ -276,7 +242,7 @@ describe('returnBuffers', () => {
sub.on('message', (chnl, msg) => {
assert.strictEqual(true, Buffer.isBuffer(msg))
assert.strictEqual('<Buffer 74 65 73 74 20 6d 65 73 73 61 67 65>', msg.inspect())
return done()
done()
})
sub.subscribe(channel)

View File

@@ -86,7 +86,7 @@ describe.skip('TLS connection tests', () => {
})
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()
const tls = utils.clone(tlsOptions)
tls.port = tlsPort
@@ -103,10 +103,10 @@ describe.skip('TLS connection tests', () => {
assert(client.stream.encrypted)
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()
const faultyCert = utils.clone(tlsOptions)
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)
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 () {}
}
const clone = utils.clone(obj)
assert.deepEqual(clone, obj)
assert.deepStrictEqual(clone, obj)
assert.strictEqual(obj.fn, clone.fn)
assert(typeof clone.fn === 'function')
})
@@ -42,9 +42,7 @@ describe('utils.js', () => {
describe('replyInOrder', () => {
let errCount = 0
let resCount = 0
let emitted = false
const clientMock = {
emit () { emitted = true },
offlineQueue: new Queue(),
commandQueue: new Queue()
}
@@ -62,7 +60,6 @@ describe('utils.js', () => {
clientMock.commandQueue.clear()
errCount = 0
resCount = 0
emitted = false
})
it('no elements in either queue. Reply in the next tick with callback', (done) => {
@@ -74,16 +71,6 @@ describe('utils.js', () => {
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) => {
clientMock.offlineQueue.push(createCommandObj(), createCommandObj())
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) => {
clientMock.commandQueue.push({}, createCommandObj(), {})
clientMock.commandQueue.push(createCommandObj(), createCommandObj(), createCommandObj())
utils.replyInOrder(clientMock, () => {
assert.strictEqual(clientMock.commandQueue.length, 0)
assert(emitted)
assert.strictEqual(errCount, 1)
assert.strictEqual(errCount, 3)
assert.strictEqual(resCount, 0)
done()
}, 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) => {
clientMock.commandQueue.push(createCommandObj(), createCommandObj())
clientMock.offlineQueue.push(createCommandObj(), {})
clientMock.offlineQueue.push(createCommandObj(), createCommandObj())
utils.replyInOrder(clientMock, (err, res) => {
if (err) throw err
assert.strictEqual(clientMock.commandQueue.length, 0)
assert.strictEqual(clientMock.offlineQueue.length, 0)
assert(!emitted)
assert.strictEqual(resCount, 3)
assert.strictEqual(resCount, 4)
done()
}, null, null)
while (clientMock.offlineQueue.length) {